| author | |
| committer | |
| log | bf25816067844efbdb7fe26e8ca7a96341fb919a |
| tree | 19e2bd872fde1c705f413352d4f78a1e229388a7 |
| parent | 17ecc77fc46727f3979abe904ec00b79f036270e |
This also unifies the rename implementations, since previously `posix.renameW` used `MoveFileEx` while `posix.renameatW` used `NtOpenFile`/`NtSetInformationFile`. This, in turn, allows the `MoveFileEx` bindings to be deleted as `posix.renameW` was the only usage.3 files changed, 129 insertions(+), 124 deletions(-)
lib/std/os/windows.zig+126-12| ... | ... | @@ -1084,21 +1084,135 @@ pub fn DeleteFile(sub_path_w: []const u16, options: DeleteFileOptions) DeleteFil |
| 1084 | 1084 | } |
| 1085 | 1085 | } |
| 1086 | 1086 | |
| 1087 | pub const MoveFileError = error{ FileNotFound, AccessDenied, Unexpected }; | |
| 1087 | pub const RenameError = error{ | |
| 1088 | IsDir, | |
| 1089 | NotDir, | |
| 1090 | FileNotFound, | |
| 1091 | NoDevice, | |
| 1092 | AccessDenied, | |
| 1093 | PipeBusy, | |
| 1094 | PathAlreadyExists, | |
| 1095 | Unexpected, | |
| 1096 | NameTooLong, | |
| 1097 | NetworkNotFound, | |
| 1098 | AntivirusInterference, | |
| 1099 | BadPathName, | |
| 1100 | RenameAcrossMountPoints, | |
| 1101 | } || UnexpectedError; | |
| 1088 | 1102 | |
| 1089 | pub fn MoveFileEx(old_path: []const u8, new_path: []const u8, flags: DWORD) (MoveFileError || Wtf8ToPrefixedFileWError)!void { | |
| 1090 | const old_path_w = try sliceToPrefixedFileW(null, old_path); | |
| 1091 | const new_path_w = try sliceToPrefixedFileW(null, new_path); | |
| 1092 | return MoveFileExW(old_path_w.span().ptr, new_path_w.span().ptr, flags); | |
| 1093 | } | |
| 1103 | pub fn RenameFile( | |
| 1104 | /// May only be `null` if `old_path_w` is a fully-qualified absolute path. | |
| 1105 | old_dir_fd: ?HANDLE, | |
| 1106 | old_path_w: []const u16, | |
| 1107 | /// May only be `null` if `new_path_w` is a fully-qualified absolute path, | |
| 1108 | /// or if the file is not being moved to a different directory. | |
| 1109 | new_dir_fd: ?HANDLE, | |
| 1110 | new_path_w: []const u16, | |
| 1111 | replace_if_exists: bool, | |
| 1112 | ) RenameError!void { | |
| 1113 | const src_fd = OpenFile(old_path_w, .{ | |
| 1114 | .dir = old_dir_fd, | |
| 1115 | .access_mask = SYNCHRONIZE | GENERIC_WRITE | DELETE, | |
| 1116 | .creation = FILE_OPEN, | |
| 1117 | .filter = .any, // This function is supposed to rename both files and directories. | |
| 1118 | .follow_symlinks = false, | |
| 1119 | }) catch |err| switch (err) { | |
| 1120 | error.WouldBlock => unreachable, // Not possible without `.share_access_nonblocking = true`. | |
| 1121 | else => |e| return e, | |
| 1122 | }; | |
| 1123 | defer CloseHandle(src_fd); | |
| 1094 | 1124 | |
| 1095 | pub fn MoveFileExW(old_path: [*:0]const u16, new_path: [*:0]const u16, flags: DWORD) MoveFileError!void { | |
| 1096 | if (kernel32.MoveFileExW(old_path, new_path, flags) == 0) { | |
| 1097 | switch (GetLastError()) { | |
| 1098 | .FILE_NOT_FOUND => return error.FileNotFound, | |
| 1099 | .ACCESS_DENIED => return error.AccessDenied, | |
| 1100 | else => |err| return unexpectedError(err), | |
| 1125 | var rc: NTSTATUS = undefined; | |
| 1126 | // FileRenameInformationEx has varying levels of support: | |
| 1127 | // - FILE_RENAME_INFORMATION_EX requires >= win10_rs1 | |
| 1128 | // (INVALID_INFO_CLASS is returned if not supported) | |
| 1129 | // - Requires the NTFS filesystem | |
| 1130 | // (on filesystems like FAT32, INVALID_PARAMETER is returned) | |
| 1131 | // - FILE_RENAME_POSIX_SEMANTICS requires >= win10_rs1 | |
| 1132 | // - FILE_RENAME_IGNORE_READONLY_ATTRIBUTE requires >= win10_rs5 | |
| 1133 | // (NOT_SUPPORTED is returned if a flag is unsupported) | |
| 1134 | // | |
| 1135 | // The strategy here is just to try using FileRenameInformationEx and fall back to | |
| 1136 | // FileRenameInformation if the return value lets us know that some aspect of it is not supported. | |
| 1137 | const need_fallback = need_fallback: { | |
| 1138 | const struct_buf_len = @sizeOf(FILE_RENAME_INFORMATION_EX) + (PATH_MAX_WIDE * 2); | |
| 1139 | var rename_info_buf: [struct_buf_len]u8 align(@alignOf(FILE_RENAME_INFORMATION_EX)) = undefined; | |
| 1140 | const struct_len = @sizeOf(FILE_RENAME_INFORMATION_EX) + new_path_w.len * 2; | |
| 1141 | if (struct_len > struct_buf_len) return error.NameTooLong; | |
| 1142 | ||
| 1143 | const rename_info: *FILE_RENAME_INFORMATION_EX = @ptrCast(&rename_info_buf); | |
| 1144 | var io_status_block: IO_STATUS_BLOCK = undefined; | |
| 1145 | ||
| 1146 | var flags: ULONG = FILE_RENAME_POSIX_SEMANTICS | FILE_RENAME_IGNORE_READONLY_ATTRIBUTE; | |
| 1147 | if (replace_if_exists) flags |= FILE_RENAME_REPLACE_IF_EXISTS; | |
| 1148 | rename_info.* = .{ | |
| 1149 | .Flags = flags, | |
| 1150 | .RootDirectory = if (std.fs.path.isAbsoluteWindowsWtf16(new_path_w)) null else new_dir_fd, | |
| 1151 | .FileNameLength = @intCast(new_path_w.len * 2), // already checked error.NameTooLong | |
| 1152 | .FileName = undefined, | |
| 1153 | }; | |
| 1154 | @memcpy((&rename_info.FileName).ptr, new_path_w); | |
| 1155 | rc = ntdll.NtSetInformationFile( | |
| 1156 | src_fd, | |
| 1157 | &io_status_block, | |
| 1158 | rename_info, | |
| 1159 | @intCast(struct_len), // already checked for error.NameTooLong | |
| 1160 | .FileRenameInformationEx, | |
| 1161 | ); | |
| 1162 | switch (rc) { | |
| 1163 | .SUCCESS => return, | |
| 1164 | // The filesystem does not support FileDispositionInformationEx | |
| 1165 | .INVALID_PARAMETER, | |
| 1166 | // The operating system does not support FileDispositionInformationEx | |
| 1167 | .INVALID_INFO_CLASS, | |
| 1168 | // The operating system does not support one of the flags | |
| 1169 | .NOT_SUPPORTED, | |
| 1170 | => break :need_fallback true, | |
| 1171 | // For all other statuses, fall down to the switch below to handle them. | |
| 1172 | else => break :need_fallback false, | |
| 1101 | 1173 | } |
| 1174 | }; | |
| 1175 | ||
| 1176 | if (need_fallback) { | |
| 1177 | const struct_buf_len = @sizeOf(FILE_RENAME_INFORMATION) + (PATH_MAX_WIDE * 2); | |
| 1178 | var rename_info_buf: [struct_buf_len]u8 align(@alignOf(FILE_RENAME_INFORMATION)) = undefined; | |
| 1179 | const struct_len = @sizeOf(FILE_RENAME_INFORMATION) + new_path_w.len * 2; | |
| 1180 | if (struct_len > struct_buf_len) return error.NameTooLong; | |
| 1181 | ||
| 1182 | const rename_info: *FILE_RENAME_INFORMATION = @ptrCast(&rename_info_buf); | |
| 1183 | var io_status_block: IO_STATUS_BLOCK = undefined; | |
| 1184 | ||
| 1185 | rename_info.* = .{ | |
| 1186 | .Flags = @intFromBool(replace_if_exists), | |
| 1187 | .RootDirectory = if (std.fs.path.isAbsoluteWindowsWtf16(new_path_w)) null else new_dir_fd, | |
| 1188 | .FileNameLength = @intCast(new_path_w.len * 2), // already checked error.NameTooLong | |
| 1189 | .FileName = undefined, | |
| 1190 | }; | |
| 1191 | @memcpy((&rename_info.FileName).ptr, new_path_w); | |
| 1192 | ||
| 1193 | rc = ntdll.NtSetInformationFile( | |
| 1194 | src_fd, | |
| 1195 | &io_status_block, | |
| 1196 | rename_info, | |
| 1197 | @intCast(struct_len), // already checked for error.NameTooLong | |
| 1198 | .FileRenameInformation, | |
| 1199 | ); | |
| 1200 | } | |
| 1201 | ||
| 1202 | switch (rc) { | |
| 1203 | .SUCCESS => {}, | |
| 1204 | .INVALID_HANDLE => unreachable, | |
| 1205 | .INVALID_PARAMETER => unreachable, | |
| 1206 | .OBJECT_PATH_SYNTAX_BAD => unreachable, | |
| 1207 | .ACCESS_DENIED => return error.AccessDenied, | |
| 1208 | .OBJECT_NAME_NOT_FOUND => return error.FileNotFound, | |
| 1209 | .OBJECT_PATH_NOT_FOUND => return error.FileNotFound, | |
| 1210 | .NOT_SAME_DEVICE => return error.RenameAcrossMountPoints, | |
| 1211 | .OBJECT_NAME_COLLISION => return error.PathAlreadyExists, | |
| 1212 | .DIRECTORY_NOT_EMPTY => return error.PathAlreadyExists, | |
| 1213 | .FILE_IS_A_DIRECTORY => return error.IsDir, | |
| 1214 | .NOT_A_DIRECTORY => return error.NotDir, | |
| 1215 | else => return unexpectedStatus(rc), | |
| 1102 | 1216 | } |
| 1103 | 1217 | } |
| 1104 | 1218 |
lib/std/os/windows/kernel32.zig-6| ... | ... | @@ -91,12 +91,6 @@ pub extern "kernel32" fn GetStdHandle( |
| 91 | 91 | nStdHandle: DWORD, |
| 92 | 92 | ) callconv(.winapi) ?HANDLE; |
| 93 | 93 | |
| 94 | pub extern "kernel32" fn MoveFileExW( | |
| 95 | lpExistingFileName: LPCWSTR, | |
| 96 | lpNewFileName: LPCWSTR, | |
| 97 | dwFlags: DWORD, | |
| 98 | ) callconv(.winapi) BOOL; | |
| 99 | ||
| 100 | 94 | // TODO: Wrapper around NtSetInformationFile + `FILE_POSITION_INFORMATION`. |
| 101 | 95 | // `FILE_STANDARD_INFORMATION` is also used if dwMoveMethod is `FILE_END` |
| 102 | 96 | pub extern "kernel32" fn SetFilePointerEx( |
lib/std/posix.zig+3-106| ... | ... | @@ -2470,8 +2470,8 @@ pub fn renameZ(old_path: [*:0]const u8, new_path: [*:0]const u8) RenameError!voi |
| 2470 | 2470 | /// Same as `rename` except the parameters are null-terminated and WTF16LE encoded. |
| 2471 | 2471 | /// Assumes target is Windows. |
| 2472 | 2472 | pub fn renameW(old_path: [*:0]const u16, new_path: [*:0]const u16) RenameError!void { |
| 2473 | const flags = windows.MOVEFILE_REPLACE_EXISTING | windows.MOVEFILE_WRITE_THROUGH; | |
| 2474 | return windows.MoveFileExW(old_path, new_path, flags); | |
| 2473 | const cwd_handle = std.fs.cwd().fd; | |
| 2474 | return windows.RenameFile(cwd_handle, mem.span(old_path), cwd_handle, mem.span(new_path), true); | |
| 2475 | 2475 | } |
| 2476 | 2476 | |
| 2477 | 2477 | /// Change the name or location of a file based on an open directory handle. |
| ... | ... | @@ -2588,110 +2588,7 @@ pub fn renameatW( |
| 2588 | 2588 | new_path_w: []const u16, |
| 2589 | 2589 | ReplaceIfExists: windows.BOOLEAN, |
| 2590 | 2590 | ) RenameError!void { |
| 2591 | const src_fd = windows.OpenFile(old_path_w, .{ | |
| 2592 | .dir = old_dir_fd, | |
| 2593 | .access_mask = windows.SYNCHRONIZE | windows.GENERIC_WRITE | windows.DELETE, | |
| 2594 | .creation = windows.FILE_OPEN, | |
| 2595 | .filter = .any, // This function is supposed to rename both files and directories. | |
| 2596 | .follow_symlinks = false, | |
| 2597 | }) catch |err| switch (err) { | |
| 2598 | error.WouldBlock => unreachable, // Not possible without `.share_access_nonblocking = true`. | |
| 2599 | else => |e| return e, | |
| 2600 | }; | |
| 2601 | defer windows.CloseHandle(src_fd); | |
| 2602 | ||
| 2603 | var rc: windows.NTSTATUS = undefined; | |
| 2604 | // FileRenameInformationEx has varying levels of support: | |
| 2605 | // - FILE_RENAME_INFORMATION_EX requires >= win10_rs1 | |
| 2606 | // (INVALID_INFO_CLASS is returned if not supported) | |
| 2607 | // - Requires the NTFS filesystem | |
| 2608 | // (on filesystems like FAT32, INVALID_PARAMETER is returned) | |
| 2609 | // - FILE_RENAME_POSIX_SEMANTICS requires >= win10_rs1 | |
| 2610 | // - FILE_RENAME_IGNORE_READONLY_ATTRIBUTE requires >= win10_rs5 | |
| 2611 | // (NOT_SUPPORTED is returned if a flag is unsupported) | |
| 2612 | // | |
| 2613 | // The strategy here is just to try using FileRenameInformationEx and fall back to | |
| 2614 | // FileRenameInformation if the return value lets us know that some aspect of it is not supported. | |
| 2615 | const need_fallback = need_fallback: { | |
| 2616 | const struct_buf_len = @sizeOf(windows.FILE_RENAME_INFORMATION_EX) + (max_path_bytes - 1); | |
| 2617 | var rename_info_buf: [struct_buf_len]u8 align(@alignOf(windows.FILE_RENAME_INFORMATION_EX)) = undefined; | |
| 2618 | const struct_len = @sizeOf(windows.FILE_RENAME_INFORMATION_EX) - 1 + new_path_w.len * 2; | |
| 2619 | if (struct_len > struct_buf_len) return error.NameTooLong; | |
| 2620 | ||
| 2621 | const rename_info: *windows.FILE_RENAME_INFORMATION_EX = @ptrCast(&rename_info_buf); | |
| 2622 | var io_status_block: windows.IO_STATUS_BLOCK = undefined; | |
| 2623 | ||
| 2624 | var flags: windows.ULONG = windows.FILE_RENAME_POSIX_SEMANTICS | windows.FILE_RENAME_IGNORE_READONLY_ATTRIBUTE; | |
| 2625 | if (ReplaceIfExists == windows.TRUE) flags |= windows.FILE_RENAME_REPLACE_IF_EXISTS; | |
| 2626 | rename_info.* = .{ | |
| 2627 | .Flags = flags, | |
| 2628 | .RootDirectory = if (fs.path.isAbsoluteWindowsWtf16(new_path_w)) null else new_dir_fd, | |
| 2629 | .FileNameLength = @intCast(new_path_w.len * 2), // already checked error.NameTooLong | |
| 2630 | .FileName = undefined, | |
| 2631 | }; | |
| 2632 | @memcpy((&rename_info.FileName).ptr, new_path_w); | |
| 2633 | rc = windows.ntdll.NtSetInformationFile( | |
| 2634 | src_fd, | |
| 2635 | &io_status_block, | |
| 2636 | rename_info, | |
| 2637 | @intCast(struct_len), // already checked for error.NameTooLong | |
| 2638 | .FileRenameInformationEx, | |
| 2639 | ); | |
| 2640 | switch (rc) { | |
| 2641 | .SUCCESS => return, | |
| 2642 | // The filesystem does not support FileDispositionInformationEx | |
| 2643 | .INVALID_PARAMETER, | |
| 2644 | // The operating system does not support FileDispositionInformationEx | |
| 2645 | .INVALID_INFO_CLASS, | |
| 2646 | // The operating system does not support one of the flags | |
| 2647 | .NOT_SUPPORTED, | |
| 2648 | => break :need_fallback true, | |
| 2649 | // For all other statuses, fall down to the switch below to handle them. | |
| 2650 | else => break :need_fallback false, | |
| 2651 | } | |
| 2652 | }; | |
| 2653 | ||
| 2654 | if (need_fallback) { | |
| 2655 | const struct_buf_len = @sizeOf(windows.FILE_RENAME_INFORMATION) + (max_path_bytes - 1); | |
| 2656 | var rename_info_buf: [struct_buf_len]u8 align(@alignOf(windows.FILE_RENAME_INFORMATION)) = undefined; | |
| 2657 | const struct_len = @sizeOf(windows.FILE_RENAME_INFORMATION) - 1 + new_path_w.len * 2; | |
| 2658 | if (struct_len > struct_buf_len) return error.NameTooLong; | |
| 2659 | ||
| 2660 | const rename_info: *windows.FILE_RENAME_INFORMATION = @ptrCast(&rename_info_buf); | |
| 2661 | var io_status_block: windows.IO_STATUS_BLOCK = undefined; | |
| 2662 | ||
| 2663 | rename_info.* = .{ | |
| 2664 | .Flags = ReplaceIfExists, | |
| 2665 | .RootDirectory = if (fs.path.isAbsoluteWindowsWtf16(new_path_w)) null else new_dir_fd, | |
| 2666 | .FileNameLength = @intCast(new_path_w.len * 2), // already checked error.NameTooLong | |
| 2667 | .FileName = undefined, | |
| 2668 | }; | |
| 2669 | @memcpy((&rename_info.FileName).ptr, new_path_w); | |
| 2670 | ||
| 2671 | rc = windows.ntdll.NtSetInformationFile( | |
| 2672 | src_fd, | |
| 2673 | &io_status_block, | |
| 2674 | rename_info, | |
| 2675 | @intCast(struct_len), // already checked for error.NameTooLong | |
| 2676 | .FileRenameInformation, | |
| 2677 | ); | |
| 2678 | } | |
| 2679 | ||
| 2680 | switch (rc) { | |
| 2681 | .SUCCESS => {}, | |
| 2682 | .INVALID_HANDLE => unreachable, | |
| 2683 | .INVALID_PARAMETER => unreachable, | |
| 2684 | .OBJECT_PATH_SYNTAX_BAD => unreachable, | |
| 2685 | .ACCESS_DENIED => return error.AccessDenied, | |
| 2686 | .OBJECT_NAME_NOT_FOUND => return error.FileNotFound, | |
| 2687 | .OBJECT_PATH_NOT_FOUND => return error.FileNotFound, | |
| 2688 | .NOT_SAME_DEVICE => return error.RenameAcrossMountPoints, | |
| 2689 | .OBJECT_NAME_COLLISION => return error.PathAlreadyExists, | |
| 2690 | .DIRECTORY_NOT_EMPTY => return error.PathAlreadyExists, | |
| 2691 | .FILE_IS_A_DIRECTORY => return error.IsDir, | |
| 2692 | .NOT_A_DIRECTORY => return error.NotDir, | |
| 2693 | else => return windows.unexpectedStatus(rc), | |
| 2694 | } | |
| 2591 | return windows.RenameFile(old_dir_fd, old_path_w, new_dir_fd, new_path_w, ReplaceIfExists != 0); | |
| 2695 | 2592 | } |
| 2696 | 2593 | |
| 2697 | 2594 | /// On Windows, `sub_dir_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). |