authorgravatar for alex@alexrp.comAlex Rønne Petersen <alex@alexrp.com> 2025-10-18 20:45:22+02:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2025-10-18 20:45:22+02:00
log1466401c153e991cd7cddb0c39b1cd361476e8e5
treeddcdcdee0e8399089c38f785ca85ccab9483fb28
parentf3da54f53c1db1484b00657de18e3677358549cd
parent88fd8ce8604f1c8ea79797f7263ac7b81910da9b
signaturebadge-check Signed by PGP key B5690EEEBB952194

Merge pull request #25614 from squeek502/windows-rename-delete

windows: Always try using POSIX_SEMANTICS/etc for rename/delete

2 files changed, 56 insertions(+), 37 deletions(-)

lib/std/os/windows.zig+28-17
......@@ -1071,13 +1071,18 @@ pub fn DeleteFile(sub_path_w: []const u16, options: DeleteFileOptions) DeleteFil
10711071 }
10721072 defer CloseHandle(tmp_handle);
10731073
1074 // FileDispositionInformationEx (and therefore FILE_DISPOSITION_POSIX_SEMANTICS and FILE_DISPOSITION_IGNORE_READONLY_ATTRIBUTE)
1075 // are only supported on NTFS filesystems, so the version check on its own is only a partial solution. To support non-NTFS filesystems
1076 // like FAT32, we need to fallback to FileDispositionInformation if the usage of FileDispositionInformationEx gives
1077 // us INVALID_PARAMETER.
1078 // The same reasoning for win10_rs5 as in os.renameatW() applies (FILE_DISPOSITION_IGNORE_READONLY_ATTRIBUTE requires >= win10_rs5).
1079 var need_fallback = true;
1080 if (comptime builtin.target.os.version_range.windows.min.isAtLeast(.win10_rs5)) {
1074 // FileDispositionInformationEx has varying levels of support:
1075 // - FILE_DISPOSITION_INFORMATION_EX requires >= win10_rs1
1076 // (INVALID_INFO_CLASS is returned if not supported)
1077 // - Requires the NTFS filesystem
1078 // (on filesystems like FAT32, INVALID_PARAMETER is returned)
1079 // - FILE_DISPOSITION_POSIX_SEMANTICS requires >= win10_rs1
1080 // - FILE_DISPOSITION_IGNORE_READONLY_ATTRIBUTE requires >= win10_rs5
1081 // (NOT_SUPPORTED is returned if a flag is unsupported)
1082 //
1083 // The strategy here is just to try using FileDispositionInformationEx and fall back to
1084 // FileDispositionInformation if the return value lets us know that some aspect of it is not supported.
1085 const need_fallback = need_fallback: {
10811086 // Deletion with posix semantics if the filesystem supports it.
10821087 var info = FILE_DISPOSITION_INFORMATION_EX{
10831088 .Flags = FILE_DISPOSITION_DELETE |
......@@ -1094,12 +1099,18 @@ pub fn DeleteFile(sub_path_w: []const u16, options: DeleteFileOptions) DeleteFil
10941099 );
10951100 switch (rc) {
10961101 .SUCCESS => return,
1097 // INVALID_PARAMETER here means that the filesystem does not support FileDispositionInformationEx
1098 .INVALID_PARAMETER => {},
1102 // The filesystem does not support FileDispositionInformationEx
1103 .INVALID_PARAMETER,
1104 // The operating system does not support FileDispositionInformationEx
1105 .INVALID_INFO_CLASS,
1106 // The operating system does not support one of the flags
1107 .NOT_SUPPORTED,
1108 => break :need_fallback true,
10991109 // For all other statuses, fall down to the switch below to handle them.
1100 else => need_fallback = false,
1110 else => break :need_fallback false,
11011111 }
1102 }
1112 };
1113
11031114 if (need_fallback) {
11041115 // Deletion with file pending semantics, which requires waiting or moving
11051116 // files to get them removed (from here).
......@@ -3129,12 +3140,12 @@ pub const FILE_DISPOSITION_INFORMATION_EX = extern struct {
31293140 Flags: ULONG,
31303141};
31313142
3132const FILE_DISPOSITION_DO_NOT_DELETE: ULONG = 0x00000000;
3133const FILE_DISPOSITION_DELETE: ULONG = 0x00000001;
3134const FILE_DISPOSITION_POSIX_SEMANTICS: ULONG = 0x00000002;
3135const FILE_DISPOSITION_FORCE_IMAGE_SECTION_CHECK: ULONG = 0x00000004;
3136const FILE_DISPOSITION_ON_CLOSE: ULONG = 0x00000008;
3137const FILE_DISPOSITION_IGNORE_READONLY_ATTRIBUTE: ULONG = 0x00000010;
3143pub const FILE_DISPOSITION_DO_NOT_DELETE: ULONG = 0x00000000;
3144pub const FILE_DISPOSITION_DELETE: ULONG = 0x00000001;
3145pub const FILE_DISPOSITION_POSIX_SEMANTICS: ULONG = 0x00000002;
3146pub const FILE_DISPOSITION_FORCE_IMAGE_SECTION_CHECK: ULONG = 0x00000004;
3147pub const FILE_DISPOSITION_ON_CLOSE: ULONG = 0x00000008;
3148pub const FILE_DISPOSITION_IGNORE_READONLY_ATTRIBUTE: ULONG = 0x00000010;
31383149
31393150// FILE_RENAME_INFORMATION.Flags
31403151pub const FILE_RENAME_REPLACE_IF_EXISTS = 0x00000001;
lib/std/posix.zig+28-20
......@@ -2844,15 +2844,19 @@ pub fn renameatW(
28442844 };
28452845 defer windows.CloseHandle(src_fd);
28462846
2847 var need_fallback = true;
28482847 var rc: windows.NTSTATUS = undefined;
2849 // FILE_RENAME_INFORMATION_EX and FILE_RENAME_POSIX_SEMANTICS require >= win10_rs1,
2850 // but FILE_RENAME_IGNORE_READONLY_ATTRIBUTE requires >= win10_rs5. We check >= rs5 here
2851 // so that we only use POSIX_SEMANTICS when we know IGNORE_READONLY_ATTRIBUTE will also be
2852 // supported in order to avoid either (1) using a redundant call that we can know in advance will return
2853 // STATUS_NOT_SUPPORTED or (2) only setting IGNORE_READONLY_ATTRIBUTE when >= rs5
2854 // and therefore having different behavior when the Windows version is >= rs1 but < rs5.
2855 if (builtin.target.os.isAtLeast(.windows, .win10_rs5) orelse false) {
2848 // FileRenameInformationEx has varying levels of support:
2849 // - FILE_RENAME_INFORMATION_EX requires >= win10_rs1
2850 // (INVALID_INFO_CLASS is returned if not supported)
2851 // - Requires the NTFS filesystem
2852 // (on filesystems like FAT32, INVALID_PARAMETER is returned)
2853 // - FILE_RENAME_POSIX_SEMANTICS requires >= win10_rs1
2854 // - FILE_RENAME_IGNORE_READONLY_ATTRIBUTE requires >= win10_rs5
2855 // (NOT_SUPPORTED is returned if a flag is unsupported)
2856 //
2857 // The strategy here is just to try using FileRenameInformationEx and fall back to
2858 // FileRenameInformation if the return value lets us know that some aspect of it is not supported.
2859 const need_fallback = need_fallback: {
28562860 const struct_buf_len = @sizeOf(windows.FILE_RENAME_INFORMATION_EX) + (max_path_bytes - 1);
28572861 var rename_info_buf: [struct_buf_len]u8 align(@alignOf(windows.FILE_RENAME_INFORMATION_EX)) = undefined;
28582862 const struct_len = @sizeOf(windows.FILE_RENAME_INFORMATION_EX) - 1 + new_path_w.len * 2;
......@@ -2879,12 +2883,17 @@ pub fn renameatW(
28792883 );
28802884 switch (rc) {
28812885 .SUCCESS => return,
2882 // INVALID_PARAMETER here means that the filesystem does not support FileRenameInformationEx
2883 .INVALID_PARAMETER => {},
2886 // The filesystem does not support FileDispositionInformationEx
2887 .INVALID_PARAMETER,
2888 // The operating system does not support FileDispositionInformationEx
2889 .INVALID_INFO_CLASS,
2890 // The operating system does not support one of the flags
2891 .NOT_SUPPORTED,
2892 => break :need_fallback true,
28842893 // For all other statuses, fall down to the switch below to handle them.
2885 else => need_fallback = false,
2894 else => break :need_fallback false,
28862895 }
2887 }
2896 };
28882897
28892898 if (need_fallback) {
28902899 const struct_buf_len = @sizeOf(windows.FILE_RENAME_INFORMATION) + (max_path_bytes - 1);
......@@ -2903,14 +2912,13 @@ pub fn renameatW(
29032912 };
29042913 @memcpy((&rename_info.FileName).ptr, new_path_w);
29052914
2906 rc =
2907 windows.ntdll.NtSetInformationFile(
2908 src_fd,
2909 &io_status_block,
2910 rename_info,
2911 @intCast(struct_len), // already checked for error.NameTooLong
2912 .FileRenameInformation,
2913 );
2915 rc = windows.ntdll.NtSetInformationFile(
2916 src_fd,
2917 &io_status_block,
2918 rename_info,
2919 @intCast(struct_len), // already checked for error.NameTooLong
2920 .FileRenameInformation,
2921 );
29142922 }
29152923
29162924 switch (rc) {