Is this proper usage of the windows file API? (Multiple overlapped requests) - c++

I have encountered some strange behavior when using the windows file API, specifically ReadFile with overlapped IO.
Under certain conditions, GetOverlappedResult will successfully read data into the provided buffer, but set lpNumberOfBytesTransferred to zero, instead of the correct amount that was read.
This only seems to happen when multiple overlapped read request are issued on the same handle, when the file was previously opened with FILE_FLAG_NO_BUFFERING.
Here is a full sample of code that illustrates the issue...
#include <Windows.h>
#include <string>
#include <iostream>
const int PageSize = 4096;
const int BufferSize = PageSize * 4;
struct OperationSlot
{
OVERLAPPED state;
unsigned char* buffer;
};
bool enableFail;
bool ReadTest(std::string filename, DWORD flags, int queueSize)
{
bool result = true;
if (enableFail)
{
HANDLE temp = CreateFile(filename.c_str(), GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, FILE_FLAG_NO_BUFFERING, 0);
CloseHandle(temp);
}
OperationSlot* slots = new OperationSlot[queueSize];
for (int i = 0; i < queueSize; ++i)
{
slots[i].buffer = (unsigned char*)_aligned_malloc(BufferSize, PageSize);
ZeroMemory(slots[i].buffer, BufferSize);
}
HANDLE file = CreateFile(filename.c_str(), GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, flags, NULL);
HANDLE controlFile = CreateFile(filename.c_str(), GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL);
unsigned char* controlBuffer = new unsigned char[BufferSize];
// Start async read operations...
for (int i = 0; i < queueSize; ++i)
{
ZeroMemory(&slots[i].state, sizeof(OVERLAPPED));
slots[i].state.Offset = i * BufferSize;
bool ok = ReadFile(file, slots[i].buffer, BufferSize, NULL, &slots[i].state);
if (!ok)
{
DWORD err = GetLastError();
if (err != ERROR_IO_PENDING)
{
std::cout << "ReadFile set error code " << err << std::endl;
}
}
}
int readId = 0;
while (true)
{
OperationSlot& active_slot = slots[readId % queueSize];
DWORD bytes = 0;
bool ok = GetOverlappedResult(file, &active_slot.state, &bytes, true);
DWORD err = GetLastError();
DWORD controlBytes = 0;
ReadFile(controlFile, controlBuffer, BufferSize, &controlBytes, NULL);
bool dataok = memcmp(active_slot.buffer, controlBuffer, controlBytes) == 0;
if (!dataok)
std::cout << "Data mismatch." << std::endl;
if (bytes != controlBytes)
{
std::cout << "Error with QueueSize (" << queueSize << ") and flags: ";
if (flags & FILE_FLAG_OVERLAPPED)
{
std::cout << "FILE_FLAG_OVERLAPPED";
}
if (flags & FILE_FLAG_NO_BUFFERING)
{
std::cout << " | FILE_FLAG_NO_BUFFERING";
}
if (flags & FILE_FLAG_SEQUENTIAL_SCAN)
{
std::cout << " | FILE_FLAG_SEQUENTIAL_SCAN";
}
std::cout << std::endl;
std::cout << "Read size error, expected " << controlBytes << ", got " << bytes << std::endl;
std::cout << "GetOverlappedResult returned " << ok << " with error code " << err << std::endl;
result = false;
}
if (controlBytes < BufferSize)
break;
ZeroMemory(&active_slot.state, sizeof(OVERLAPPED));
active_slot.state.Offset = (readId + queueSize) * BufferSize;
ReadFile(file, active_slot.buffer, BufferSize, NULL, &active_slot.state);
++readId;
}
CloseHandle(file);
CloseHandle(controlFile);
delete[] controlBuffer;
for (int i = 0; i < queueSize; ++i)
{
_aligned_free(slots[i].buffer);
}
delete[] slots;
return !result;
}
int main()
{
enableFail = false;
int totalfail = 0;
std::cout << "Testing without fail." << std::endl;
totalfail += ReadTest("C:\\Test\\SmallFile.txt.txt", FILE_FLAG_OVERLAPPED, 1);
totalfail += ReadTest("C:\\Test\\SmallFile.txt.txt", FILE_FLAG_OVERLAPPED | FILE_FLAG_SEQUENTIAL_SCAN, 1);
totalfail += ReadTest("C:\\Test\\SmallFile.txt.txt", FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING, 1);
totalfail += ReadTest("C:\\Test\\SmallFile.txt.txt", FILE_FLAG_OVERLAPPED, 4);
totalfail += ReadTest("C:\\Test\\SmallFile.txt.txt", FILE_FLAG_OVERLAPPED | FILE_FLAG_SEQUENTIAL_SCAN, 4);
totalfail += ReadTest("C:\\Test\\SmallFile.txt.txt", FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING, 4);
totalfail += ReadTest("C:\\Test\\LargeFile.txt", FILE_FLAG_OVERLAPPED, 1);
totalfail += ReadTest("C:\\Test\\LargeFile.txt", FILE_FLAG_OVERLAPPED | FILE_FLAG_SEQUENTIAL_SCAN, 1);
totalfail += ReadTest("C:\\Test\\LargeFile.txt", FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING, 1);
totalfail += ReadTest("C:\\Test\\LargeFile.txt", FILE_FLAG_OVERLAPPED, 4);
totalfail += ReadTest("C:\\Test\\LargeFile.txt", FILE_FLAG_OVERLAPPED | FILE_FLAG_SEQUENTIAL_SCAN, 4);
totalfail += ReadTest("C:\\Test\\LargeFile.txt", FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING, 4);
std::cout << totalfail << " calls failed." << std::endl;
enableFail = true;
totalfail = 0;
std::cout << "Testing with fail enabled." << std::endl;
totalfail += ReadTest("C:\\Test\\SmallFile.txt.txt", FILE_FLAG_OVERLAPPED, 1);
totalfail += ReadTest("C:\\Test\\SmallFile.txt.txt", FILE_FLAG_OVERLAPPED | FILE_FLAG_SEQUENTIAL_SCAN, 1);
totalfail += ReadTest("C:\\Test\\SmallFile.txt.txt", FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING, 1);
totalfail += ReadTest("C:\\Test\\SmallFile.txt.txt", FILE_FLAG_OVERLAPPED, 4);
totalfail += ReadTest("C:\\Test\\SmallFile.txt.txt", FILE_FLAG_OVERLAPPED | FILE_FLAG_SEQUENTIAL_SCAN, 4);
totalfail += ReadTest("C:\\Test\\SmallFile.txt.txt", FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING, 4);
totalfail += ReadTest("C:\\Test\\LargeFile.txt", FILE_FLAG_OVERLAPPED, 1);
totalfail += ReadTest("C:\\Test\\LargeFile.txt", FILE_FLAG_OVERLAPPED | FILE_FLAG_SEQUENTIAL_SCAN, 1);
totalfail += ReadTest("C:\\Test\\LargeFile.txt", FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING, 1);
totalfail += ReadTest("C:\\Test\\LargeFile.txt", FILE_FLAG_OVERLAPPED, 4);
totalfail += ReadTest("C:\\Test\\LargeFile.txt", FILE_FLAG_OVERLAPPED | FILE_FLAG_SEQUENTIAL_SCAN, 4);
totalfail += ReadTest("C:\\Test\\LargeFile.txt", FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING, 4);
std::cout << totalfail << " calls failed." << std::endl;
system("pause");
return 0;
}
On my system, this results in 4 of the calls 'failing'. (The ones with a 'queue size' of more than one.)
What happens is that the overlapped version reports that it only reads 0 bytes, while the 'normal' file handle reads 20. (A small text file that says "this is a test".)
The other strange thing is that it is actually reading the data properly. The correct buffer is populated with the correct data, only the reported amount of data transferred is wrong...
And this only happens if the file opened and closed with FILE_FLAG_NO_BUFFERING right before the file is opened.
Why would previously touching a file cause subsequent access to behave differently like this?
Am I doing something that's not supported, or is the API not functioning the way it is supposed to? (Or possibly there is a mistake in my code I have overlooked...?)
EDIT: It seems I was lulled into thinking I was using the API correctly by the fact that it was miraculously working under most conditions. The proper way to do this is to specify unique events for each overlapped structure, as pointed out in the accepted answer. After giving each overlapped request it's own event, it works consistently.

