How to modify file attributes without race conditions? - file

I want to modify a single attribute on a file (e.g the read-only attribute). In order to do that, it looks like I have to query the current file attributes with either GetFileAttributes or GetFileInformationByHandle, then set the new attributes with either SetFileAttributes or SetFileInformationByHandle: https://learn.microsoft.com/en-us/windows/win32/fileio/retrieving-and-changing-file-attributes
However that is inherently racy, as the file attributes may change between the query and the update. Is there a method to update file attributes atomically? I would expect there to be an API like ModifyFileAttributes(DWORD addAttributes, DWORD rmAttributes) which would do its best to work atomically. Transactional NTFS is not an option for me because a) it's deprecated b) only works on NTFS.
Thanks!

As mentioned in the comment, FILE_SHARE_READ is a trade-off. The following code is adapted from SetFileInformationByHandle function. SetFileInformationByHandle for hFile2 is ERROR_ACCESS_DENIED.
#include <Windows.h>
#include <Tchar.h>
int main()
{
//...
HANDLE hFile1 = CreateFile(TEXT("tempfile"),
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ,
NULL,
CREATE_ALWAYS,
0,
NULL);
HANDLE hFile2 = CreateFile(TEXT("tempfile"),
GENERIC_READ,
FILE_SHARE_READ| FILE_SHARE_WRITE,
NULL,
OPEN_ALWAYS,
0,
NULL);
if (hFile1 != INVALID_HANDLE_VALUE && hFile2 != INVALID_HANDLE_VALUE)
{
HANDLE hFile = hFile1;
//HANDLE hFile = hFile2;
FILE_BASIC_INFO fdi{};
fdi.FileAttributes = FILE_ATTRIBUTE_TEMPORARY | FILE_ATTRIBUTE_NORMAL;
BOOL fResult = SetFileInformationByHandle(hFile,
FileBasicInfo,
&fdi,
sizeof(FILE_BASIC_INFO));
if (fResult)
{
// File will be deleted upon CloseHandle.
_tprintf(TEXT("SetFileInformationByHandle marked tempfile for deletion\n"));
// ...
// Now use the file for whatever temp data storage you need,
// it will automatically be deleted upon CloseHandle or
// application termination.
// ...
}
else
{
_tprintf(TEXT("error %lu: SetFileInformationByHandle could not mark tempfile for deletion\n"),
GetLastError());
}
CloseHandle(hFile);
// At this point, the file is closed and deleted by the system.
}
else
{
_tprintf(TEXT("error %lu: could not create tempfile\n"),
GetLastError());
}
//...
}

Related

Mapping files into virtual memory in C on Windows

On POSIX systems, I am able to use the mmap function to read the contents of a file faster than getline, getc, etc. This is important in the program that I am developing as it is expected to read very large files into memory; iteratively collecting lines using getline is too costly. Portability is also a requirement of my software, so if I use mmap, I need to find a way to memory map files using the WinApi, as I'd rather not compile through cygwin/msys. From a cursory search I identified this MSDN article which describes very briefly a way to map files into memory, however, from trawling through documentation I can't make head nor tails of how to actually implement it, and I'm stuck on finding example snippets of code, like there are for POSIX mmap.
How do I use the WinApi's memory mapping options to read a file into a char*?
How do I use the WinApi's memory mapping options to read a file into a
char*?
Under Windows, when you map a file in memory, you get a pointer to the memory location where the first byte of the file has been mapped. You can cast that pointer to whatever datatype you like, including char*.
In other words, it is Windows which decide where the mapped data will be in memory. You cannot provide a char* and expect Windows will load data there.
This means that if you already have a char* and want the data from the file in the location pointed by that char*, then you have to copy it. Not a good idea in terms of performances.
Here is a simple program dumping a text file by mapping the file into memory and then displaying all ASCII characters. Tested with MSVC2019.
#include <stdio.h>
#include <Windows.h>
int main(int argc, char *argv[])
{
TCHAR *lpFileName = TEXT("hello.txt");
HANDLE hFile;
HANDLE hMap;
LPVOID lpBasePtr;
LARGE_INTEGER liFileSize;
hFile = CreateFile(lpFileName,
GENERIC_READ, // dwDesiredAccess
0, // dwShareMode
NULL, // lpSecurityAttributes
OPEN_EXISTING, // dwCreationDisposition
FILE_ATTRIBUTE_NORMAL, // dwFlagsAndAttributes
0); // hTemplateFile
if (hFile == INVALID_HANDLE_VALUE) {
fprintf(stderr, "CreateFile failed with error %d\n", GetLastError());
return 1;
}
if (!GetFileSizeEx(hFile, &liFileSize)) {
fprintf(stderr, "GetFileSize failed with error %d\n", GetLastError());
CloseHandle(hFile);
return 1;
}
if (liFileSize.QuadPart == 0) {
fprintf(stderr, "File is empty\n");
CloseHandle(hFile);
return 1;
}
hMap = CreateFileMapping(
hFile,
NULL, // Mapping attributes
PAGE_READONLY, // Protection flags
0, // MaximumSizeHigh
0, // MaximumSizeLow
NULL); // Name
if (hMap == 0) {
fprintf(stderr, "CreateFileMapping failed with error %d\n", GetLastError());
CloseHandle(hFile);
return 1;
}
lpBasePtr = MapViewOfFile(
hMap,
FILE_MAP_READ, // dwDesiredAccess
0, // dwFileOffsetHigh
0, // dwFileOffsetLow
0); // dwNumberOfBytesToMap
if (lpBasePtr == NULL) {
fprintf(stderr, "MapViewOfFile failed with error %d\n", GetLastError());
CloseHandle(hMap);
CloseHandle(hFile);
return 1;
}
// Display file content as ASCII charaters
char *ptr = (char *)lpBasePtr;
LONGLONG i = liFileSize.QuadPart;
while (i-- > 0) {
fputc(*ptr++, stdout);
}
UnmapViewOfFile(lpBasePtr);
CloseHandle(hMap);
CloseHandle(hFile);
printf("\nDone\n");
}

