Not that it\'s particularly useful, but I\'m curious as to why the following works, is it simply because the page still happens to be in memory even after the file is delete
Windows is different from Linux and other Unix-alikes in this regard. It maintains a separate reference and handle count for each FILE_OBJECT
. When the last handle is closed, the filesystem unlinks the file and switches it to a "deleted" state. Meanwhile the FILE_OBJECT
might live longer in a "deleted" state until reference count drops to zero.
Quote from here:
The file gets deleted when you close the file handle. After this, if any pages from the view are trimmed and repurposed and then accessed again, the memory manager will try to read them from the file (which is now in the "deleted" state). What happens next depends on the filesystem. NTFS returns an error code (
STATUS_END_OF_FILE
) which causes the memory manager to satisfy the page fault with zeroed pages. As far as I can tell, this behavior is not documented so a future version of NTFS (or a different filesystem) could return a different error, which would result in an inpage exception instead.
That means that once the file is unlinked, swapped-out data will be lost and replaced with zeroes.
You can observe this behavior with the program below. It does the following:
FILE_FLAG_DELETE_ON_CLOSE
.0xCD
pattern. Since the file is as large as RAM, this should force pages out of a memory and into a file.The output says the data at the beginning of the mapping is all zeroes - data has been lost. It is not clear if this can be considered a bug, but as of today this behavior is at least 12 years old and is not likely to be changed.
#include <stdio.h>
#include <stdint.h>
#include <Windows.h>
int main()
{
LARGE_INTEGER size;
MEMORYSTATUSEX mem = { 0 };
mem.dwLength = sizeof mem;
GlobalMemoryStatusEx(&mem);
size.QuadPart = mem.ullTotalPhys;
const HANDLE hFile = CreateFileW(
L"file-under-test",
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE,
NULL);
if (hFile == INVALID_HANDLE_VALUE) {
fprintf(stderr, "CreateFileW() failed\n");
return 1;
}
if (!SetFilePointerEx(hFile, size, NULL, SEEK_SET)) {
fprintf(stderr, "SetFilePointerEx() failed\n");
return 1;
}
if (!SetEndOfFile(hFile)) {
fprintf(stderr, "SetEndOfFile() failed\n");
return 1;
}
const HANDLE hMap = CreateFileMappingW(hFile, NULL, PAGE_READWRITE, 0, 0, NULL);
if (hFile == INVALID_HANDLE_VALUE) {
fprintf(stderr, "CreateFileMappingW() failed\n");
return 1;
}
void *const view = MapViewOfFile(
hMap,
FILE_MAP_READ | FILE_MAP_WRITE,
0, 0,
(size_t) size.QuadPart);
if (view == NULL) {
fprintf(stderr, "MapViewOfFile() failed\n");
return 1;
}
CloseHandle(hMap);
CloseHandle(hFile); // this removes the file
memset(view, 0xCD, (size_t) size.QuadPart);
// Print first 16 bytes
for (int i = 0; i < 16; ++i) {
printf("%2d: %#x\n", i, ((const volatile unsigned char *) view)[i]);
}
return 0;
}
FILE_FLAG_DELETE_ON_CLOSE
follows the unfortunate Windows tradition of referring to an unlink operation as "delete". In fact, the flag only causes the file to be unlinked from the specified directory when the file is closed.
Like other operating systems, Windows only gives ordinary user code the ability to unlink a file from a particular directory. Deleting is always the operating system's decision, taking place when the file can no longer be referenced in any way.
If you look, you'll see the file has in fact been unlinked from the directory, but it will not actually be deleted (and the space the data takes on disk available for re-use) until its reference count drops to zero. The mapping holds a reference.