You're not giving your OVERLAPPED structures unique events, so all GetOverlappedResult() has to wait on is the file handle - and with multiple requests outstanding there's no guarantee the request you've asked for will actually be the one that completed when the file handle gets signalled.
Each OVERLAPPED structure should have its hEvent member initialized to a new event handle created with CreateEvent().

Related

How to write and calculate hash of a file without closing it in between

I'm trying to calculate MD5 of a file right after writing it:
std::wstring filePath = L"file.txt";
auto hFile = CreateFile(
filePath.c_str(),
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ,
NULL,
OPEN_ALWAYS,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,
NULL);
//writing to file with WriteFile(hFile, buffer, DWORD(size), &written, NULL))
Now if I close it and reopen it's OK.
But I'm trying to calculate MD5 without closing it.
To be sure that pointer is set to correct position, I also tried to save a pointer to file's end:
LARGE_INTEGER liOfs = {0};
LARGE_INTEGER liNew = {0};
SetFilePointerEx(hFile, liOfs, &liNew, FILE_CURRENT);
SetFilePointer(hFile, 0, NULL, FILE_BEGIN);
auto md5 = CalculateMd5(hFile); // md5 is correct here
// restore pointer
SetFilePointerEx(hFile, liNew, NULL, FILE_BEGIN);
CloseHandle(hFile);
So, I'm getting exception at CloseHandle(hFile): 0xC0000008: An invalid handle was specified.
And there is MD5 calculating:
std::string GetMD5HashOfFile(HANDLE hFile)
{
if (INVALID_HANDLE_VALUE == hFile) {
return {};
}
HCRYPTPROV hProv = 0;
if (!CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) {
return {};
}
HCRYPTHASH hHash = 0;
if (!CryptCreateHash(hProv, CALG_MD5, 0, 0, &hHash)) {
CryptReleaseContext(hProv, 0);
return {};
}
static constexpr int BUFSIZE = 1024;
DWORD cbRead = 0;
BYTE rgbFile[BUFSIZE];
BOOL bResult = FALSE;
while (bResult = ReadFile(hFile, rgbFile, BUFSIZE, &cbRead, NULL)) {
if (0 == cbRead) {
break;
}
if (!CryptHashData(hHash, rgbFile, cbRead, 0)) {
CryptReleaseContext(hProv, 0);
CryptDestroyHash(hHash);
return {};
}
}
if (!bResult) {
CryptReleaseContext(hProv, 0);
CryptDestroyHash(hHash);
return {};
}
static constexpr int MD5LEN = 16;
CHAR rgbDigits[] = "0123456789abcdef";
BYTE rgbHash[MD5LEN];
DWORD cbHash = MD5LEN;
if (CryptGetHashParam(hHash, HP_HASHVAL, rgbHash, &cbHash, 0)) {
std::ostringstream oss;
for (auto c : rgbHash) {
oss.fill('0');
oss.width(2);
oss << std::hex << static_cast<int>(c);
}
CryptDestroyHash(hHash);
CryptReleaseContext(hProv, 0);
return oss.str();
}
else {
CryptDestroyHash(hHash);
CryptReleaseContext(hProv, 0);
return {};
}
CryptDestroyHash(hHash);
CryptReleaseContext(hProv, 0);
return {};
}
Here is the test program:
#include <Windows.h>
#include <wincrypt.h>
#include <iostream>
#include <sstream>
int main()
{
const std::wstring filePath = L"test.txt";
auto r = DeleteFile(filePath.c_str());
if (!r) {
auto e = GetLastError();
if (e != ERROR_FILE_NOT_FOUND) {
std::cout << e << '\n';
return -1;
}
}
auto hFile = CreateFile(filePath.c_str(),
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ,
NULL,
OPEN_ALWAYS,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,
NULL);
if (hFile == INVALID_HANDLE_VALUE) {
return -1;
}
DWORD written = 0;
const std::wstring buffer = L"Hello, world.";
const auto size = buffer.length() * sizeof(wchar_t);
if (!WriteFile(hFile, buffer.c_str(), size, &written, NULL)) {
CloseHandle(hFile);
return -1;
}
if (size != written) {
CloseHandle(hFile);
return -1;
}
/*CloseHandle(hFile);
hFile = CreateFile(filePath.c_str(),
GENERIC_READ,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,
NULL);*/
LARGE_INTEGER liOfs = { 0 };
LARGE_INTEGER liNew = { 0 };
SetFilePointerEx(hFile, liOfs, &liNew, FILE_CURRENT);
auto md5 = GetMD5HashOfFile(hFile);
std::cout << "MD5: " << md5 << '\n';
SetFilePointerEx(hFile, liNew, NULL, FILE_BEGIN);
CloseHandle(hFile);
return 0;
}
It doesn't throw exception. But it somehow calculates incorrect hash: app's MD5 is d41d8cd98f00b204e9800998ecf8427e, and cmd tool shows another - 1207b6ae90980a5b039d57384b8bbd26. If I uncomment lines in the middle hashes are equal, but still no exception. Command to check hash is:
certutil -hashfile test.txt MD5
UPDATE: I'm really sorry. It's a third question where I cann't debug my app properly. Actually, the file was closed twice, hence the exception. I swear, I'll try to do something with myself).
The only question left: is it possible to calculate file hash properly, because without closing the file handle in between gives a wrong hash.

