Locking files using C++ on Windows - c++

I have a program writing/reading from a file, and I want to lock the file for other instances of my application. How can I do it (in c++ visual studio 2003)?
I tried using the _locking() but then also I myself cannot reach the file when trying to read/write (in the same instance).
I know there's an option of LockFile() but have no idea how to set it properly.
Please help me.

You can simply use the Win32 API CreateFile and then specify no sharing rights. This will ensure that no other processes can access the file.
The dwShareMode DWORD specifies the type of sharing you would like, for example GENERIC_READ. If you specify 0 then that means no sharing rights should be granted.
Example:
HANDLE hFile = CreateFile(_T("c:\\file.txt"), GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL);
If you want to only lock a certain part of the file you can use LockFile or LockFileEx.
Example:
//Lock the first 1024 bytes
BOOL bLocked = LockFile(hFile, 0, 0, 1024, 0);
For locking on other platforms please see my post here.

You want LockFileEx() (exclusive file locking). Have a look at this discussion from Secure Programming Cookbook for C and C++.

After searching online for a while, I didn't find any good examples.
Here are two calls to CreateFile with the intent of locking the file for the life of a process... I use this along side the CLimitSingleInstance that uses CreateMutex for a global named mutex.
The first call to CreateFile attempts to open it, the second one creates it if necessary.
I have a little bit more thorough implementation. I implemented it in Qt, hence the qCritical() instead of std::cout and the QDir::tempPath() instead of getting that some other way.
class SingleInstance
{
protected:
DWORD m_dwLastError;
HANDLE m_hFile;
public:
SingleInstance(const char *strMutexName) { }
bool attemptToLockTempFile()
{
QString lockFile = QDir::tempPath() + "/My.exe.lock";
m_hFile = CreateFileA(lockFile.toLocal8Bit().data(), GENERIC_READ, 0,
NULL, OPEN_EXISTING, 0, NULL);
DWORD dwLastError = GetLastError();
if(m_hFile != NULL && m_hFile != INVALID_HANDLE_VALUE)
{
return true;
}
else
{
if(dwLastError == ERROR_FILE_NOT_FOUND )
{
m_hFile = CreateFileA(lockFile.toLocal8Bit().data(), GENERIC_READ,
0, NULL, CREATE_NEW, 0, NULL);
dwLastError = GetLastError();
if(m_hFile != NULL && m_hFile != INVALID_HANDLE_VALUE)
{
return true;
}
else if(dwLastError == ERROR_SHARING_VIOLATION)
{
qCritical() << "Sharing Violation on My.exe.lock";
}
else
{
qCritical() << "Error reading" << "My.exe.lock" << "-" << dwLastError;
}
}
else if(dwLastError == ERROR_SHARING_VIOLATION)
{
qCritical() << "Sharing Violation on My.exe.lock";
}
else
{
qCritical() << "Unable to obtain file lock -" << dwLastError;
}
return false;
}
}
~SingleInstance()
{
if ( m_hFile != NULL && m_hFile != INVALID_HANDLE_VALUE)
{
::CloseHandle(m_hFile); //Do as late as possible.
m_hFile = NULL;
}
}
}
Here is what you would have at the top of your main function:
SingleInstance g_SingleInstanceObj(globalId_QA);
// Makes sure that the program doesn't run if there is another
// instance already running
if (g_SingleInstanceObj.IsAnotherInstanceRunning())
{
return 0;
}

Related

Detect if file is open locally or over share

