Related
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.
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.
Here is my attempt:
// ConsoleApplication1.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <windows.h>
#include <tlhelp32.h>
#include <tchar.h>
#include <iostream>
#include <string>
// Forward declarations:
void append(LPCTSTR, LPCVOID, DWORD);
void readTail(LPCTSTR, LPVOID, DWORD);
int main()
{
LPCTSTR fn = L"C:/kaiyin/kybig.out";
LPCVOID buf = "eeeee";
append(fn, buf, 5);
LPVOID buf1 = "";
readTail(fn, buf1, 5);
std::cout << (char*) buf1 << std::endl;
printf("hello world\n");
std::string s = "";
std::getline(std::cin, s);
return 0;
}
void append(LPCTSTR filename, LPCVOID buf, DWORD writeSize) {
LARGE_INTEGER size;
size.QuadPart = 0;
HANDLE fh = CreateFile(filename, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
GetFileSizeEx(fh, &size);
SetFilePointerEx(fh, size, NULL, FILE_BEGIN);
WriteFile(fh, buf, writeSize, NULL, NULL);
CloseHandle(fh);
}
void readTail(LPCTSTR filename, LPVOID buf, DWORD readSize) {
LARGE_INTEGER size;
size.QuadPart = 0;
HANDLE fh = CreateFile(filename, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
GetFileSizeEx(fh, &size);
size.QuadPart -= readSize;
SetFilePointerEx(fh, size, NULL, FILE_BEGIN);
ReadFile(fh, buf, readSize, NULL, NULL);
CloseHandle(fh);
}
The append function seems to increase the file size by the correct number (5 bytes), but printing the last 5 byte to in the console doesn't show anything.
What went wrong?
You have not allocated buffer for your result:
LPVOID buf1 = "";
readTail(fn, buf1, 5);
buf1 is only a pointer to empty string, what you want is some buffer where to put your results:
BYTE buf1[10] = { 0 };
readTail(fn, buf1, 5);
Also, you are appending initialy to existing file (due to OPEN_EXISTING),so makesure you have an empty C:/kaiyin/kybig.out file.
Just for the record, here is the modified code according to suggestions from #marcinj :
// ConsoleApplication1.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <windows.h>
#include <tlhelp32.h>
#include <tchar.h>
#include <iostream>
#include <string>
// Forward declarations:
void append(LPCTSTR, LPCVOID, DWORD);
void readTail(LPCTSTR, LPVOID, DWORD);
void truncateTail(LPCTSTR, long);
int main()
{
LPCTSTR fn = L"C:/kaiyin/kybig.out";
char buf[] = "helloWorld";
append(fn, buf, 10);
BYTE buf1[10] = {0};
readTail(fn, buf1, 5);
std::cout << (char*) buf1 << std::endl;
truncateTail(fn, 5);
for (int i = 0; i < 10; i++) {
buf1[i] = 0;
}
readTail(fn, buf1, 5);
std::cout << (char*) buf1 << std::endl;
printf("End of program\n");
std::string s = "";
std::getline(std::cin, s);
return 0;
}
void append(LPCTSTR filename, LPCVOID buf, DWORD writeSize) {
LARGE_INTEGER size;
size.QuadPart = 0;
HANDLE fh = CreateFile(filename, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
GetFileSizeEx(fh, &size);
SetFilePointerEx(fh, size, NULL, FILE_BEGIN);
WriteFile(fh, buf, writeSize, NULL, NULL);
CloseHandle(fh);
}
void readTail(LPCTSTR filename, LPVOID buf, DWORD readSize) {
LARGE_INTEGER size;
size.QuadPart = 0;
HANDLE fh = CreateFile(filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
GetFileSizeEx(fh, &size);
size.QuadPart -= readSize;
SetFilePointerEx(fh, size, NULL, FILE_BEGIN);
ReadFile(fh, buf, readSize, NULL, NULL);
CloseHandle(fh);
}
void truncateTail(LPCTSTR filename, long truncateSize) {
LARGE_INTEGER size;
size.QuadPart = 0;
HANDLE fh = CreateFile(filename, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (fh == INVALID_HANDLE_VALUE) {
std::cerr << GetLastError();
return;
}
GetFileSizeEx(fh, &size);
size.QuadPart -= truncateSize;
SetFilePointerEx(fh, size, NULL, FILE_BEGIN);
if (SetEndOfFile(fh) == 0) {
std::cerr << GetLastError();
return;
}
CloseHandle(fh);
}
simple question, I have problem with memmove() and memcpy() when i'm using it. I really don't understand what wrong with my code. by the way i use QT.
HANDLE hFile;
HANDLE hMapFile;
HANDLE hMapView;
hFile = CreateFileW((const wchar_t*) objPath.constData(), GENERIC_READ , 0, NULL, OPEN_EXISTING, 0, NULL);
if (hFile != INVALID_HANDLE_VALUE){
hMapFile = CreateFileMappingW(hFile, NULL, PAGE_READONLY, 0, 0, NULL);
if (hMapFile != INVALID_HANDLE_VALUE){
hMapView = MapViewOfFile(hMapFile, GENERIC_READ, 0, 0,0);
if (hMapView != INVALID_HANDLE_VALUE){
uint DefineWord;
memmove((void *) &DefineWord, hMapView,2); // <- always error right here
qDebug()<<DefineWord;
}
}
}
hMapView is not a pointer. memmove requires two pointers. Fix this by declaring hMapView properly. It should be a LPVOID.
MapViewOfFile returns a pointer, or NULL (0) when there is an error, not INVALID_HANDLE_VALUE (-1).
Edit: There was a lot of other problems with your code:
QString::constData() returns QChar*, not wchar_t*, you have to use QString::utf16() instead.
If CreateFileMappingW fails it returns NULL, not INVALID_HANDLE_VALUE.
MapViewOfFile access parameter is FILE_MAP_READ, not GENERIC_READ.
uint is often bigger than 2 bytes, so you should initialize the variable to 0 before memmove if you only read 2 bytes.
Here is a minimal code that should work (only tested on wineg++/wine):
#include <windows.h>
#include <QtCore/QString>
#include <QtCore/QDebug>
#include <QtCore/QTextStream>
int main(int argc, char const *argv[])
{
if (argc < 2) {
QTextStream(stdout) << "Usage :" << argv[0] << " filename" << endl;
return 1;
}
QString objPath(argv[1]);
// Qt source uses C-Style cast from utf16() to (wchar_t*),
// so it should be safe
HANDLE hFile = CreateFileW((const wchar_t *) objPath.utf16(), GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL);
if (hFile == INVALID_HANDLE_VALUE) {
qDebug() << qt_error_string();
} else {
HANDLE hMapFile = CreateFileMappingW(hFile, NULL, PAGE_READONLY, 0, 0, NULL);
if (!hMapFile) {
qDebug() << qt_error_string();
} else {
void *pMapView = MapViewOfFile(hMapFile, FILE_MAP_READ, 0, 0, 0);
if (!pMapView) {
qDebug() << qt_error_string();
} else {
uint DefineWord = 0;
memmove((void *) &DefineWord, pMapView, 2);
qDebug() << DefineWord;
}
CloseHandle(hMapFile);
}
CloseHandle(hFile);
}
return 0;
}
PS: QString qt_error_string(int errorCode = -1) is an apparently undocumented Qt function that returns the error string of the last error (from the error code returned from GetLastError() or errno).
If you are using Qt, you can map a file to memory with QFile::map().
To do what your initial code was supposed to do, you only had to add 2 lines to the code sample you found (plus the error checking):
QFile file("foo");
if(!file.open(QFile::ReadOnly)) {
qDebug() << file.errorString();
} else {
uchar *memory = file.map(0, file.size());
if (!memory) {
qDebug() << file.errorString();
} else {
uint DefineWord = 0;
memmove(&DefineWord, memory, 2);
file.unmap();
}
}
by the way i use QT.
You aren't really using it in your example.
Qt has QFile::map method which can (and in my opinion should) be used instead of platform-specific MapViewOfFile.
The problem I'm having: CreateFile returns a handle of 0x194. ReadFileEx is saying that this handle is invalid. (Error 6.) Any ideas? The argument passed in is "C:\testfile.txt", which is a valid text file I made in Notepad. Despite being a C++ programmer for the last 12 years, this is my first time writing anything with "windows.h" or threads.
#include "stdafx.h"
#include "stdio.h"
#include "windows.h"
using namespace System;
int main(int argc, char **argv)
{
if (argc != 2)
{
printf("To display a file, you must enter the filename as the only command argument.");
scanf("\n");
return 0;
}
HANDLE file;
int nameLen = (strlen(argv[1]) + 1);
wchar_t *filename = new wchar_t[nameLen];
if (filename == 0)
{
printf("To display a file, you must enter the filename as the only command argument.");
scanf("\n");
return 0;
}
memset(filename, 0, nameLen);
::MultiByteToWideChar(CP_ACP, NULL, argv[1], -1, filename, nameLen);
file = CreateFile(filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (file == NULL)
{
printf("A valid filename is required.");
scanf("\n");
return 0;
}
char *buffer;
buffer = new char[1024];
OVERLAPPED overlapped;
overlapped.Offset = overlapped.OffsetHigh = 0;
ReadFileEx(file, &buffer, 1024, &overlapped, NULL);
WaitForSingleObject(&file, 0);
if (GetLastError() != ERROR_SUCCESS)
{
printf("A valid file is required., Error: %d", GetLastError());
scanf("\n");
return 0;
}
printf("%s", buffer);
scanf("\n");
delete buffer;
return 0;
}
My guess is to change
ReadFileEx(&file, &buffer, 1024, &overlapped, NULL);
to
ReadFileEx(file, &buffer, 1024, &overlapped, NULL);