PE Executable gets corrupted when adding a new section to it

I am trying to add a new section to a portable executable, where I need to write some data, in this case an entire file.
The second file is able to parse itself and read the data from the section I created inside of it, but for some reason, it gets corrupted when I modify it using the code below.
The Imgur links are below: I'm sorry for the bad formatting :(
This messagebox should appear.
But I get this error message instead: "This app cannot run on your pc."
The new section gets added properly inside the PE:
The data inside the new section:
I can't really tell what is wrong here.
#include <iostream>
#include <Windows.h>
#include <ShlObj.h>
#include <Shlwapi.h>
#pragma comment(lib, "Shell32.lib")
#pragma comment(lib, "Shlwapi.lib")
DWORD align(DWORD size, DWORD align, DWORD addr) {
if (!(size % align))
return addr + size;
return addr + (size / align + 1) * align;
}
int main(int argc, char* argv[])
{
if (argc < 3)
{
std::cout << "Argomenti insufficienti.\n";
return 0;
}
char PathToSave[MAX_PATH];
if (SUCCEEDED(SHGetFolderPathA(NULL, CSIDL_COMMON_DOCUMENTS, NULL, 0, PathToSave)))
{
PathAppendA(PathToSave, "Bind.exe");
}
HANDLE fOutput = CreateFileA(PathToSave, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, NULL, NULL); // Unused
HANDLE FirstFile = CreateFileA(argv[5], GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, NULL, NULL); // File to read data from
if (FirstFile == INVALID_HANDLE_VALUE)
{
std::cout << "Impossibile aprire il file passato come primo argomento.\n";
return 0;
}
HANDLE SecFile = CreateFileA(argv[5], GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, NULL, NULL); // File to write the read data in
if (SecFile == INVALID_HANDLE_VALUE)
{
std::cout << "Impossibile aprire il file passato come secondo argomento.\n";
return 0;
}
DWORD FirstFS = GetFileSize(FirstFile, 0); // First file dimension
DWORD SecondFS = GetFileSize(SecFile, 0); // Second file dimension
BYTE* FirstFB = (BYTE*)VirtualAlloc(NULL, FirstFS, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); // Allocates memory for the first file
BYTE* SecondFB = (BYTE*)VirtualAlloc(NULL, SecondFS, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); // Allocates memory for the second file
DWORD BytesRead = 0;
DWORD BytesWritten = 0;
if (bool Read = ReadFile(FirstFile, FirstFB, FirstFS, &BytesRead, NULL) == FALSE) // Reads the first file
{
std::cout << "Impossibile leggere primo file.\n";
return 0;
}
else
{
std::cout << "Letti " << BytesRead << " dal primo file.\n";
BytesRead = 0;
}
if (bool Read = ReadFile(SecFile, SecondFB, SecondFS, &BytesRead, NULL) == FALSE) // Reads the second file
{
std::cout << "Impossibile leggere secondo file.\n";
return 0;
}
else
{
std::cout << "Letti " << BytesRead << " bytes dal secondo file.\n";
BytesRead = 0;
}
/*
*
* The code is problematic beyond this point!
*
* SecondFB = Pointer to the second file's data buffer that needs to be modified by adding the new section.
* FirstFB = Pointer to the first file's data buffer that will be written inside the ".sdata" section.
* Both of them have been loaded in memory using VirtualAlloc.
*
* Ask me anything for further info and many, many thanks :D
*/
// Here I add a new section to the second file.
PIMAGE_DOS_HEADER sIDH = (IMAGE_DOS_HEADER*)SecondFB;
PIMAGE_NT_HEADERS sINH = (IMAGE_NT_HEADERS*)(SecondFB + sIDH->e_lfanew);
PIMAGE_FILE_HEADER sIFH = (PIMAGE_FILE_HEADER)(SecondFB + sIDH->e_lfanew + sizeof(DWORD));
PIMAGE_OPTIONAL_HEADER sIOH = (PIMAGE_OPTIONAL_HEADER)(SecondFB + sIDH->e_lfanew + sizeof(DWORD) + sizeof(IMAGE_FILE_HEADER));
PIMAGE_SECTION_HEADER sISH = (PIMAGE_SECTION_HEADER)(SecondFB + sIDH->e_lfanew + sizeof(IMAGE_NT_HEADERS));
// Here I name the new section inside the file
ZeroMemory(sISH, sizeof(IMAGE_SECTION_HEADER));
CopyMemory(sISH[sIFH->NumberOfSections].Name, ".scode", 8);
/*
0xE00000E0 = IMAGE_SCN_MEM_WRITE |
IMAGE_SCN_CNT_CODE |
IMAGE_SCN_CNT_UNINITIALIZED_DATA |
IMAGE_SCN_MEM_EXECUTE |
IMAGE_SCN_CNT_INITIALIZED_DATA |
IMAGE_SCN_MEM_READ
*/
// Here all the required information gets filled in
sISH[sIFH->NumberOfSections].VirtualAddress = align(sISH[sIFH->NumberOfSections - 1].Misc.VirtualSize, sIOH->SectionAlignment, sISH[sIFH->NumberOfSections - 1].VirtualAddress);
sISH[sIFH->NumberOfSections].SizeOfRawData = align(FirstFS, sIOH->SectionAlignment, 0);
sISH[sIFH->NumberOfSections].Misc.VirtualSize = align(FirstFS, sIOH->SectionAlignment, 0);
sISH[sIFH->NumberOfSections].PointerToRawData = align(sISH[sIFH->NumberOfSections - 1].SizeOfRawData, sIOH->FileAlignment, sISH[sIFH->NumberOfSections - 1].PointerToRawData);
sISH[sIFH->NumberOfSections].Characteristics = 0xE00000E0;
// Here the changes are written to the second file
SetFilePointer(SecFile, sISH[sIFH->NumberOfSections].PointerToRawData + sISH[sIFH->NumberOfSections].SizeOfRawData, NULL, FILE_BEGIN);
SetEndOfFile(SecFile);
sIOH->SizeOfImage = sISH[sIFH->NumberOfSections].VirtualAddress + sISH[sIFH->NumberOfSections].Misc.VirtualSize;
sIFH->NumberOfSections += 1;
SetFilePointer(SecFile, 0, NULL, FILE_BEGIN);
BytesWritten = 0;
bool W = WriteFile(SecFile, SecondFB, SecondFS, &BytesWritten, NULL);
if (W == FALSE)
{
std::cout << "Impossibile aggiungere sezione alla stub.\n";
return 0;
}
else
{
std::cout << "Scritti " << BytesWritten << " bytes nella stub. (Aggiunta nuova sezione.)\n";
BytesWritten = 0;
}
// Here I write the data inside the new section
SetFilePointer(SecFile, sISH[sIFH->NumberOfSections - 1].PointerToRawData, 0, FILE_BEGIN);
if (bool Write = WriteFile(SecFile, FirstFB, FirstFS, &BytesWritten, NULL) == FALSE)
{
std::cout << "Impossibile aggiungere sezione alla stub.\n";
}
else
{
std::cout << "Scritti " << BytesWritten << " bytes nella stub.\n";
BytesWritten = 0;
}
// Here I close all the handles
VirtualFree(FirstFB, FirstFS, MEM_RELEASE);
CloseHandle(FirstFile);
VirtualFree(SecondFB, SecondFS, MEM_RELEASE);
CloseHandle(SecFile);
std::cout << "Binding completato.\n";
return 0;
}
The problem is in ZeroMemory(sISH, sizeof(IMAGE_SECTION_HEADER));
You removed the memory of the first section, which caused problems with the section structure of the exe. This is why the exe cannot run.
Solution:
=> ZeroMemory(&sISH[sIFH->NumberOfSections], sizeof(IMAGE_SECTION_HEADER)); //Clear the memory behind the last section
Don't forget to add FILE_SHARE_WRITE for the first CreateFileA, otherwise it will cause the second CreateFileA to fail to write.
=> HANDLE FirstFile = CreateFileA(argv[5], GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, NULL, NULL); // File to read data from

