From 3eb3fbec9c455b4fcea144919ddcd0932cefb23a Mon Sep 17 00:00:00 2001 From: Ryan Liptak Date: Fri, 17 Oct 2025 00:40:17 -0700 Subject: [PATCH 1/2] windows: make FILE_DISPOSITION_ constants pub --- lib/std/os/windows.zig | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index 4a2487c836..d422327295 100644 --- a/lib/std/os/windows.zig +++ b/lib/std/os/windows.zig @@ -3129,12 +3129,12 @@ pub const FILE_DISPOSITION_INFORMATION_EX = extern struct { Flags: ULONG, }; -const FILE_DISPOSITION_DO_NOT_DELETE: ULONG = 0x00000000; -const FILE_DISPOSITION_DELETE: ULONG = 0x00000001; -const FILE_DISPOSITION_POSIX_SEMANTICS: ULONG = 0x00000002; -const FILE_DISPOSITION_FORCE_IMAGE_SECTION_CHECK: ULONG = 0x00000004; -const FILE_DISPOSITION_ON_CLOSE: ULONG = 0x00000008; -const FILE_DISPOSITION_IGNORE_READONLY_ATTRIBUTE: ULONG = 0x00000010; +pub const FILE_DISPOSITION_DO_NOT_DELETE: ULONG = 0x00000000; +pub const FILE_DISPOSITION_DELETE: ULONG = 0x00000001; +pub const FILE_DISPOSITION_POSIX_SEMANTICS: ULONG = 0x00000002; +pub const FILE_DISPOSITION_FORCE_IMAGE_SECTION_CHECK: ULONG = 0x00000004; +pub const FILE_DISPOSITION_ON_CLOSE: ULONG = 0x00000008; +pub const FILE_DISPOSITION_IGNORE_READONLY_ATTRIBUTE: ULONG = 0x00000010; // FILE_RENAME_INFORMATION.Flags pub const FILE_RENAME_REPLACE_IF_EXISTS = 0x00000001; From 88fd8ce8604f1c8ea79797f7263ac7b81910da9b Mon Sep 17 00:00:00 2001 From: Ryan Liptak Date: Fri, 17 Oct 2025 00:50:16 -0700 Subject: [PATCH 2/2] windows: Always try using POSIX_SEMANTICS/etc for rename/delete The compile-time check against the minimum version here wasn't appropriate, since it still makes sense to try using FILE_RENAME_INFORMATION_EX even if the minimum version is something like `xp`, since that doesn't rule out the possibility of the compiled code running on Windows 10/11. This compile-time check was doubly bad since the default minimum windows version (`.win10`) was below the `.win10_rs5` that was checked for, so when providing a target like `x86_64-windows-gnu` it'd always rule out using this syscall. After this commit, we always try using FILE_RENAME_INFORMATION_EX and then let the operating system tell us when some aspect of it is not supported. This allows us to get the benefits of these new syscalls/flags whenever it's actually possible. The possible error returns were validated experimentally: - INVALID_PARAMETER is returned when the underlying filesystem is FAT32 - INVALID_INFO_CLASS is returned on Windows 7 when trying to use FileRenameInformationEx/FileDispositionInformationEx - NOT_SUPPORTED is returned on Windows 10 >= .win10_rs5 when setting a bogus flag value (I used `0x1000`) --- lib/std/os/windows.zig | 33 +++++++++++++++++++---------- lib/std/posix.zig | 48 ++++++++++++++++++++++++------------------ 2 files changed, 50 insertions(+), 31 deletions(-) diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index d422327295..2e18e8ec16 100644 --- a/lib/std/os/windows.zig +++ b/lib/std/os/windows.zig @@ -1071,13 +1071,18 @@ pub fn DeleteFile(sub_path_w: []const u16, options: DeleteFileOptions) DeleteFil } defer CloseHandle(tmp_handle); - // FileDispositionInformationEx (and therefore FILE_DISPOSITION_POSIX_SEMANTICS and FILE_DISPOSITION_IGNORE_READONLY_ATTRIBUTE) - // are only supported on NTFS filesystems, so the version check on its own is only a partial solution. To support non-NTFS filesystems - // like FAT32, we need to fallback to FileDispositionInformation if the usage of FileDispositionInformationEx gives - // us INVALID_PARAMETER. - // The same reasoning for win10_rs5 as in os.renameatW() applies (FILE_DISPOSITION_IGNORE_READONLY_ATTRIBUTE requires >= win10_rs5). - var need_fallback = true; - if (comptime builtin.target.os.version_range.windows.min.isAtLeast(.win10_rs5)) { + // FileDispositionInformationEx has varying levels of support: + // - FILE_DISPOSITION_INFORMATION_EX requires >= win10_rs1 + // (INVALID_INFO_CLASS is returned if not supported) + // - Requires the NTFS filesystem + // (on filesystems like FAT32, INVALID_PARAMETER is returned) + // - FILE_DISPOSITION_POSIX_SEMANTICS requires >= win10_rs1 + // - FILE_DISPOSITION_IGNORE_READONLY_ATTRIBUTE requires >= win10_rs5 + // (NOT_SUPPORTED is returned if a flag is unsupported) + // + // The strategy here is just to try using FileDispositionInformationEx and fall back to + // FileDispositionInformation if the return value lets us know that some aspect of it is not supported. + const need_fallback = need_fallback: { // Deletion with posix semantics if the filesystem supports it. var info = FILE_DISPOSITION_INFORMATION_EX{ .Flags = FILE_DISPOSITION_DELETE | @@ -1094,12 +1099,18 @@ pub fn DeleteFile(sub_path_w: []const u16, options: DeleteFileOptions) DeleteFil ); switch (rc) { .SUCCESS => return, - // INVALID_PARAMETER here means that the filesystem does not support FileDispositionInformationEx - .INVALID_PARAMETER => {}, + // The filesystem does not support FileDispositionInformationEx + .INVALID_PARAMETER, + // The operating system does not support FileDispositionInformationEx + .INVALID_INFO_CLASS, + // The operating system does not support one of the flags + .NOT_SUPPORTED, + => break :need_fallback true, // For all other statuses, fall down to the switch below to handle them. - else => need_fallback = false, + else => break :need_fallback false, } - } + }; + if (need_fallback) { // Deletion with file pending semantics, which requires waiting or moving // files to get them removed (from here). diff --git a/lib/std/posix.zig b/lib/std/posix.zig index c05015c304..e3baab0a46 100644 --- a/lib/std/posix.zig +++ b/lib/std/posix.zig @@ -2844,15 +2844,19 @@ pub fn renameatW( }; defer windows.CloseHandle(src_fd); - var need_fallback = true; var rc: windows.NTSTATUS = undefined; - // FILE_RENAME_INFORMATION_EX and FILE_RENAME_POSIX_SEMANTICS require >= win10_rs1, - // but FILE_RENAME_IGNORE_READONLY_ATTRIBUTE requires >= win10_rs5. We check >= rs5 here - // so that we only use POSIX_SEMANTICS when we know IGNORE_READONLY_ATTRIBUTE will also be - // supported in order to avoid either (1) using a redundant call that we can know in advance will return - // STATUS_NOT_SUPPORTED or (2) only setting IGNORE_READONLY_ATTRIBUTE when >= rs5 - // and therefore having different behavior when the Windows version is >= rs1 but < rs5. - if (builtin.target.os.isAtLeast(.windows, .win10_rs5) orelse false) { + // FileRenameInformationEx has varying levels of support: + // - FILE_RENAME_INFORMATION_EX requires >= win10_rs1 + // (INVALID_INFO_CLASS is returned if not supported) + // - Requires the NTFS filesystem + // (on filesystems like FAT32, INVALID_PARAMETER is returned) + // - FILE_RENAME_POSIX_SEMANTICS requires >= win10_rs1 + // - FILE_RENAME_IGNORE_READONLY_ATTRIBUTE requires >= win10_rs5 + // (NOT_SUPPORTED is returned if a flag is unsupported) + // + // The strategy here is just to try using FileRenameInformationEx and fall back to + // FileRenameInformation if the return value lets us know that some aspect of it is not supported. + const need_fallback = need_fallback: { const struct_buf_len = @sizeOf(windows.FILE_RENAME_INFORMATION_EX) + (max_path_bytes - 1); var rename_info_buf: [struct_buf_len]u8 align(@alignOf(windows.FILE_RENAME_INFORMATION_EX)) = undefined; const struct_len = @sizeOf(windows.FILE_RENAME_INFORMATION_EX) - 1 + new_path_w.len * 2; @@ -2879,12 +2883,17 @@ pub fn renameatW( ); switch (rc) { .SUCCESS => return, - // INVALID_PARAMETER here means that the filesystem does not support FileRenameInformationEx - .INVALID_PARAMETER => {}, + // The filesystem does not support FileDispositionInformationEx + .INVALID_PARAMETER, + // The operating system does not support FileDispositionInformationEx + .INVALID_INFO_CLASS, + // The operating system does not support one of the flags + .NOT_SUPPORTED, + => break :need_fallback true, // For all other statuses, fall down to the switch below to handle them. - else => need_fallback = false, + else => break :need_fallback false, } - } + }; if (need_fallback) { const struct_buf_len = @sizeOf(windows.FILE_RENAME_INFORMATION) + (max_path_bytes - 1); @@ -2903,14 +2912,13 @@ pub fn renameatW( }; @memcpy((&rename_info.FileName).ptr, new_path_w); - rc = - windows.ntdll.NtSetInformationFile( - src_fd, - &io_status_block, - rename_info, - @intCast(struct_len), // already checked for error.NameTooLong - .FileRenameInformation, - ); + rc = windows.ntdll.NtSetInformationFile( + src_fd, + &io_status_block, + rename_info, + @intCast(struct_len), // already checked for error.NameTooLong + .FileRenameInformation, + ); } switch (rc) {