I'm trying to check if a file is open in Win32:
bool CheckFileUnlocked(const TCHAR *file)
{
HANDLE fh = ::CreateFile(file, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL);
if(fh != NULL && fh != INVALID_HANDLE_VALUE) {
return (CloseHandle(fh) == TRUE);
}
return false;
}
I need to be able to distinguish if a file is opened locally, in that case the function must return true against if it is opened from a shared path. The file itself is accessible over network, and is mapped in a shared drive. The function above tries to open file with exclusive access. I tried adding else clause reducing to:
bool CheckFileUnlocked(const TCHAR *file)
{
HANDLE fh = ::CreateFile(file, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL);
if(fh != NULL && fh != INVALID_HANDLE_VALUE) {
return (CloseHandle(fh) == TRUE);
} else {
fh = ::CreateFile(file, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
if(fh != NULL && fh != INVALID_HANDLE_VALUE) {
return (CloseHandle(fh) == TRUE);
}
}
return false;
}
But I still couldn't figure out if the file was open locally or over network on another system. I also tried LockFileEx() and UnlockFileEx(), but I'm guessing these might be wrong approaches. How do I solve this without actually querying the Application (LibreOffice Writer), assuming it provides API level access to this condition (LO actually provides a popup upon opening said document and allows to open it as ReadOnly, or open a Copy)?
You can try GetFileInformationByHandleEx:
FileRemoteProtocolInfo should return properties of network access, and probably should fail on local files
FileStorageInfo should return properties of the storage, it might fail on network (but need to verify that)

Windows: Ensure a file is written to the physical disk

I wrote a C++ code that writes to file and attempts to flush it to the physical disk. By the end of the code I want to know for sure that the file is written to the physical disk and to commit that I'm in a stable state even if someone unplugs the machine. Nevertheless, when I unplug immediately after the execution of all of the following lines, the file is lost i.e. it hasn't been written to the physical disk although I attempted to flush and used FILE_FLAG_WRITE_THROUGH.
HANDLE hFile = CreateFileA(
filePath.c_str(),
GENERIC_WRITE,
0,
NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL| FILE_FLAG_WRITE_THROUGH ,
NULL);
if (hFile == INVALID_HANDLE_VALUE)
{
throw MyException("CreateFile failed");
}
DWORD bytesWritten = 0;
auto errorFlag = WriteFile(
hFile,
data.data(),
static_cast<DWORD>(data.size()),
&bytesWritten,
NULL);
if (bytesWritten != data.size() || errorFlag != TRUE)
{
CloseHandle(hFile);
throw MyException("WriteFile failed" + std::to_string(GetLastError()));
}
auto ret = FlushFileBuffers(hFile);
if (!ret)
{
CloseHandle(hFile);
throw MyException("FlushFileBuffers failed");
}
CloseHandle(hFile);
// The file isn't written to the disk yet!!!
How will I make sure that the file is already on the disk so I can commit the change?

Send IOCTL to Windows device driver - CreateFile fails

I want to send an IOCTL command to a PC/SC reader connected to my computer (win7 64 bit).
In order to send an IOCTL command I need a HANDLE to the device, which I'm unable to create.
The device is listed as "OMNIKEY 1021" in the device manager, the physical device object name is "\Device\USBPDO-15". Using the "WinObj" tool, I can detect 2 symlinks:
USB#VID_076B&PID_1021#5&291f6990&0&1#{50dd5230-ba8a-11d1-bf5d-0000f805f530}
USB#VID_076B&PID_1021#5&291f6990&0&1#{a5dcbf10-6530-11d2-901f-00c04fb951ed}
My problem: I cannot create a valid handle to this device with the CreateFile function:
I found several possible formats on MSDN/Google to use as the lpFileName param of the CreateFile function, but none of them seem to work:
\\?\Device\USBPDO-15
\\.\Device\USBPDO-15
\\GLOBAL??\Device\USBPDO-15
\GLOBAL??\Device\USBPDO-15
\\.\USBPDO-15
\\?\USB#VID_076B&PID_1021#5&291f6990&0&1#{50dd5230-ba8a-11d1-bf5d-0000f805f530}
\\.\USB#VID_076B&PID_1021#5&291f6990&0&1#{50dd5230-ba8a-11d1-bf5d-0000f805f530}
\\GLOBAL??\USB#VID_076B&PID_1021#5&291f6990&0&1#{50dd5230-ba8a-11d1-bf5d-0000f805f530}
\GLOBAL??\USB#VID_076B&PID_1021#5&291f6990&0&1#{50dd5230-ba8a-11d1-bf5d-0000f805f530}
\\?\USB#VID_076B&PID_1021#5&291f6990&0&1#{a5dcbf10-6530-11d2-901f-00c04fb951ed}
\\.\USB#VID_076B&PID_1021#5&291f6990&0&1#{a5dcbf10-6530-11d2-901f-00c04fb951ed}
\\GLOBAL??\USB#VID_076B&PID_1021#5&291f6990&0&1#{a5dcbf10-6530-11d2-901f-00c04fb951ed}
\GLOBAL??\USB#VID_076B&PID_1021#5&291f6990&0&1#{a5dcbf10-6530-11d2-901f-00c04fb951ed}
Code sample:
#include <iostream>
#include <Windows.h>
int main (int argc, char* argv[])
{
HANDLE handle = CreateFile (
L"\\\\.\\Device\\USBPDO-15",
0,
FILE_SHARE_READ, //FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
0, //FILE_FLAG_OVERLAPPED,
NULL
);
if (handle == INVALID_HANDLE_VALUE)
std::cout << "INVALID HANDLE" << std::endl;
else
std::cout << "HANDLE: " << std::hex << handle << std::endl;
}
Notes:
The returned handle is always invalid
Always running as Administrator, so the privileges should not be a problem
edit:
Solution:
The PC/SC service takes exclusive ownership of the devices, so any attempt to call 'CreateFile' will always fail.
The solution is a kernel space driver, this allows you to pass IRP's to the driver. (I was able to implement a KMDF filter driver to alter data sent/received to/from the device)
Try it my way. I'm using Setup API to enumerate all USB active devices in the system and get paths. That way you can find out whether it's the path or other arguments that CreateFile doesn't like.
I'll add some comments a bit later, if anyone's interested.
HDEVINFO hDevInfo = SetupDiGetClassDevs( &_DEVINTERFACE_USB_DEVICE, 0, 0, DIGCF_DEVICEINTERFACE | DIGCF_PRESENT);
if(hDevInfo == INVALID_HANDLE_VALUE)
{
return ERR_FAIL;
}
std::vector<SP_INTERFACE_DEVICE_DATA> interfaces;
for (DWORD i = 0; true; ++i)
{
SP_DEVINFO_DATA devInfo;
devInfo.cbSize = sizeof(SP_DEVINFO_DATA);
BOOL succ = SetupDiEnumDeviceInfo(hDevInfo, i, &devInfo);
if (GetLastError() == ERROR_NO_MORE_ITEMS)
break;
if (!succ) continue;
SP_INTERFACE_DEVICE_DATA ifInfo;
ifInfo.cbSize = sizeof(SP_INTERFACE_DEVICE_DATA);
if (TRUE != SetupDiEnumDeviceInterfaces(hDevInfo, &devInfo, &(_DEVINTERFACE_USB_DEVICE), 0, &ifInfo))
{
if (GetLastError() != ERROR_NO_MORE_ITEMS)
break;
}
interfaces.push_back(ifInfo);
}
std::vector<SP_INTERFACE_DEVICE_DETAIL_DATA*> devicePaths;
for (size_t i = 0; i < interfaces.size(); ++i)
{
DWORD requiredSize = 0;
SetupDiGetDeviceInterfaceDetail(hDevInfo, &(interfaces.at(i)), NULL, NULL, &requiredSize, NULL);
SP_INTERFACE_DEVICE_DETAIL_DATA* data = (SP_INTERFACE_DEVICE_DETAIL_DATA*) malloc(requiredSize);
assert (data);
data->cbSize = sizeof(SP_INTERFACE_DEVICE_DETAIL_DATA);
if (!SetupDiGetDeviceInterfaceDetail(hDevInfo, &(interfaces.at(i)), data, requiredSize, NULL, NULL))
{
continue;
}
devicePaths.push_back(data);
}
Just try with CreateFile(L"\\\\.\\{GUID}",etc...

how in C++ send file to browser

I need to send file from my directory to user. The problem file was not send.
Can any one help me?
My code is like:
CHttpServerContext* pCtxt;
// ... there i set headers for open
DWORD dwRead;
CString fileName = "c:\txt.doc";
HANDLE hFile = CreateFile (fileName, GENERIC_READ, FILE_SHARE_READ,
(LPSECURITY_ATTRIBUTES) NULL,
OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, (HANDLE) NULL);
if (hFile == INVALID_HANDLE_VALUE)
{
return;
}
int c = 0;
CHAR szBuffer [2048];
do
{
if (c++ > 20) {
break;
return;
}
// read chunk of the file
if (!ReadFile (hFile, szBuffer, 2048, &dwRead, NULL))
{
return;
}
if (!dwRead)
// EOF reached, bail out
break;
// Send binary chunk to the browser
if (!pCtxt->WriteClient( szBuffer, &dwRead, 0))
{
return;
}
}
while (1);
CloseHandle (hFile);
}
Doctor, I'm sick. What's wrong with me?
I mean, you give almost no information about what happened.
Do you know if some function returned you an error in your code?
Why do you abort the loop after 20 iterations? This limits you to 40KB.
How exactly do you initialize CHttpServerContext?
You might use high-performance TransmitFile function if you just send the file as-is.
What is your client? How do you know it didn't get the file?
No point in re-inventing the wheel - just use the TransmitFile API instead - it is built into CHttpServerContent::TransmitFile().

MapViewOfFile shared between 32bit and 64bit processes

I'm trying to use MapViewOfFile in a 64 bit process on a file that is already mapped to memory of another 32 bit process. It fails and gives me an "access denied" error. Is this a known Windows limitation or am I doing something wrong? Same code works fine with 2 32bit processes.
The code sort of looks like this:
hMapFile = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, szShmName);
if (NULL == hMapFile)
{ /* failed to open - create new (this happens in the 32 bit app) */
SECURITY_ATTRIBUTES sa;
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.bInheritHandle = FALSE;
/* give access to members of administrators group */
BOOL success = ConvertStringSecurityDescriptorToSecurityDescriptor(
"D:(A;OICI;GA;;;BA)",
SDDL_REVISION_1,
&(sa.lpSecurityDescriptor),
NULL);
HANDLE hShmFile = CreateFile(FILE_XXX_SHM,
FILE_ALL_ACCESS, 0,
&sa,
OPEN_ALWAYS, 0, NULL);
hMapFile = CreateFileMapping(hShmFile, &sa, PAGE_READWRITE,
0,
SHM_SIZE,
szShmName);
CloseHandle(hShmFile);
}
// this one fails in 64 bit app
pShm = MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, SHM_SIZE);
When you call CreateFile in the 32-bit application, you're passing 0 for the sharing parameter, which means no sharing is allowed. Changing that to FILE_SHARE_READ | FiLE_SHARE_WRITE would probably be a step in the right direction.
Edit: I just whipped together a demo that works (at least for me):
#include <windows.h>
#include <iostream>
static const char map_name[] = "FileMapping1";
static const char event1_name[] = "EventName1";
static const char event2_name[] = "EventName2";
int main() {
HANDLE mapping = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, map_name);
if (NULL == mapping) {
std::cout << "Calling CreateFile\n";
HANDLE file = CreateFile("MappedFile",
FILE_ALL_ACCESS,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_ALWAYS,
0,
NULL);
std::cout << "Creating File mapping\n";
mapping = CreateFileMapping(file, NULL, PAGE_READWRITE, 0, 65536, map_name);
std::cout << "Closing file handle\n";
CloseHandle(file);
}
std::cout << "Mapping view of file\n";
char *memory = (char *)MapViewOfFile(mapping, FILE_MAP_ALL_ACCESS, 0, 0, 65536);
if (memory == NULL) {
std::cerr << "Mapping Failed.\n";
return 1;
}
std::cout << "Mapping succeeded\n";
HANDLE event = CreateEvent(NULL, false, false, event1_name);
if (GetLastError()==ERROR_ALREADY_EXISTS) {
std::cout <<"Waiting to receive string:\n";
WaitForSingleObject(event, INFINITE);
std::cout << "Received: " << memory;
HANDLE event2 = CreateEvent(NULL, false, false, event2_name);
SetEvent(event2);
}
else {
char string[] = "This is the shared string";
std::cout << "Sending string: " << string << "\n";
strncpy(memory, string, sizeof(string));
SetEvent(event);
HANDLE event2 = CreateEvent(NULL, false, false, event2_name);
WaitForSingleObject(event2, INFINITE);
}
return 0;
}
Any combination of 32- or 64-bit executables seems to work fine.
Edit2: Note, however, that this is purely demo-level code. Just for example, the name of each shared object should normally contain a GUID-string to ensure against accidental collision with other programs. I've also skipped quite a bit of error checking, not to mention the minor detail that this code doesn't accomplish anything useful.