Using file as a mutex with CreateFile()

I've been given a task to create 2 processes. First one opens a file "log.txt" and adds the input given by user to it.
The second process is meant to be a "monitor" of that file. It checks if it exists, gives its size and gives the number of characters entered by user since the start of the second process. I'm using the GetFileSize() function to it so it's not a problem.
I'm slighty confused by the CreateProcess() and CreateFile() functions as I'm not sure how to connect it with one another.
I've read that the CreateFile() function can be used as a mutex by changing its flags. I've come up with something like this:
HANDLE hFile = CreateFile(
"log.txt",
FILE_APPEND_DATA,
FILE_SHARE_WRITE | FILE_SHARE_READ,
0,
OPEN_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
0);
Now I'm not really sure how to connect it to processes and where to start the processes from. And also I have no idea how to check how many characters were given since the start of the second process.
Can someone explain to me when to start those 2 processes and how to connect the CreateFile() function to them?
FILE_SHARE_WRITE | FILE_SHARE_READ will allow other processes to open and share the read and write access. You need to open the file exclusively (with dwShareMode = 0), but this requires process2 to try to open the file exclusively in a loop.
Instead, use CreateMutex to create a mutex, then process1 uses WaitForSingleObject to take up the mutex, do something and then ReleaseMutex, process2 uses WaitForSingleObject waits for released mutex, and then reads the file. (One synchronization is completed)
process 2:
#include <windows.h>
#include <iostream>
int wmain(int argc, wchar_t* argv[])
{
HANDLE hMutex = OpenMutex(MUTEX_ALL_ACCESS, false, L"MyMutex");
DWORD dwWaitResult = WaitForSingleObject(hMutex, INFINITE);
if (dwWaitResult == WAIT_OBJECT_0)
{
HANDLE hFile = CreateFile(L"log.txt", FILE_READ_ATTRIBUTES, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
if (hFile == INVALID_HANDLE_VALUE)
{
std::cout << "CreateFile error " << GetLastError() << std::endl;
ReleaseMutex(hMutex);
}
else
{
DWORD size = GetFileSize(hFile, NULL);
std::cout << "File Size: " << size << std::endl;
CloseHandle(hFile);
ReleaseMutex(hMutex);
}
}
return 0;
}
process 1:
#include <windows.h>
#include <iostream>
int wmain(int argc, wchar_t* argv[])
{
HANDLE hMutex = CreateMutex(NULL, false, L"MyMutex");
DWORD dwWaitResult = WaitForSingleObject(hMutex, INFINITE);
if (dwWaitResult == WAIT_OBJECT_0)
{
STARTUPINFO si = { 0 };
si.cb = sizeof(si);
PROCESS_INFORMATION pi = { 0 };
CreateProcess(L"process2.exe",
NULL,
NULL,
NULL,
false,
CREATE_NEW_CONSOLE,
NULL,
NULL,
&si,
&pi);
std::string buffer;
std::cin >> buffer;
HANDLE hFile = CreateFile(L"log.txt", FILE_APPEND_DATA, 0, 0, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
DWORD written = 0;
WriteFile(hFile, buffer.c_str(), buffer.size(), &written, NULL);
CloseHandle(hFile);
ReleaseMutex(hMutex);
}
return 0;
}
But this is more troublesome, because you need to synchronize the two processes every time.
As #Remy Lebeau said, use ReadDirectoryChangesW in process2:
#include <windows.h>
#include <iostream>
int wmain(int argc, wchar_t* argv[])
{
FILE_NOTIFY_INFORMATION* pInfo = NULL;
HANDLE hFile = CreateFile(L"Directory of log.txt", GENERIC_READ, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS, 0);
while (1)
{
DWORD returned = 0;
DWORD dwOffset = 0;
BYTE szBuffer[1024] = { 0 };
ReadDirectoryChangesW(hFile, szBuffer, sizeof(szBuffer), false, FILE_NOTIFY_CHANGE_SIZE, &returned, NULL, NULL);
do
{
pInfo = (FILE_NOTIFY_INFORMATION*)&szBuffer[dwOffset];
if (wcscmp(pInfo->FileName, L"log.txt") == 0)
{
HANDLE hFile = CreateFile(L"path\\log.txt", FILE_APPEND_DATA, 0, 0, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
DWORD size = GetFileSize(hFile, NULL);
std::cout << "File Size: " << size << std::endl;
CloseHandle(hFile);
}
dwOffset += pInfo->NextEntryOffset;
} while (pInfo->NextEntryOffset != 0);
}
return 0;
}
And process 1 only need to get user input and write to the file:
#include <windows.h>
#include <iostream>
int wmain(int argc, wchar_t* argv[])
{
std::string buffer;
while (1)
{
std::cin >> buffer;
HANDLE hFile = CreateFile(L"log.txt", FILE_APPEND_DATA, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
DWORD written = 0;
WriteFile(hFile, buffer.c_str(), buffer.size(), &written, NULL);
CloseHandle(hFile);
}
return 0;
}
It appears that you want to synchronize these two proccesses so that the second one waits for the first one to complete writing to "log.txt".
For that, you would need to open that file in the first process with exclusive access (no FILE_SHARE_WRITE | FILE_SHARE_READ), and close it when it's done writing.
The second process would try to open that same file, also with exclusive access. CreateFile() would fail with "access denied" error if that file is still in use by the first process. This is an essence of "mutually exclusive" concept of mutex. You would then wait a little and try again.
Contrary to synchronization objects, I am not aware of the way to wait for the file to become available (easily done with WaitForSingleObject for mutex).
So I've managed to make something out of your precious comments. I'm not sure if it is a right way to solve this task. It would be nice if someone could review my code and give me some additional tips.
Here is my code
int main(int argc, char *argv[])
{
STARTUPINFO si;
PROCESS_INFORMATION pi;
ZeroMemory(&si, sizeof(si));
ZeroMemory(&pi, sizeof(pi));
si.cb = sizeof(si);
CreateProcess("process2.exe",
NULL,
NULL,
NULL,
false,
CREATE_NEW_CONSOLE,
NULL,
NULL,
&si,
&pi);
std::string buffer;
std::cout << "Enter your text:" << std::endl;
getline(std::cin, buffer);
HANDLE hFile = CreateFile("log.txt", FILE_APPEND_DATA, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
DWORD written = 0;
WriteFile(hFile, buffer.c_str(), buffer.size(), &written, NULL);
hFile = CreateFile("log.txt", FILE_READ_ATTRIBUTES, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
if (hFile == INVALID_HANDLE_VALUE)
{
std::cout << "CreateFile error " << GetLastError() << std::endl;
}
else
{
DWORD size = GetFileSize(hFile, NULL);
std::cout << "\nCurrent file size: " << size << std::endl;
CloseHandle(hFile);
}
int stringLenght = 0;
for(int i=0; buffer[i]; i++)
stringLenght++;
std::cout << "\nCharacters given since last startup: " << stringLenght << std::endl;
return 0;
}
I'm not sure if that was the point in this task or should it check the file size and ask user for input in a while loop and if it's possible to do without a mutex.

Memory mapped file C++

please help me with reading memory mapped file. I open file in code below. And then i want to read bytes from 8 to 16. How can i do that?
// 0. Handle or create and handle file
m_hFile = CreateFile(file_path.c_str(), GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (m_hFile == INVALID_HANDLE_VALUE)
{
if (GetLastError() == ERROR_FILE_NOT_FOUND)
{
m_hFile = createNewFile(file_path.c_str());
}
else throw GetLastError();
}
// 1. Create a file mapping object for the file
m_hMapFile = CreateFileMapping(m_hFile, NULL, PAGE_READWRITE, 0, 0, NULL);
if (m_hMapFile == NULL) throw GetLastError();
// 2. Map the view.
m_lpMapAddress = MapViewOfFile(m_hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, 0);
// to map
if (m_lpMapAddress == NULL) throw GetLastError();
You can access it like any other memory block. Here's an example that prints those bytes interpreted as unsigned chars:
unsigned char *mappedDataAsUChars = (unsigned char*)m_lpMapAddress;
for(int k = 8; k < 16; k++)
std::cout << "Byte at " << k << " is " << mappedDataAsUChars[k] << std::endl;

Named Pipe CreateFile() returns INVALID_HANDLE_VALUE, and GetLastError() returns ERROR_PIPE_BUSY

I have written a class to handle named pipe connections, and if I create an instance, close it, and then try to create another instance the call to CreateFile() returns INVALID_HANDLE_VALUE, and GetLastError() returns ERROR_PIPE_BUSY. What's going on here? What can I do to insure the call to Connect() succeeds?
PipeAsync A, B;
A.Connect("\\\\.\\pipe\\test",5000);
A.Close();
cout << GetLastError(); // some random value
B.Connect("\\\\.\\pipe\\test",5000);
cout << GetLastError(); // 231 (ERROR_PIPE_BUSY)
B.Close();
Here are my implementations of Connect() and Close()
BOOL PipeAsync::Connect(LPCSTR pszPipeName, DWORD dwTimeout)
{
this->pszPipeName = pszPipeName;
this->fExisting = TRUE;
DWORD dwMode = this->fMessageMode ? PIPE_READMODE_MESSAGE : PIPE_READMODE_BYTE;
hPipe = CreateFile(
this->pszPipeName,
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
FILE_FLAG_OVERLAPPED,
NULL);
if( INVALID_HANDLE_VALUE == hPipe )
return FALSE; /* set break point here ; breaks here on second call to Connect() */
if( GetLastError() == ERROR_PIPE_BUSY )
if(!WaitNamedPipe( this->pszPipeName, dwTimeout ))
return FALSE; /* set break point here */
if( !SetNamedPipeHandleState( hPipe, &dwMode, NULL, NULL ) )
return FALSE; /* set break point here */
return TRUE;
}
VOID PipeAsync::Close()
{
if( fExisting )
DisconnectNamedPipe( hPipe );
CloseHandle( hPipe );
}
EDIT: I forgot to tell you how I concluded this... I set break points indicated in the comments. When run, it stops on the first break point.
EDIT: This is my updated code
if( INVALID_HANDLE_VALUE == hPipe )
if( GetLastError() == ERROR_PIPE_BUSY )
{
if(!WaitNamedPipe( this->pszPipeName, dwTimeout ))
return FALSE; /* break-point: breaks here on second call */
}
else
return FALSE; /* break-point /*
Now, WaitNamedPipe() is returning false on the second call to Connect() and GetLastError() is returning 2, or ERROR_FILE_NOT_FOUND ?
From Named Pipe Client:
If the pipe exists but all of its instances are busy, CreateFile
returns INVALID_HANDLE_VALUE and the GetLastError function returns
ERROR_PIPE_BUSY. When this happens, the named pipe client uses the
WaitNamedPipe function to wait for an instance of the named pipe to
become available.
The link has example code on coping with ERROR_PIPE_BUSY.
EDIT:
Small compilable example that demonstrates accepting and connecting on a named pipe:
const char* const PIPE_NAME = "\\\\.\\pipe\\test";
const int MAX_CONNECTIONS = 10;
void client_main()
{
DWORD last_error;
unsigned int elapsed_seconds = 0;
const unsigned int timeout_seconds = 5;
HANDLE handle = CreateFile(PIPE_NAME,
GENERIC_READ | GENERIC_WRITE,
0,
0,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
0);
while (INVALID_HANDLE_VALUE == handle &&
elapsed_seconds < timeout_seconds)
{
last_error = GetLastError();
if (last_error != ERROR_PIPE_BUSY)
{
break;
}
Sleep(1 * 1000);
elapsed_seconds++;
handle = CreateFile(PIPE_NAME,
GENERIC_READ | GENERIC_WRITE,
0,
0,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
0);
}
if (INVALID_HANDLE_VALUE == handle)
{
std::cerr << "Failed to connect to pipe " << PIPE_NAME <<
": last_error=" << last_error << "\n";
}
else
{
std::cout << "Connected to pipe " << PIPE_NAME << "\n";
CloseHandle(handle);
}
}
HANDLE _get_server_handle()
{
// Error handling omitted for security descriptor creation.
SECURITY_DESCRIPTOR sd;
InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION);
SetSecurityDescriptorDacl(&sd, TRUE, static_cast<PACL>(0), FALSE);
SECURITY_ATTRIBUTES sa;
sa.nLength = sizeof(sa);
sa.lpSecurityDescriptor = &sd;
sa.bInheritHandle = FALSE;
// Create a bi-directional message pipe.
HANDLE handle = CreateNamedPipe(PIPE_NAME,
PIPE_ACCESS_DUPLEX,
PIPE_TYPE_MESSAGE |
PIPE_READMODE_MESSAGE |
PIPE_NOWAIT,
PIPE_UNLIMITED_INSTANCES,
4096,
4096,
0,
&sa);
if (INVALID_HANDLE_VALUE == handle)
{
std::cerr << "Failed to create named pipe handle: last_error=" <<
GetLastError() << "\n";
}
return handle;
}
void server_main()
{
HANDLE handle = _get_server_handle();
if (INVALID_HANDLE_VALUE != handle)
{
int count = 0;
while (count < MAX_CONNECTIONS)
{
BOOL result = ConnectNamedPipe(handle, 0);
const DWORD last_error = GetLastError();
if (ERROR_NO_DATA == last_error)
{
count++;
std::cout << "A client connected and disconnected: count=" <<
count << "\n";
CloseHandle(handle);
handle = _get_server_handle();
}
else if (ERROR_PIPE_CONNECTED == last_error)
{
count++;
std::cout << "A client connected before call to " <<
"ConnectNamedPipe(): count=" << count << "\n";
CloseHandle(handle);
handle = _get_server_handle();
}
else if (ERROR_PIPE_LISTENING != last_error)
{
std::cerr << "Failed to wait for connection: last_error=" <<
GetLastError() << "\n";
CloseHandle(handle);
break;
}
Sleep(100);
}
}
}
int main(int a_argc, char** a_argv)
{
if (2 == a_argc)
{
if (std::string("client") == *(a_argv + 1))
{
for (int i = 0; i < MAX_CONNECTIONS; i++)
{
client_main();
}
}
else if (std::string("server") == *(a_argv + 1))
{
server_main();
}
}
return 0;
}
Execute server-side first:
pipetest.exe server
Then execute client-side:
pipetest.exe client
I could not tell what the problem was from the posted code. Hopefully this small example will assist you in finding the issue.