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...@@ -1071,13 +1071,18 @@ pub fn DeleteFile(sub_path_w: []const u16, options: DeleteFileOptions) DeleteFil
1071 }1071 }
1072 defer CloseHandle(tmp_handle);1072 defer CloseHandle(tmp_handle);
10731073
1074 // FileDispositionInformationEx (and therefore FILE_DISPOSITION_POSIX_SEMANTICS and FILE_DISPOSITION_IGNORE_READONLY_ATTRIBUTE)1074 // FileDispositionInformationEx has varying levels of support:
1075 // are only supported on NTFS filesystems, so the version check on its own is only a partial solution. To support non-NTFS filesystems1075 // - FILE_DISPOSITION_INFORMATION_EX requires >= win10_rs1
1076 // like FAT32, we need to fallback to FileDispositionInformation if the usage of FileDispositionInformationEx gives1076 // (INVALID_INFO_CLASS is returned if not supported)
1077 // us INVALID_PARAMETER.1077 // - Requires the NTFS filesystem
1078 // The same reasoning for win10_rs5 as in os.renameatW() applies (FILE_DISPOSITION_IGNORE_READONLY_ATTRIBUTE requires >= win10_rs5).1078 // (on filesystems like FAT32, INVALID_PARAMETER is returned)
1079 var need_fallback = true;1079 // - FILE_DISPOSITION_POSIX_SEMANTICS requires >= win10_rs1
1080 if (comptime builtin.target.os.version_range.windows.min.isAtLeast(.win10_rs5)) {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: {
1081 // Deletion with posix semantics if the filesystem supports it.1086 // Deletion with posix semantics if the filesystem supports it.
1082 var info = FILE_DISPOSITION_INFORMATION_EX{1087 var info = FILE_DISPOSITION_INFORMATION_EX{
1083 .Flags = FILE_DISPOSITION_DELETE |1088 .Flags = FILE_DISPOSITION_DELETE |
...@@ -1094,12 +1099,18 @@ pub fn DeleteFile(sub_path_w: []const u16, options: DeleteFileOptions) DeleteFil...@@ -1094,12 +1099,18 @@ pub fn DeleteFile(sub_path_w: []const u16, options: DeleteFileOptions) DeleteFil
1094 );1099 );
1095 switch (rc) {1100 switch (rc) {
1096 .SUCCESS => return,1101 .SUCCESS => return,
1097 // INVALID_PARAMETER here means that the filesystem does not support FileDispositionInformationEx1102 // The filesystem does not support FileDispositionInformationEx
1098 .INVALID_PARAMETER => {},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,
1099 // For all other statuses, fall down to the switch below to handle them.1109 // For all other statuses, fall down to the switch below to handle them.
1100 else => need_fallback = false,1110 else => break :need_fallback false,
1101 }1111 }
1102 }1112 };
1113
1103 if (need_fallback) {1114 if (need_fallback) {
1104 // Deletion with file pending semantics, which requires waiting or moving1115 // Deletion with file pending semantics, which requires waiting or moving
1105 // files to get them removed (from here).1116 // files to get them removed (from here).
...@@ -3129,12 +3140,12 @@ pub const FILE_DISPOSITION_INFORMATION_EX = extern struct {...@@ -3129,12 +3140,12 @@ pub const FILE_DISPOSITION_INFORMATION_EX = extern struct {
3129 Flags: ULONG,3140 Flags: ULONG,
3130};3141};
31313142
3132const FILE_DISPOSITION_DO_NOT_DELETE: ULONG = 0x00000000;3143pub const FILE_DISPOSITION_DO_NOT_DELETE: ULONG = 0x00000000;
3133const FILE_DISPOSITION_DELETE: ULONG = 0x00000001;3144pub const FILE_DISPOSITION_DELETE: ULONG = 0x00000001;
3134const FILE_DISPOSITION_POSIX_SEMANTICS: ULONG = 0x00000002;3145pub const FILE_DISPOSITION_POSIX_SEMANTICS: ULONG = 0x00000002;
3135const FILE_DISPOSITION_FORCE_IMAGE_SECTION_CHECK: ULONG = 0x00000004;3146pub const FILE_DISPOSITION_FORCE_IMAGE_SECTION_CHECK: ULONG = 0x00000004;
3136const FILE_DISPOSITION_ON_CLOSE: ULONG = 0x00000008;3147pub const FILE_DISPOSITION_ON_CLOSE: ULONG = 0x00000008;
3137const FILE_DISPOSITION_IGNORE_READONLY_ATTRIBUTE: ULONG = 0x00000010;3148pub const FILE_DISPOSITION_IGNORE_READONLY_ATTRIBUTE: ULONG = 0x00000010;
31383149
3139// FILE_RENAME_INFORMATION.Flags3150// FILE_RENAME_INFORMATION.Flags
3140pub const FILE_RENAME_REPLACE_IF_EXISTS = 0x00000001;3151pub const FILE_RENAME_REPLACE_IF_EXISTS = 0x00000001;
lib/std/posix.zig+28-20
...@@ -2844,15 +2844,19 @@ pub fn renameatW(...@@ -2844,15 +2844,19 @@ pub fn renameatW(
2844 };2844 };
2845 defer windows.CloseHandle(src_fd);2845 defer windows.CloseHandle(src_fd);
28462846
2847 var need_fallback = true;
2848 var rc: windows.NTSTATUS = undefined;2847 var rc: windows.NTSTATUS = undefined;
2849 // FILE_RENAME_INFORMATION_EX and FILE_RENAME_POSIX_SEMANTICS require >= win10_rs1,2848 // FileRenameInformationEx has varying levels of support:
2850 // but FILE_RENAME_IGNORE_READONLY_ATTRIBUTE requires >= win10_rs5. We check >= rs5 here2849 // - FILE_RENAME_INFORMATION_EX requires >= win10_rs1
2851 // so that we only use POSIX_SEMANTICS when we know IGNORE_READONLY_ATTRIBUTE will also be2850 // (INVALID_INFO_CLASS is returned if not supported)
2852 // supported in order to avoid either (1) using a redundant call that we can know in advance will return2851 // - Requires the NTFS filesystem
2853 // STATUS_NOT_SUPPORTED or (2) only setting IGNORE_READONLY_ATTRIBUTE when >= rs52852 // (on filesystems like FAT32, INVALID_PARAMETER is returned)
2854 // and therefore having different behavior when the Windows version is >= rs1 but < rs5.2853 // - FILE_RENAME_POSIX_SEMANTICS requires >= win10_rs1
2855 if (builtin.target.os.isAtLeast(.windows, .win10_rs5) orelse false) {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: {
2856 const struct_buf_len = @sizeOf(windows.FILE_RENAME_INFORMATION_EX) + (max_path_bytes - 1);2860 const struct_buf_len = @sizeOf(windows.FILE_RENAME_INFORMATION_EX) + (max_path_bytes - 1);
2857 var rename_info_buf: [struct_buf_len]u8 align(@alignOf(windows.FILE_RENAME_INFORMATION_EX)) = undefined;2861 var rename_info_buf: [struct_buf_len]u8 align(@alignOf(windows.FILE_RENAME_INFORMATION_EX)) = undefined;
2858 const struct_len = @sizeOf(windows.FILE_RENAME_INFORMATION_EX) - 1 + new_path_w.len * 2;2862 const struct_len = @sizeOf(windows.FILE_RENAME_INFORMATION_EX) - 1 + new_path_w.len * 2;
...@@ -2879,12 +2883,17 @@ pub fn renameatW(...@@ -2879,12 +2883,17 @@ pub fn renameatW(
2879 );2883 );
2880 switch (rc) {2884 switch (rc) {
2881 .SUCCESS => return,2885 .SUCCESS => return,
2882 // INVALID_PARAMETER here means that the filesystem does not support FileRenameInformationEx2886 // The filesystem does not support FileDispositionInformationEx
2883 .INVALID_PARAMETER => {},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,
2884 // For all other statuses, fall down to the switch below to handle them.2893 // For all other statuses, fall down to the switch below to handle them.
2885 else => need_fallback = false,2894 else => break :need_fallback false,
2886 }2895 }
2887 }2896 };
28882897
2889 if (need_fallback) {2898 if (need_fallback) {
2890 const struct_buf_len = @sizeOf(windows.FILE_RENAME_INFORMATION) + (max_path_bytes - 1);2899 const struct_buf_len = @sizeOf(windows.FILE_RENAME_INFORMATION) + (max_path_bytes - 1);
...@@ -2903,14 +2912,13 @@ pub fn renameatW(...@@ -2903,14 +2912,13 @@ pub fn renameatW(
2903 };2912 };
2904 @memcpy((&rename_info.FileName).ptr, new_path_w);2913 @memcpy((&rename_info.FileName).ptr, new_path_w);
29052914
2906 rc =2915 rc = windows.ntdll.NtSetInformationFile(
2907 windows.ntdll.NtSetInformationFile(2916 src_fd,
2908 src_fd,2917 &io_status_block,
2909 &io_status_block,2918 rename_info,
2910 rename_info,2919 @intCast(struct_len), // already checked for error.NameTooLong
2911 @intCast(struct_len), // already checked for error.NameTooLong2920 .FileRenameInformation,
2912 .FileRenameInformation,2921 );
2913 );
2914 }2922 }
29152923
2916 switch (rc) {2924 switch (rc) {