From d675d5897cf23e98f95e6cb6697b00b12937c76d Mon Sep 17 00:00:00 2001 From: Vladimir Shiryaev Date: Thu, 12 Nov 2026 22:48:15 -0700 Subject: [PATCH] [Support] Use the file id for UniqueID on local NTFS and ReFS volumes getStatus() builds UniqueID from a hash of the NT path returned by GetFinalPathNameByHandleW, because file indices are not stable on every file system (ImDisk, network redirectors; see rust-lang/rust#86447). The call is expensive: on NTFS it is about half of sys::fs::status, and the dependency scanner pays it twice for every file it sees for the first time, once for the status by path and once for the status of the opened file. On a local fixed NTFS or ReFS volume that supports opening by file id, the 128-bit id from FILE_ID_INFO identifies a file for as long as it exists. Hash that id there and keep the path hash on all other volumes. Whether a volume qualifies is decided once per volume serial number and cached. With Follow, status(path) first tries GetFileInformationByName (Windows 11 24H2 and later, looked up at run time), which returns the same data without opening a handle. Reparse points, non-disk devices, volumes not yet known to have stable ids and errors other than "not found" take the existing handle path. --- llvm/lib/Support/Windows/Path.inc | 186 +++++++++++++++++++++++++++++- 1 file changed, 182 insertions(+), 4 deletions(-) diff --git a/llvm/lib/Support/Windows/Path.inc b/llvm/lib/Support/Windows/Path.inc index d67d13a95e8b..b0b40d4bd3e5 100644 --- a/llvm/lib/Support/Windows/Path.inc +++ b/llvm/lib/Support/Windows/Path.inc @@ -20,6 +20,8 @@ #include "llvm/Support/IOSandbox.h" #include "llvm/Support/WindowsError.h" #include +#include +#include #include #include @@ -29,6 +31,7 @@ #include #include #include +#include #undef max @@ -43,6 +46,36 @@ typedef int errno_t; #pragma comment(lib, "ole32.lib") // This provides CoTaskMemFree #endif +#ifndef __MINGW32__ +// The Windows SDK does not declare these types. +// The WDK declares the types in `wdm.h` and `NtQueryVolumeInformationFile` +// in `ntifs.h`. +// MinGW-w64 declares all three in `winternl.h`. +typedef enum _FSINFOCLASS { FileFsDeviceInformation = 4 } FS_INFORMATION_CLASS; + +typedef struct _FILE_FS_DEVICE_INFORMATION { + DEVICE_TYPE DeviceType; + ULONG Characteristics; +} FILE_FS_DEVICE_INFORMATION; + +// Exported by `ntdll.dll`. +extern "C" NTSYSAPI NTSTATUS NTAPI NtQueryVolumeInformationFile( + HANDLE FileHandle, PIO_STATUS_BLOCK IoStatusBlock, PVOID FsInformation, + ULONG Length, FS_INFORMATION_CLASS FsInformationClass); +#endif + +// Defined only in `wdm.h` in WDK and MinGW-w64. +#ifndef FILE_REMOVABLE_MEDIA +#define FILE_REMOVABLE_MEDIA 0x00000001 +#endif +#ifndef FILE_REMOTE_DEVICE +#define FILE_REMOTE_DEVICE 0x00000010 +#endif + +#ifndef FILE_SUPPORTS_OPEN_BY_FILE_ID +#define FILE_SUPPORTS_OPEN_BY_FILE_ID 0x01000000 +#endif + using namespace llvm; using llvm::sys::windows::CurCPToUTF16; @@ -866,6 +899,113 @@ static perms perms_from_attrs(DWORD Attrs) { return (Attrs & FILE_ATTRIBUTE_READONLY) ? (all_read | all_exe) : all_all; } +namespace { +// FILE_ID_INFO and FILE_STAT_BASIC_INFORMATION, which the _WIN32_WINNT of +// WindowsSupport.h hides. +struct FileIdInformation { + ULONGLONG VolumeSerialNumber; + BYTE FileId[16]; +}; +constexpr int FileIdInfoClass = 18; + +struct FileStatBasicInformation { + LARGE_INTEGER FileId; + LARGE_INTEGER CreationTime; + LARGE_INTEGER LastAccessTime; + LARGE_INTEGER LastWriteTime; + LARGE_INTEGER ChangeTime; + LARGE_INTEGER AllocationSize; + LARGE_INTEGER EndOfFile; + ULONG FileAttributes; + ULONG ReparseTag; + ULONG NumberOfLinks; + ULONG DeviceType; + ULONG DeviceCharacteristics; + ULONG Reserved; + LARGE_INTEGER VolumeSerialNumber; + BYTE FileId128[16]; +}; +constexpr int FileStatBasicByNameInfoClass = 3; +using GetFileInformationByNameFn = BOOL(WINAPI *)(PCWSTR, int, PVOID, ULONG); + +// The volumes seen so far by their 64-bit serial number, and whether their +// file ids are stable. +class FileIdVolumes { +public: + std::optional lookup(uint64_t Serial) { + std::lock_guard Guard(Lock); + for (const auto &[Known, Stable] : Volumes) + if (Known == Serial) + return Stable; + return std::nullopt; + } + + bool insert(uint64_t Serial, bool Stable) { + std::lock_guard Guard(Lock); + Volumes.emplace_back(Serial, Stable); + return Stable; + } + +private: + std::mutex Lock; + SmallVector, 4> Volumes; +}; +} // namespace + +static FileIdVolumes &fileIdVolumes() { + static FileIdVolumes Volumes; + return Volumes; +} + +static bool isDiskDevice(ULONG DeviceType, ULONG Characteristics) { + return (DeviceType == FILE_DEVICE_DISK || + DeviceType == FILE_DEVICE_DISK_FILE_SYSTEM) && + !(Characteristics & (FILE_REMOVABLE_MEDIA | FILE_REMOTE_DEVICE)); +} + +// Whether the file ids of the volume of Handle identify its files for as long +// as they exist: a local fixed NTFS or ReFS volume. Elsewhere an id may be +// missing, zero or change while the file is closed (FAT, network +// redirectors), so a UniqueID there hashes the path instead. +static bool volumeHasStableFileIds(HANDLE Handle) { + IO_STATUS_BLOCK IoStatus; + FILE_FS_DEVICE_INFORMATION Device; + if (::NtQueryVolumeInformationFile(Handle, &IoStatus, &Device, sizeof(Device), + FileFsDeviceInformation) < 0 || + !isDiskDevice(Device.DeviceType, Device.Characteristics)) + return false; + DWORD Flags; + wchar_t FileSystem[MAX_PATH + 1]; + if (!::GetVolumeInformationByHandleW(Handle, nullptr, 0, nullptr, nullptr, + &Flags, FileSystem, MAX_PATH + 1)) + return false; + return (Flags & FILE_SUPPORTS_OPEN_BY_FILE_ID) && + (wcscmp(FileSystem, L"NTFS") == 0 || wcscmp(FileSystem, L"ReFS") == 0); +} + +static bool hasStableFileIds(uint64_t VolumeSerial, HANDLE Handle) { + FileIdVolumes &Volumes = fileIdVolumes(); + if (std::optional Stable = Volumes.lookup(VolumeSerial)) + return *Stable; + return Volumes.insert(VolumeSerial, volumeHasStableFileIds(Handle)); +} + +static bool isZeroFileId(const BYTE (&FileId)[16]) { + return llvm::all_of(FileId, [](BYTE B) { return B == 0; }); +} + +static uint64_t fileIdHash(const BYTE (&FileId)[16]) { + return hash_combine_range(std::begin(FileId), std::end(FileId)); +} + +// Windows 11 24H2 and later: the status of a path without opening it. +static GetFileInformationByNameFn getFileInformationByName() { + static GetFileInformationByNameFn Fn = + reinterpret_cast(::GetProcAddress( + ::GetModuleHandleW(L"kernel32.dll"), "GetFileInformationByName")); + return Fn; +} + static std::error_code getStatus(HANDLE FileHandle, file_status &Result) { SmallVector ntPath; if (FileHandle == INVALID_HANDLE_VALUE) @@ -895,8 +1035,9 @@ static std::error_code getStatus(HANDLE FileHandle, file_status &Result) { if (!::GetFileInformationByHandle(FileHandle, &Info)) goto handle_status_error; - // File indices aren't necessarily stable after closing the file handle; - // instead hash a canonicalized path. + // On a volume with stable file ids, hash the 128-bit file id. Elsewhere file + // indices aren't necessarily stable after closing the file handle; instead + // hash a canonicalized path. // // For getting a canonical path to the file, call GetFinalPathNameByHandleW // with VOLUME_NAME_NT. We don't really care exactly what the path looks @@ -906,8 +1047,15 @@ static std::error_code getStatus(HANDLE FileHandle, file_status &Result) { // drivers, such as ImDisk. (See // https://github.com/rust-lang/rust/pull/86447.) uint64_t PathHash; - if (std::error_code EC = - realPathFromHandle(FileHandle, ntPath, VOLUME_NAME_NT)) { + FileIdInformation IdInfo; + if (::GetFileInformationByHandleEx(FileHandle, + FILE_INFO_BY_HANDLE_CLASS(FileIdInfoClass), + &IdInfo, sizeof(IdInfo)) && + !isZeroFileId(IdInfo.FileId) && + hasStableFileIds(IdInfo.VolumeSerialNumber, FileHandle)) { + PathHash = fileIdHash(IdInfo.FileId); + } else if (std::error_code EC = + realPathFromHandle(FileHandle, ntPath, VOLUME_NAME_NT)) { // If realPathFromHandle failed, fall back on the fields // nFileIndex{High,Low} instead. They're not necessarily stable on all file // systems as they're only documented as being unique/stable as long as the @@ -954,6 +1102,36 @@ std::error_code status(const Twine &path, file_status &result, bool Follow) { if (std::error_code ec = widenPath(path8, path_utf16)) return ec; + GetFileInformationByNameFn GetByName = + Follow ? getFileInformationByName() : nullptr; + if (GetByName) { + FileStatBasicInformation S; + if (GetByName(path_utf16.begin(), FileStatBasicByNameInfoClass, &S, + sizeof(S))) { + // Reparse points, other devices and volumes whose file ids are not + // known to be stable take the handle path below: it follows links, + // reports the device type and checks the volume. + std::optional StableIds = + fileIdVolumes().lookup(S.VolumeSerialNumber.QuadPart); + if (!(S.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) && + isDiskDevice(S.DeviceType, S.DeviceCharacteristics) && + StableIds.value_or(false) && !isZeroFileId(S.FileId128)) { + result = file_status( + file_type_from_attrs(S.FileAttributes), + perms_from_attrs(S.FileAttributes), S.NumberOfLinks, + uint32_t(S.LastAccessTime.HighPart), S.LastAccessTime.LowPart, + uint32_t(S.LastWriteTime.HighPart), S.LastWriteTime.LowPart, + S.VolumeSerialNumber.LowPart, uint32_t(S.EndOfFile.HighPart), + S.EndOfFile.LowPart, fileIdHash(S.FileId128)); + return std::error_code(); + } + } else { + DWORD Err = ::GetLastError(); + if (Err == ERROR_FILE_NOT_FOUND || Err == ERROR_PATH_NOT_FOUND) + return getStatus(INVALID_HANDLE_VALUE, result); + } + } + DWORD Flags = FILE_FLAG_BACKUP_SEMANTICS; if (!Follow) { DWORD attr = ::GetFileAttributesW(path_utf16.begin());