authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-12-31 16:35:01-08:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-01-02 16:58:05-08:00
logd3f87f8ac01039722197a13a12342fc747a90567
tree226dfc7c6e61533b34a083f810b3e1429390e679
parentb4d6e85a339e971829404dc1a260bde4062735f8

std.fs.rename: fix Windows implementation

The semantics of this function are that it moves both files and directories. Previously we had this `is_dir` boolean field of `std.os.windows.OpenFile` which required the API user to choose: are we opening a file or directory? And the other kind would either cause error.IsDir or error.NotDir. But that is not a limitation of the Windows file system API; it was self-imposed. On Windows, rename is implemented internally with `NtCreateFile` so we need to allow it to open either files or directories. This is now done by `std.os.windows.OpenFile` accepting enum{file_only,dir_only,any} instead of a boolean.

4 files changed, 26 insertions(+), 12 deletions(-)

lib/std/fs.zig+1-1
...@@ -1361,7 +1361,7 @@ pub const Dir = struct {...@@ -1361,7 +1361,7 @@ pub const Dir = struct {
1361 .share_access = share_access,1361 .share_access = share_access,
1362 .creation = creation,1362 .creation = creation,
1363 .io_mode = .blocking,1363 .io_mode = .blocking,
1364 .open_dir = true,1364 .filter = .dir_only,
1365 }) catch |er| switch (er) {1365 }) catch |er| switch (er) {
1366 error.WouldBlock => unreachable,1366 error.WouldBlock => unreachable,
1367 else => |e2| return e2,1367 else => |e2| return e2,
lib/std/fs/watch.zig+1-1
...@@ -401,7 +401,7 @@ pub fn Watch(comptime V: type) type {...@@ -401,7 +401,7 @@ pub fn Watch(comptime V: type) type {
401 .access_mask = windows.FILE_LIST_DIRECTORY,401 .access_mask = windows.FILE_LIST_DIRECTORY,
402 .creation = windows.FILE_OPEN,402 .creation = windows.FILE_OPEN,
403 .io_mode = .evented,403 .io_mode = .evented,
404 .open_dir = true,404 .filter = .dir_only,
405 });405 });
406 errdefer windows.CloseHandle(dir_handle);406 errdefer windows.CloseHandle(dir_handle);
407407
lib/std/os.zig+6-5
...@@ -1353,7 +1353,7 @@ fn openOptionsFromFlags(flags: u32) windows.OpenFileOptions {...@@ -1353,7 +1353,7 @@ fn openOptionsFromFlags(flags: u32) windows.OpenFileOptions {
1353 access_mask |= w.GENERIC_READ | w.GENERIC_WRITE;1353 access_mask |= w.GENERIC_READ | w.GENERIC_WRITE;
1354 }1354 }
13551355
1356 const open_dir: bool = flags & O.DIRECTORY != 0;1356 const filter: windows.OpenFileOptions.Filter = if (flags & O.DIRECTORY != 0) .dir_only else .file_only;
1357 const follow_symlinks: bool = flags & O.NOFOLLOW == 0;1357 const follow_symlinks: bool = flags & O.NOFOLLOW == 0;
13581358
1359 const creation: w.ULONG = blk: {1359 const creation: w.ULONG = blk: {
...@@ -1369,7 +1369,7 @@ fn openOptionsFromFlags(flags: u32) windows.OpenFileOptions {...@@ -1369,7 +1369,7 @@ fn openOptionsFromFlags(flags: u32) windows.OpenFileOptions {
1369 .access_mask = access_mask,1369 .access_mask = access_mask,
1370 .io_mode = .blocking,1370 .io_mode = .blocking,
1371 .creation = creation,1371 .creation = creation,
1372 .open_dir = open_dir,1372 .filter = filter,
1373 .follow_symlinks = follow_symlinks,1373 .follow_symlinks = follow_symlinks,
1374 };1374 };
1375}1375}
...@@ -2324,6 +2324,7 @@ pub fn renameatW(...@@ -2324,6 +2324,7 @@ pub fn renameatW(
2324 .access_mask = windows.SYNCHRONIZE | windows.GENERIC_WRITE | windows.DELETE,2324 .access_mask = windows.SYNCHRONIZE | windows.GENERIC_WRITE | windows.DELETE,
2325 .creation = windows.FILE_OPEN,2325 .creation = windows.FILE_OPEN,
2326 .io_mode = .blocking,2326 .io_mode = .blocking,
2327 .filter = .any, // This function is supposed to rename both files and directories.
2327 }) catch |err| switch (err) {2328 }) catch |err| switch (err) {
2328 error.WouldBlock => unreachable, // Not possible without `.share_access_nonblocking = true`.2329 error.WouldBlock => unreachable, // Not possible without `.share_access_nonblocking = true`.
2329 else => |e| return e,2330 else => |e| return e,
...@@ -2435,7 +2436,7 @@ pub fn mkdiratW(dir_fd: fd_t, sub_path_w: []const u16, mode: u32) MakeDirError!v...@@ -2435,7 +2436,7 @@ pub fn mkdiratW(dir_fd: fd_t, sub_path_w: []const u16, mode: u32) MakeDirError!v
2435 .access_mask = windows.GENERIC_READ | windows.SYNCHRONIZE,2436 .access_mask = windows.GENERIC_READ | windows.SYNCHRONIZE,
2436 .creation = windows.FILE_CREATE,2437 .creation = windows.FILE_CREATE,
2437 .io_mode = .blocking,2438 .io_mode = .blocking,
2438 .open_dir = true,2439 .filter = .dir_only,
2439 }) catch |err| switch (err) {2440 }) catch |err| switch (err) {
2440 error.IsDir => unreachable,2441 error.IsDir => unreachable,
2441 error.PipeBusy => unreachable,2442 error.PipeBusy => unreachable,
...@@ -2511,7 +2512,7 @@ pub fn mkdirW(dir_path_w: []const u16, mode: u32) MakeDirError!void {...@@ -2511,7 +2512,7 @@ pub fn mkdirW(dir_path_w: []const u16, mode: u32) MakeDirError!void {
2511 .access_mask = windows.GENERIC_READ | windows.SYNCHRONIZE,2512 .access_mask = windows.GENERIC_READ | windows.SYNCHRONIZE,
2512 .creation = windows.FILE_CREATE,2513 .creation = windows.FILE_CREATE,
2513 .io_mode = .blocking,2514 .io_mode = .blocking,
2514 .open_dir = true,2515 .filter = .dir_only,
2515 }) catch |err| switch (err) {2516 }) catch |err| switch (err) {
2516 error.IsDir => unreachable,2517 error.IsDir => unreachable,
2517 error.PipeBusy => unreachable,2518 error.PipeBusy => unreachable,
...@@ -4693,7 +4694,7 @@ pub fn realpathW(pathname: []const u16, out_buffer: *[MAX_PATH_BYTES]u8) RealPat...@@ -4693,7 +4694,7 @@ pub fn realpathW(pathname: []const u16, out_buffer: *[MAX_PATH_BYTES]u8) RealPat
4693 .share_access = share_access,4694 .share_access = share_access,
4694 .creation = creation,4695 .creation = creation,
4695 .io_mode = .blocking,4696 .io_mode = .blocking,
4696 .open_dir = true,4697 .filter = .dir_only,
4697 }) catch |er| switch (er) {4698 }) catch |er| switch (er) {
4698 error.WouldBlock => unreachable,4699 error.WouldBlock => unreachable,
4699 else => |e2| return e2,4700 else => |e2| return e2,
lib/std/os/windows.zig+18-5
...@@ -53,17 +53,26 @@ pub const OpenFileOptions = struct {...@@ -53,17 +53,26 @@ pub const OpenFileOptions = struct {
53 io_mode: std.io.ModeOverride,53 io_mode: std.io.ModeOverride,
54 /// If true, tries to open path as a directory.54 /// If true, tries to open path as a directory.
55 /// Defaults to false.55 /// Defaults to false.
56 open_dir: bool = false,56 filter: Filter = .file_only,
57 /// If false, tries to open path as a reparse point without dereferencing it.57 /// If false, tries to open path as a reparse point without dereferencing it.
58 /// Defaults to true.58 /// Defaults to true.
59 follow_symlinks: bool = true,59 follow_symlinks: bool = true,
60
61 pub const Filter = enum {
62 /// Causes `OpenFile` to return `error.IsDir` if the opened handle would be a directory.
63 file_only,
64 /// Causes `OpenFile` to return `error.NotDir` if the opened handle would be a file.
65 dir_only,
66 /// `OpenFile` does not discriminate between opening files and directories.
67 any,
68 };
60};69};
6170
62pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HANDLE {71pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HANDLE {
63 if (mem.eql(u16, sub_path_w, &[_]u16{'.'}) and !options.open_dir) {72 if (mem.eql(u16, sub_path_w, &[_]u16{'.'}) and options.filter == .file_only) {
64 return error.IsDir;73 return error.IsDir;
65 }74 }
66 if (mem.eql(u16, sub_path_w, &[_]u16{ '.', '.' }) and !options.open_dir) {75 if (mem.eql(u16, sub_path_w, &[_]u16{ '.', '.' }) and options.filter == .file_only) {
67 return error.IsDir;76 return error.IsDir;
68 }77 }
6978
...@@ -87,7 +96,11 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN...@@ -87,7 +96,11 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN
87 };96 };
88 var io: IO_STATUS_BLOCK = undefined;97 var io: IO_STATUS_BLOCK = undefined;
89 const blocking_flag: ULONG = if (options.io_mode == .blocking) FILE_SYNCHRONOUS_IO_NONALERT else 0;98 const blocking_flag: ULONG = if (options.io_mode == .blocking) FILE_SYNCHRONOUS_IO_NONALERT else 0;
90 const file_or_dir_flag: ULONG = if (options.open_dir) FILE_DIRECTORY_FILE else FILE_NON_DIRECTORY_FILE;99 const file_or_dir_flag: ULONG = switch (options.filter) {
100 .file_only => FILE_NON_DIRECTORY_FILE,
101 .dir_only => FILE_DIRECTORY_FILE,
102 .any => 0,
103 };
91 // If we're not following symlinks, we need to ensure we don't pass in any synchronization flags such as FILE_SYNCHRONOUS_IO_NONALERT.104 // If we're not following symlinks, we need to ensure we don't pass in any synchronization flags such as FILE_SYNCHRONOUS_IO_NONALERT.
92 const flags: ULONG = if (options.follow_symlinks) file_or_dir_flag | blocking_flag else file_or_dir_flag | FILE_OPEN_REPARSE_POINT;105 const flags: ULONG = if (options.follow_symlinks) file_or_dir_flag | blocking_flag else file_or_dir_flag | FILE_OPEN_REPARSE_POINT;
93106
...@@ -695,7 +708,7 @@ pub fn CreateSymbolicLink(...@@ -695,7 +708,7 @@ pub fn CreateSymbolicLink(
695 .dir = dir,708 .dir = dir,
696 .creation = FILE_CREATE,709 .creation = FILE_CREATE,
697 .io_mode = .blocking,710 .io_mode = .blocking,
698 .open_dir = is_directory,711 .filter = if (is_directory) .dir_only else .file_only,
699 }) catch |err| switch (err) {712 }) catch |err| switch (err) {
700 error.IsDir => return error.PathAlreadyExists,713 error.IsDir => return error.PathAlreadyExists,
701 error.NotDir => unreachable,714 error.NotDir => unreachable,