| author | |
| committer | |
| log | 88fd8ce8604f1c8ea79797f7263ac7b81910da9b |
| tree | 9550825076c18c92feae5dce15a8d1640a4237ca |
| parent | 3eb3fbec9c455b4fcea144919ddcd0932cefb23a |
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`)2 files changed, 50 insertions(+), 31 deletions(-)
lib/std/os/windows.zig+22-11| ... | ... | @@ -1071,13 +1071,18 @@ pub fn DeleteFile(sub_path_w: []const u16, options: DeleteFileOptions) DeleteFil |
| 1071 | 1071 | } |
| 1072 | 1072 | defer CloseHandle(tmp_handle); |
| 1073 | 1073 | |
| 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: { | |
| 1081 | 1086 | // Deletion with posix semantics if the filesystem supports it. |
| 1082 | 1087 | var info = FILE_DISPOSITION_INFORMATION_EX{ |
| 1083 | 1088 | .Flags = FILE_DISPOSITION_DELETE | |
| ... | ... | @@ -1094,12 +1099,18 @@ pub fn DeleteFile(sub_path_w: []const u16, options: DeleteFileOptions) DeleteFil |
| 1094 | 1099 | ); |
| 1095 | 1100 | switch (rc) { |
| 1096 | 1101 | .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, | |
| 1099 | 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 | 1114 | if (need_fallback) { |
| 1104 | 1115 | // Deletion with file pending semantics, which requires waiting or moving |
| 1105 | 1116 | // files to get them removed (from here). |
lib/std/posix.zig+28-20| ... | ... | @@ -2844,15 +2844,19 @@ pub fn renameatW( |
| 2844 | 2844 | }; |
| 2845 | 2845 | defer windows.CloseHandle(src_fd); |
| 2846 | 2846 | |
| 2847 | var need_fallback = true; | |
| 2848 | 2847 | 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: { | |
| 2856 | 2860 | const struct_buf_len = @sizeOf(windows.FILE_RENAME_INFORMATION_EX) + (max_path_bytes - 1); |
| 2857 | 2861 | var rename_info_buf: [struct_buf_len]u8 align(@alignOf(windows.FILE_RENAME_INFORMATION_EX)) = undefined; |
| 2858 | 2862 | const struct_len = @sizeOf(windows.FILE_RENAME_INFORMATION_EX) - 1 + new_path_w.len * 2; |
| ... | ... | @@ -2879,12 +2883,17 @@ pub fn renameatW( |
| 2879 | 2883 | ); |
| 2880 | 2884 | switch (rc) { |
| 2881 | 2885 | .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, | |
| 2884 | 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 | }; | |
| 2888 | 2897 | |
| 2889 | 2898 | if (need_fallback) { |
| 2890 | 2899 | const struct_buf_len = @sizeOf(windows.FILE_RENAME_INFORMATION) + (max_path_bytes - 1); |
| ... | ... | @@ -2903,14 +2912,13 @@ pub fn renameatW( |
| 2903 | 2912 | }; |
| 2904 | 2913 | @memcpy((&rename_info.FileName).ptr, new_path_w); |
| 2905 | 2914 | |
| 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 | ); | |
| 2914 | 2922 | } |
| 2915 | 2923 | |
| 2916 | 2924 | switch (rc) { |