Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 90 additions & 0 deletions nob.h
Original file line number Diff line number Diff line change
Expand Up @@ -858,6 +858,96 @@ NOBDEF void nob__go_rebuild_urself(int argc, char **argv, const char *source_pat
// TODO: this is an experimental behavior behind a compilation flag.
// Once it is confirmed that it does not cause much problems on both POSIX and Windows
// we may turn it on by default.
#ifdef _WIN32
CHAR szFileSystemName[MAX_PATH+1] = { 0 };
CHAR szFullPath[MAX_PATH+1] = { 0 };

GetFullPathNameA(old_binary_path, NOB_ARRAY_LEN(szFullPath), szFullPath, NULL);

CHAR *vol = nob_temp_sprintf("%c:\\", szFullPath[0]);
GetVolumeInformationA(vol, NULL, 0, NULL, NULL, NULL, (LPSTR)&szFileSystemName, MAX_PATH+1);

// Deleting the running executable on Windows is not possible, since the Image of
// the running process is mapped in memory from `nob.exe.old` and a reference
// to the file is kept.
// If we simply rename the file on disk the process will reference the newly named file,
// making it impossible to delete.
// A trick that can be used to delete `nob.exe.old` is to take advantage of NTFS streams.
// In essence we can rename the standard data stream `nob.exe.old:$DATA`
// to `nob.exe.old:del`, making the process reference the newly created data stream for
// its Image.
// At this point `nob.exe.old` is no longer referenced in the memory of the process,
// making it possible to be marked for deletion.
// In NTFS deleting a file will also remove all data streams that belong to it.
// Deleting `nob.exe.old` will succeed since the process keeps a reference to the
// `:del` data stream and the `:del` stream will vanish because it belonged to `nob.exe.old`.
// This will leave no trace on the disk of any executable.
//
// Here's a before and after view of a sample execution (taken from Process Hacker 2):
// before:
// 0x7ff6d5970000, Image, 8.400 kB, WCX, C:\Users\user\Programming\nob.h\nob.exe.old:del, 164 kB, 20 kB, 144 kB, 144 kB,
// after:
// 0x7ff6d5970000, Image, 8.400 kB, WCX, C:\$Extend\$Deleted\001A00000007F98D67225CE2:del, 164 kB, 20 kB, 144 kB, 144 kB,
//
// Note how the Image references an invalid path after `C:\Users\user\Programming\nob.h\nob.exe.old`
// gets deleted.
//
// Also, running `dir /r` right after renaming and before deletion will show:
// 0 nob.exe.old
// 193.024 nob.exe.old:del:$DATA
//
//
// The downside of this approach is that it will work only if the current volume supports NTFS.

if (strcmp(szFileSystemName, "NTFS") == 0) {
HANDLE hFile = INVALID_HANDLE_VALUE;
WCHAR lpszStreamW[] = L":del";
const DWORD cbStream = wcslen(lpszStreamW) * sizeof(*lpszStreamW); // do not count '\0'
const DWORD cbRenameInfo = sizeof(FILE_RENAME_INFO) + cbStream;
PFILE_RENAME_INFO pRenameInfo = NULL;

pRenameInfo = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, cbRenameInfo);
if (!pRenameInfo) {
nob_log(NOB_ERROR, "Could not allocate %zu bytes of space: %s", cbRenameInfo, nob_win32_error_message(GetLastError()));
nob_log(NOB_ERROR, "Could not delete file %s", old_binary_path);
return false;
}


hFile = CreateFileA(
szFullPath,
DELETE | SYNCHRONIZE | GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL,
OPEN_EXISTING,
0,
NULL
);
if (hFile == INVALID_HANDLE_VALUE) {
nob_log(NOB_ERROR, "Could not open file `%s`: %s", old_binary_path, nob_win32_error_message(GetLastError()));
nob_log(NOB_ERROR, "Could not delete file %s", old_binary_path);
HeapFree(GetProcessHeap(), HEAP_NO_SERIALIZE, pRenameInfo);
return false;
}

pRenameInfo->ReplaceIfExists = FALSE;
pRenameInfo->RootDirectory = NULL;
pRenameInfo->FileNameLength = cbStream;
memcpy(pRenameInfo->FileName, lpszStreamW, cbStream);

if(!SetFileInformationByHandle(hFile, FileRenameInfo, pRenameInfo, cbRenameInfo)) {
nob_log(NOB_ERROR, "Could not rename the data stream: %s", nob_win32_error_message(GetLastError()));
nob_log(NOB_ERROR, "Could not delete file %s", old_binary_path);
CloseHandle(hFile);
HeapFree(GetProcessHeap(), HEAP_NO_SERIALIZE, pRenameInfo);
return false;
}
CloseHandle(hFile);
HeapFree(GetProcessHeap(), HEAP_NO_SERIALIZE, pRenameInfo);
} else {
nob_log(NOB_WARNING, "Volume `%s` is not NTFS, deletion of file `%s` could fail.", vol, old_binary_path);
}
#endif // _WIN32
nob_delete_file(old_binary_path);
#endif // NOB_EXPERIMENTAL_DELETE_OLD

Expand Down