How to find the end of a memory mapped file in Windows platform without previously knowing the size of the file?

I have mapped a file of unknown size (around 4-6 GiB) in Windows platform and got a pointer to the start of the file data returned from the MapFileView function. But how can I know that I have reached the end of the file when I access the data using the pointer sequentially?
Here is the code I have so far written and it successfully maps the file and returns the pointer:
#include <Windows.h>
#include <stdio.h>
#include <inttypes.h>
int main()
{
HANDLE hFile = CreateFile("Test.bin",
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (!hFile)
{
printf("Could not create file (%lu).\n", GetLastError());
exit(1) ;
}
HANDLE hMapFile = CreateFileMappingA(hFile,
NULL,
PAGE_READWRITE,
0,
0,
NULL);
if (!hMapFile)
{
printf("Could not create file mapping object (%lu).\n", GetLastError());
CloseHandle(hFile);
exit(1);
}
int32_t* pBuf = (int32_t*) MapViewOfFile(hMapFile,
FILE_MAP_ALL_ACCESS,
0,
0,
0);
if (!pBuf)
{
printf("Could not map file (%lu).\n", GetLastError());
CloseHandle(hFile);
CloseHandle(hMapFile);
exit(1);
};
UnmapViewOfFile(pBuf);
CloseHandle(hFile);
CloseHandle(hMapFile);
exit(0);
}
So I wanted to read equal sized different parts of the file simultaneously in multiple threads. I believe mapped file is the right choice for this purpose. Advice about any other faster and possible approaches is highly appreciated.
I have researched some similar questions in the forum and I suppose this is the closest topic I could find:
Read all contents of memory mapped file or Memory Mapped View Accessor without knowing the size of it
But this answer is using C# and is not written using the WinAPI, therefore, I couldn't understand their process.
Thanks in advance :)
Call GetFileSizeEx to get the size of a file, and use this in combination with the base address and the current read address to determine where the end address is.

SetFileShortName does not reset short (8.3) name of a file

I am trying to remove short name of a file in NTFS. I am testing it on Windows 7. I am running my process as an administrator.
Here is my code:
HANDLE hFile;
DWORD error = NO_ERROR;
hFile = fh = CreateFileA(name,
GENERIC_ALL,
FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE,
NULL,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS,
NULL);
if(hFile == NULL || hFile == INVALID_HANDLE_VALUE)
{
error = GetLastError();
if(GetLastError() == ERROR_ACCESS_DENIED)
printf("File Access Denied.\n");
if(GetLastError() == ERROR_FILE_NOT_FOUND)
printf("File not found.\n");
return error;
}
SetLastError(NO_ERROR);
ModifyPrivilege(SE_RESTORE_NAME, TRUE);
SetLastError(NO_ERROR);
SetFileShortNameW(hFile, L""); // As per MSDN, It will work only in windows 7 and above
error = GetlastError(); // returns 1314 error
ModifyPrivilege(SE_RESTORE_NAME, FALSE);
CloseHandle(hFile);
Code for ModifyPrivilege() is same as on MSDN:
http://msdn.microsoft.com/en-us/library/windows/desktop/aa387705(v=vs.85).aspx
I am making sure that I have SE_RESTORE_NAME privilege (using process explorer for this). Above code does not generate any error but when I look at mft record of the file in hex editor, Short file name is still there i.e. MFT record has two $30 File name attributes, one for full name and other for short name.
I want to know if my code is wrong or some thing else that I have to do? Why does not SetFileShortNameEx function does not any effect in this case?
You need to add the privilege before you call CreateFile.
Your error handling is a bit messed up too. There's no need for any calls to SetLastError. You simply need to check the return values of the API call before you call GetLastError. Only call GetLastError if the docs say that it has meaning. In the case of SetFileShortName, as is the case for many API calls, you only call GetLastError when the API call returns FALSE. So you should write:
if (!SetFileShortNameW(hFile, L""))
{
error = GetLastError();
// ...
}

