diff options
Diffstat (limited to 'lib/std')
| -rw-r--r-- | lib/std/fs.zig | 100 | ||||
| -rw-r--r-- | lib/std/fs/test.zig | 12 |
2 files changed, 101 insertions, 11 deletions
diff --git a/lib/std/fs.zig b/lib/std/fs.zig index d95321cfd3..808124f5aa 100644 --- a/lib/std/fs.zig +++ b/lib/std/fs.zig @@ -1483,22 +1483,91 @@ pub const Dir = struct { } } + /// Calls makeOpenDirAccessMaskW recursively to make an entire path + /// (i.e. falling back if the parent directory does not exist). Opens the dir if the path + /// already exists and is a directory. + /// This function is not atomic, and if it returns an error, the file system may + /// have been modified regardless. + fn makeOpenPathAccessMaskW(self: Dir, sub_path: []const u8, access_mask: u32, no_follow: bool) OpenError!Dir { + const w = os.windows; + var end_index: usize = sub_path.len; + + return while (true) { + const sub_path_w = try w.sliceToPrefixedFileW(sub_path[0..end_index]); + const result = self.makeOpenDirAccessMaskW(sub_path_w.span().ptr, access_mask, .{ + .no_follow = no_follow, + .create_disposition = if (end_index == sub_path.len) w.FILE_OPEN_IF else w.FILE_CREATE, + }) catch |err| switch (err) { + error.FileNotFound => { + // march end_index backward until next path component + while (true) { + if (end_index == 0) return err; + end_index -= 1; + if (path.isSep(sub_path[end_index])) break; + } + continue; + }, + else => return err, + }; + + if (end_index == sub_path.len) return result; + // march end_index forward until next path component + while (true) { + end_index += 1; + if (end_index == sub_path.len or path.isSep(sub_path[end_index])) break; + } + }; + } + /// This function performs `makePath`, followed by `openDir`. /// If supported by the OS, this operation is atomic. It is not atomic on /// all operating systems. + /// On Windows, this function performs `makeOpenPathAccessMaskW`. pub fn makeOpenPath(self: Dir, sub_path: []const u8, open_dir_options: OpenDirOptions) !Dir { - // TODO improve this implementation on Windows; we can avoid 1 call to NtClose - try self.makePath(sub_path); - return self.openDir(sub_path, open_dir_options); + return switch (builtin.os.tag) { + .windows => { + const w = os.windows; + const base_flags = w.STANDARD_RIGHTS_READ | w.FILE_READ_ATTRIBUTES | w.FILE_READ_EA | + w.SYNCHRONIZE | w.FILE_TRAVERSE; + + return self.makeOpenPathAccessMaskW(sub_path, base_flags, open_dir_options.no_follow); + }, + else => { + return self.openDir(sub_path, open_dir_options) catch |err| switch (err) { + error.FileNotFound => { + try self.makePath(sub_path); + return self.openDir(sub_path, open_dir_options); + }, + else => |e| return e, + }; + }, + }; } /// This function performs `makePath`, followed by `openIterableDir`. /// If supported by the OS, this operation is atomic. It is not atomic on /// all operating systems. pub fn makeOpenPathIterable(self: Dir, sub_path: []const u8, open_dir_options: OpenDirOptions) !IterableDir { - // TODO improve this implementation on Windows; we can avoid 1 call to NtClose - try self.makePath(sub_path); - return self.openIterableDir(sub_path, open_dir_options); + return switch (builtin.os.tag) { + .windows => { + const w = os.windows; + const base_flags = w.STANDARD_RIGHTS_READ | w.FILE_READ_ATTRIBUTES | w.FILE_READ_EA | + w.SYNCHRONIZE | w.FILE_TRAVERSE | w.FILE_LIST_DIRECTORY; + + return IterableDir{ + .dir = try self.makeOpenPathAccessMaskW(sub_path, base_flags, open_dir_options.no_follow), + }; + }, + else => { + return self.openIterableDir(sub_path, open_dir_options) catch |err| switch (err) { + error.FileNotFound => { + try self.makePath(sub_path); + return self.openIterableDir(sub_path, open_dir_options); + }, + else => |e| return e, + }; + }, + }; } /// This function returns the canonicalized absolute pathname of @@ -1742,7 +1811,10 @@ pub const Dir = struct { const base_flags = w.STANDARD_RIGHTS_READ | w.FILE_READ_ATTRIBUTES | w.FILE_READ_EA | w.SYNCHRONIZE | w.FILE_TRAVERSE; const flags: u32 = if (iterable) base_flags | w.FILE_LIST_DIRECTORY else base_flags; - var dir = try self.openDirAccessMaskW(sub_path_w, flags, args.no_follow); + var dir = try self.makeOpenDirAccessMaskW(sub_path_w, flags, .{ + .no_follow = args.no_follow, + .create_disposition = w.FILE_OPEN, + }); return dir; } @@ -1765,7 +1837,12 @@ pub const Dir = struct { return Dir{ .fd = fd }; } - fn openDirAccessMaskW(self: Dir, sub_path_w: [*:0]const u16, access_mask: u32, no_follow: bool) OpenError!Dir { + const MakeOpenDirAccessMaskWOptions = struct { + no_follow: bool, + create_disposition: u32, + }; + + fn makeOpenDirAccessMaskW(self: Dir, sub_path_w: [*:0]const u16, access_mask: u32, flags: MakeOpenDirAccessMaskWOptions) OpenError!Dir { const w = os.windows; var result = Dir{ @@ -1786,7 +1863,7 @@ pub const Dir = struct { .SecurityDescriptor = null, .SecurityQualityOfService = null, }; - const open_reparse_point: w.DWORD = if (no_follow) w.FILE_OPEN_REPARSE_POINT else 0x0; + const open_reparse_point: w.DWORD = if (flags.no_follow) w.FILE_OPEN_REPARSE_POINT else 0x0; var io: w.IO_STATUS_BLOCK = undefined; const rc = w.ntdll.NtCreateFile( &result.fd, @@ -1794,13 +1871,14 @@ pub const Dir = struct { &attr, &io, null, - 0, + w.FILE_ATTRIBUTE_NORMAL, w.FILE_SHARE_READ | w.FILE_SHARE_WRITE, - w.FILE_OPEN, + flags.create_disposition, w.FILE_DIRECTORY_FILE | w.FILE_SYNCHRONOUS_IO_NONALERT | w.FILE_OPEN_FOR_BACKUP_INTENT | open_reparse_point, null, 0, ); + switch (rc) { .SUCCESS => return result, .OBJECT_NAME_INVALID => return error.BadPathName, diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index 6f607bfa45..9a11c7cb29 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -663,6 +663,18 @@ test "file operations on directories" { }.impl); } +test "makeOpenPath parent dirs do not exist" { + var tmp_dir = tmpDir(.{}); + defer tmp_dir.cleanup(); + + var dir = try tmp_dir.dir.makeOpenPath("root_dir/parent_dir/some_dir", .{}); + dir.close(); + + // double check that the full directory structure was created + var dir_verification = try tmp_dir.dir.openDir("root_dir/parent_dir/some_dir", .{}); + dir_verification.close(); +} + test "deleteDir" { try testWithAllSupportedPathTypes(struct { fn impl(ctx: *TestContext) !void { |
