FileUtils: Implement RecursiveRemoveDirectory and WalkDirectory with NT syscalls

A bit messy but it basically duplicates the Linux side. We don't have a
way to include NT internal defines in to FEXCore currently so the two
required symbols are just forward declared.
This commit is contained in:
Ryan Houdek committed 2026-09-25 14:40:21 -07:00
1 parent f3b0b41254
commit 7962f40065
2 files changed
+169 -5

No files matched your search

+4
View File
@@ -262,6 +262,10 @@ endfunction()
# Build FEXCore_Base static library
add_library(FEXCore_Base STATIC ${FEXCORE_BASE_SRCS})
target_link_libraries(FEXCore_Base PUBLIC ${LIBS})
if (MINGW)
target_link_libraries(FEXCore_Base PUBLIC ntdll)
endif()
AddDefaultOptionsToTarget(FEXCore_Base)
if (ENABLE_FEXCORE_PROFILER AND FEXCORE_PROFILER_BACKEND STREQUAL "TRACY")
+165 -5
View File
@@ -9,6 +9,18 @@
#include <dirent.h>
#include <fcntl.h>
#include <unistd.h>
#else
#include <windows.h>
#include <winnt.h>
#include <winternl.h>
#include <ntstatus.h>
// FEX today doesn't have Windows include in FEXCore.
extern "C" NTSTATUS WINAPI NtQueryDirectoryFile(HANDLE, HANDLE, PIO_APC_ROUTINE, PVOID, PIO_STATUS_BLOCK, PVOID, ULONG,
FILE_INFORMATION_CLASS, BOOLEAN, PUNICODE_STRING, BOOLEAN);
extern "C" NTSTATUS RtlUnicodeToUTF8N(OUT PCHAR UTF8StringDestination, IN ULONG UTF8StringMaxByteCount,
OUT PULONG UTF8StringActualByteCount, IN PCWCH UnicodeStringSource, IN ULONG UnicodeStringByteCount);
#endif
namespace FEXCore::FileUtils {
@@ -190,17 +202,165 @@ end:
}
#else
FEX_DEFAULT_VISIBILITY bool RecursiveRemoveDirectory(const fextl::string& Directory) {
std::error_code ec;
return std::filesystem::remove_all(Directory, ec) != ~0ULL;
static inline std::optional<UNICODE_STRING> PathToNTPath(std::string_view Path) {
UNICODE_STRING PathW;
if (!RtlCreateUnicodeStringFromAsciiz(&PathW, fextl::string(Path).c_str())) {
return std::nullopt;
}
UNICODE_STRING NTPath;
bool Success = RtlDosPathNameToNtPathName_U(PathW.Buffer, &NTPath, nullptr, nullptr);
RtlFreeUnicodeString(&PathW);
if (!Success) {
return std::nullopt;
}
return NTPath;
}
static inline void FreeNTPath(UNICODE_STRING Path) {
RtlFreeUnicodeString(&Path);
}
FEX_DEFAULT_VISIBILITY void WalkDirectory(std::string_view Directory,
fextl::move_only_function<void(std::string_view name, bool is_dir, const void* user_data)> Callback,
const void* user_data) {
for (const auto& iter : std::filesystem::directory_iterator(Directory)) {
Callback(iter.path().string(), iter.is_directory(), user_data);
auto NTPath = PathToNTPath(Directory);
if (!NTPath) {
return;
}
NTSTATUS Status {};
HANDLE dir_fd {};
OBJECT_ATTRIBUTES attr {};
IO_STATUS_BLOCK io {};
InitializeObjectAttributes(&attr, &*NTPath, OBJ_CASE_INSENSITIVE, nullptr, nullptr);
Status = NtOpenFile(&dir_fd, FILE_LIST_DIRECTORY | SYNCHRONIZE, &attr, &io, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT);
FreeNTPath(*NTPath);
if (!NT_SUCCESS(Status)) {
return;
}
BOOLEAN FirstQuery = TRUE;
size_t dirent_size = 4096 * 4;
uint8_t* dirent_buffer = reinterpret_cast<uint8_t*>(FEXCore::Allocator::malloc(dirent_size));
if (!dirent_buffer) {
// Ran out of memory?
goto end;
}
while (true) {
Status = NtQueryDirectoryFile(dir_fd,
nullptr, // Event
nullptr, // ApcRoutine
nullptr, // ApcContext
&io, dirent_buffer, dirent_size, FileDirectoryInformation,
FALSE, // ReturnSingleEntry
nullptr, // FileName
FirstQuery);
FirstQuery = FALSE;
if (Status == STATUS_NO_MORE_FILES) {
// No more files
break;
}
if (!NT_SUCCESS(Status)) {
if (Status == STATUS_BUFFER_TOO_SMALL || Status == STATUS_INFO_LENGTH_MISMATCH) {
// Buffer too small? Scale and try again.
dirent_size *= 2;
FEXCore::Allocator::free(dirent_buffer);
uint8_t* dirent_buffer = reinterpret_cast<uint8_t*>(FEXCore::Allocator::malloc(dirent_size));
if (!dirent_buffer) {
// Ran out of memory?
goto end;
}
continue;
}
// Any other failure, exit loop.
break;
}
// Iterate the entries returned.
for (size_t dirent_offset = 0;;) {
auto Info = reinterpret_cast<FILE_DIRECTORY_INFORMATION*>(dirent_buffer + dirent_offset);
std::wstring_view EntryName(Info->FileName, Info->FileNameLength / sizeof(wchar_t));
if (EntryName != L"." && EntryName != L"..") {
ULONG utf8_bytes_needed = 0;
ULONG actual_utf8_bytes = 0;
RtlUnicodeToUTF8N(nullptr, 0, &utf8_bytes_needed, Info->FileName, Info->FileNameLength);
char* dynamic_buf = reinterpret_cast<char*>(FEXCore::Allocator::malloc(utf8_bytes_needed + 1));
if (dynamic_buf) {
RtlUnicodeToUTF8N(dynamic_buf, utf8_bytes_needed + 1, &actual_utf8_bytes, Info->FileName, Info->FileNameLength);
std::string_view name_view(dynamic_buf, actual_utf8_bytes);
bool is_dir = (Info->FileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
Callback(name_view, is_dir, user_data);
FEXCore::Allocator::free(dynamic_buf);
}
}
if (Info->NextEntryOffset == 0) {
break;
}
dirent_offset += Info->NextEntryOffset;
}
}
end:
if (dirent_buffer) {
FEXCore::Allocator::free(dirent_buffer);
}
NtClose(dir_fd);
}
FEX_DEFAULT_VISIBILITY bool RecursiveRemoveDirectory(const fextl::string& Directory) {
using CallbackType = void (*)(std::string_view name, bool is_dir, const void* user_data);
struct UserData {
CallbackType RemoveFile {};
std::string_view base_path;
};
CallbackType RemoveFile = [](std::string_view name, bool is_dir, const void* user_data) {
auto Data = reinterpret_cast<const UserData*>(user_data);
auto full_path = std::format("{}/{}", Data->base_path, name);
if (is_dir) {
UserData NewData {
.RemoveFile = Data->RemoveFile,
.base_path = full_path,
};
WalkDirectory(full_path, Data->RemoveFile, &NewData);
}
if (is_dir) {
RemoveDirectoryA(full_path.c_str());
} else {
DeleteFileA(full_path.c_str());
}
};
UserData Data {
.RemoveFile = RemoveFile,
.base_path = Directory,
};
WalkDirectory(Directory, RemoveFile, &Data);
return RemoveDirectoryA(Directory.c_str()) != 0;
}
#endif
} // namespace FEXCore::FileUtils