Calling VirtualProtect on a mapped file

I'm using the CreateFileMapping and MapViewOfFile functions to map a file into memory. After a certain point, I call VirtualProtect to change its protection from read-only to read and write. This call fails and GetLastError gives ERROR_INVALID_PARAMETER.
Here is a simplified version of my code that demonstrates the problem.
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
int main() {
HANDLE fd, md;
char *addr;
DWORD old;
BOOL ok;
fd = CreateFile("filename", GENERIC_READ|GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
md = CreateFileMapping(fd, NULL, PAGE_READWRITE, 0, 100, NULL);
addr = MapViewOfFile(md, FILE_MAP_READ, 0, 0, 100);
ok = VirtualProtect(addr, 100, PAGE_READWRITE, &old);
if (!ok) {
// we fall into this if block
DWORD err = GetLastError();
// this outputs "error protecting: 87"
printf("error protecting: %u\n", err);
return 1;
}
UnmapViewOfFile(addr);
CloseHandle(md);
CloseHandle(fd);
return 0;
}
What am I doing wrong here? Am I not allowed to call VirtualProtect on a region containing a mapped file?
Start out by creating the view with FILE_MAP_READ | FILE_MAP_WRITE and protect with PAGE_READONLY. Now you have no trouble making it PAGE_READWRITE later:
addr = MapViewOfFile(md, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 100);
ok = VirtualProtect(addr, 100, PAGE_READONLY, &old);
//...
ok = VirtualProtect(addr, 100, PAGE_READWRITE, &old);
What happens in your code is that VirtualProtectEx (invoked by VirtualProtect of yours) fails with error STATUS_SECTION_PROTECTION (0xC000004E) - "A view to a section specifies a protection that is incompatible with the protection of the initial view" and this seems to be what you did indeed by creating a section view with more restrictive protection (FILE_MAP_READ).
This topic doesn't seem to be documented with enough details, so I think you'd better simply follow what Hans suggested.
According to http://msdn.microsoft.com/en-us/library/aa366556(v=vs.85).aspx this should be legal. According to VirtualProtect documentation, the new flags must be compatible with the "VirtualAlloc" flags - if this transfer to the "MapViewOfFile" flags, I'd suspect that you can tighten but not loosen the protection. Try mapping readwrite and changing protection to readonly.

Monitoring directory using ReadDirectoryChangesW API

I am trying to monitor a directory e:\test using ReadDirectoryChangesW API.
My Code :
#define UNICODE
#define WIN32_WINNT 0x0500
#include "stdafx.h"
#include <stdio.h>
#include <windows.h>
HANDLE hDir;
int _tmain(int argc, _TCHAR* argv[])
{
FILE_NOTIFY_INFORMATION fniDir;
DWORD i = 0;
hDir = CreateFile(_T("e:\\test"), GENERIC_READ , FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
ReadDirectoryChangesW(hDir, &fniDir, sizeof(fniDir), TRUE, FILE_NOTIFY_CHANGE_FILE_NAME, &i, NULL, NULL);
while(TRUE)
{
if(i>0)
wprintf(L"%s", fniDir.FileName);
}
CloseHandle(hDir);
return 0;
}
I don't know what's wrong with my code as I haven't understood ReadDirectoryChangesW documentation completely, specially the LPOVERLAPPED parameters.
When I run the code I don't get any output, except for a blank console window. Can someone point me in a right direction?
Thanks.
You only need the overlapped struct if you plan on catching the changes notifications asynchronously. In your code you don't need it.
Here's how you do it.
HANDLE hDir = CreateFile(
p.string().c_str(), /* pointer to the file name */
FILE_LIST_DIRECTORY, /* (this is important to be FILE_LIST_DIRECTORY!) access (read-write) mode */
FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE, /* (file share write is needed, or else user is not able to rename file while you hold it) share mode */
NULL, /* security descriptor */
OPEN_EXISTING, /* how to create */
FILE_FLAG_BACKUP_SEMANTICS, /* file attributes */
NULL /* file with attributes to copy */
);
if(hDir == INVALID_HANDLE_VALUE){
throw runtime_error(string("Could not open ").append(p.string()).append(" for watching!"));
}
FILE_NOTIFY_INFORMATION buffer[1024];
DWORD BytesReturned;
while( ReadDirectoryChangesW(
hDir, /* handle to directory */
&buffer, /* read results buffer */
sizeof(buffer), /* length of buffer */
TRUE, /* monitoring option */
FILE_NOTIFY_CHANGE_LAST_WRITE, /* filter conditions */
&BytesReturned, /* bytes returned */
NULL, /* overlapped buffer */
NULL)){
do{
//CANT DO THIS! FileName is NOT \0 terminated
//wprintf("file: %s\n",buffer.FileName);
buffer += buffer.NextEntryOffset;
}while(buffer.NextEntryOffset);
}

Resources