authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-10-21 23:30:47-04:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2023-10-21 23:30:47-04:00
loge354aac8f21ffb1a30602b32fbacf64e5067278e
treeb81a2630c5510f0f9c7ad6b0bde6373cf50bf430
parent7d50634e0ad4355e339bc243a2e2842693e133f9
parentfb5f69a55283e65a8ae9508e74b9eefedc3ac3da
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #14833 from QusaiHroub/optimize_std.fs.Dir.makeOpenPath_12474

#12474: std.fs.Dir.makeOpenPath: optimize case, if path already exists

3 files changed, 122 insertions(+), 21 deletions(-)

lib/std/fs.zig+90-13
......@@ -1459,8 +1459,9 @@ pub const Dir = struct {
14591459 try os.mkdiratW(self.fd, sub_path, default_new_dir_mode);
14601460 }
14611461
1462 /// Calls makeDir recursively to make an entire path. Returns success if the path
1463 /// already exists and is a directory.
1462 /// Calls makeDir iteratively to make an entire path
1463 /// (i.e. creating any parent directories that do not exist).
1464 /// Returns success if the path already exists and is a directory.
14641465 /// This function is not atomic, and if it returns an error, the file system may
14651466 /// have been modified regardless.
14661467 pub fn makePath(self: Dir, sub_path: []const u8) !void {
......@@ -1483,22 +1484,89 @@ pub const Dir = struct {
14831484 }
14841485 }
14851486
1487 /// Calls makeOpenDirAccessMaskW iteratively to make an entire path
1488 /// (i.e. creating any parent directories that do not exist).
1489 /// Opens the dir if the path already exists and is a directory.
1490 /// This function is not atomic, and if it returns an error, the file system may
1491 /// have been modified regardless.
1492 fn makeOpenPathAccessMaskW(self: Dir, sub_path: []const u8, access_mask: u32, no_follow: bool) OpenError!Dir {
1493 const w = os.windows;
1494 var it = try path.componentIterator(sub_path);
1495 // If there are no components in the path, then create a dummy component with the full path.
1496 var component = it.last() orelse path.NativeUtf8ComponentIterator.Component{
1497 .name = "",
1498 .path = sub_path,
1499 };
1500
1501 while (true) {
1502 const sub_path_w = try w.sliceToPrefixedFileW(self.fd, component.path);
1503 const is_last = it.peekNext() == null;
1504 var result = self.makeOpenDirAccessMaskW(sub_path_w.span().ptr, access_mask, .{
1505 .no_follow = no_follow,
1506 .create_disposition = if (is_last) w.FILE_OPEN_IF else w.FILE_CREATE,
1507 }) catch |err| switch (err) {
1508 error.FileNotFound => |e| {
1509 component = it.previous() orelse return e;
1510 continue;
1511 },
1512 else => |e| return e,
1513 };
1514
1515 component = it.next() orelse return result;
1516 // Don't leak the intermediate file handles
1517 result.close();
1518 }
1519 }
1520
14861521 /// This function performs `makePath`, followed by `openDir`.
14871522 /// If supported by the OS, this operation is atomic. It is not atomic on
14881523 /// all operating systems.
1524 /// On Windows, this function performs `makeOpenPathAccessMaskW`.
14891525 pub fn makeOpenPath(self: Dir, sub_path: []const u8, open_dir_options: OpenDirOptions) !Dir {
1490 // TODO improve this implementation on Windows; we can avoid 1 call to NtClose
1491 try self.makePath(sub_path);
1492 return self.openDir(sub_path, open_dir_options);
1526 return switch (builtin.os.tag) {
1527 .windows => {
1528 const w = os.windows;
1529 const base_flags = w.STANDARD_RIGHTS_READ | w.FILE_READ_ATTRIBUTES | w.FILE_READ_EA |
1530 w.SYNCHRONIZE | w.FILE_TRAVERSE;
1531
1532 return self.makeOpenPathAccessMaskW(sub_path, base_flags, open_dir_options.no_follow);
1533 },
1534 else => {
1535 return self.openDir(sub_path, open_dir_options) catch |err| switch (err) {
1536 error.FileNotFound => {
1537 try self.makePath(sub_path);
1538 return self.openDir(sub_path, open_dir_options);
1539 },
1540 else => |e| return e,
1541 };
1542 },
1543 };
14931544 }
14941545
14951546 /// This function performs `makePath`, followed by `openIterableDir`.
14961547 /// If supported by the OS, this operation is atomic. It is not atomic on
14971548 /// all operating systems.
14981549 pub fn makeOpenPathIterable(self: Dir, sub_path: []const u8, open_dir_options: OpenDirOptions) !IterableDir {
1499 // TODO improve this implementation on Windows; we can avoid 1 call to NtClose
1500 try self.makePath(sub_path);
1501 return self.openIterableDir(sub_path, open_dir_options);
1550 return switch (builtin.os.tag) {
1551 .windows => {
1552 const w = os.windows;
1553 const base_flags = w.STANDARD_RIGHTS_READ | w.FILE_READ_ATTRIBUTES | w.FILE_READ_EA |
1554 w.SYNCHRONIZE | w.FILE_TRAVERSE | w.FILE_LIST_DIRECTORY;
1555
1556 return IterableDir{
1557 .dir = try self.makeOpenPathAccessMaskW(sub_path, base_flags, open_dir_options.no_follow),
1558 };
1559 },
1560 else => {
1561 return self.openIterableDir(sub_path, open_dir_options) catch |err| switch (err) {
1562 error.FileNotFound => {
1563 try self.makePath(sub_path);
1564 return self.openIterableDir(sub_path, open_dir_options);
1565 },
1566 else => |e| return e,
1567 };
1568 },
1569 };
15021570 }
15031571
15041572 /// This function returns the canonicalized absolute pathname of
......@@ -1742,7 +1810,10 @@ pub const Dir = struct {
17421810 const base_flags = w.STANDARD_RIGHTS_READ | w.FILE_READ_ATTRIBUTES | w.FILE_READ_EA |
17431811 w.SYNCHRONIZE | w.FILE_TRAVERSE;
17441812 const flags: u32 = if (iterable) base_flags | w.FILE_LIST_DIRECTORY else base_flags;
1745 var dir = try self.openDirAccessMaskW(sub_path_w, flags, args.no_follow);
1813 var dir = try self.makeOpenDirAccessMaskW(sub_path_w, flags, .{
1814 .no_follow = args.no_follow,
1815 .create_disposition = w.FILE_OPEN,
1816 });
17461817 return dir;
17471818 }
17481819
......@@ -1765,7 +1836,12 @@ pub const Dir = struct {
17651836 return Dir{ .fd = fd };
17661837 }
17671838
1768 fn openDirAccessMaskW(self: Dir, sub_path_w: [*:0]const u16, access_mask: u32, no_follow: bool) OpenError!Dir {
1839 const MakeOpenDirAccessMaskWOptions = struct {
1840 no_follow: bool,
1841 create_disposition: u32,
1842 };
1843
1844 fn makeOpenDirAccessMaskW(self: Dir, sub_path_w: [*:0]const u16, access_mask: u32, flags: MakeOpenDirAccessMaskWOptions) OpenError!Dir {
17691845 const w = os.windows;
17701846
17711847 var result = Dir{
......@@ -1786,7 +1862,7 @@ pub const Dir = struct {
17861862 .SecurityDescriptor = null,
17871863 .SecurityQualityOfService = null,
17881864 };
1789 const open_reparse_point: w.DWORD = if (no_follow) w.FILE_OPEN_REPARSE_POINT else 0x0;
1865 const open_reparse_point: w.DWORD = if (flags.no_follow) w.FILE_OPEN_REPARSE_POINT else 0x0;
17901866 var io: w.IO_STATUS_BLOCK = undefined;
17911867 const rc = w.ntdll.NtCreateFile(
17921868 &result.fd,
......@@ -1794,13 +1870,14 @@ pub const Dir = struct {
17941870 &attr,
17951871 &io,
17961872 null,
1797 0,
1873 w.FILE_ATTRIBUTE_NORMAL,
17981874 w.FILE_SHARE_READ | w.FILE_SHARE_WRITE,
1799 w.FILE_OPEN,
1875 flags.create_disposition,
18001876 w.FILE_DIRECTORY_FILE | w.FILE_SYNCHRONOUS_IO_NONALERT | w.FILE_OPEN_FOR_BACKUP_INTENT | open_reparse_point,
18011877 null,
18021878 0,
18031879 );
1880
18041881 switch (rc) {
18051882 .SUCCESS => return result,
18061883 .OBJECT_NAME_INVALID => return error.BadPathName,
lib/std/fs/path.zig+20-8
......@@ -1507,6 +1507,14 @@ pub fn ComponentIterator(comptime path_type: PathType, comptime T: type) type {
15071507 /// For example, if the path is `/a/b/c` and the most recently returned component
15081508 /// is `b`, then this will return the `c` component.
15091509 pub fn next(self: *Self) ?Component {
1510 const peek_result = self.peekNext() orelse return null;
1511 self.start_index = peek_result.path.len - peek_result.name.len;
1512 self.end_index = peek_result.path.len;
1513 return peek_result;
1514 }
1515
1516 /// Like `next`, but does not modify the iterator state.
1517 pub fn peekNext(self: Self) ?Component {
15101518 var start_index = self.end_index;
15111519 while (start_index < self.path.len and path_type.isSep(T, self.path[start_index])) {
15121520 start_index += 1;
......@@ -1516,11 +1524,9 @@ pub fn ComponentIterator(comptime path_type: PathType, comptime T: type) type {
15161524 end_index += 1;
15171525 }
15181526 if (start_index == end_index) return null;
1519 self.start_index = start_index;
1520 self.end_index = end_index;
15211527 return .{
1522 .name = self.path[self.start_index..self.end_index],
1523 .path = self.path[0..self.end_index],
1528 .name = self.path[start_index..end_index],
1529 .path = self.path[0..end_index],
15241530 };
15251531 }
15261532
......@@ -1529,6 +1535,14 @@ pub fn ComponentIterator(comptime path_type: PathType, comptime T: type) type {
15291535 /// For example, if the path is `/a/b/c` and the most recently returned component
15301536 /// is `b`, then this will return the `a` component.
15311537 pub fn previous(self: *Self) ?Component {
1538 const peek_result = self.peekPrevious() orelse return null;
1539 self.start_index = peek_result.path.len - peek_result.name.len;
1540 self.end_index = peek_result.path.len;
1541 return peek_result;
1542 }
1543
1544 /// Like `previous`, but does not modify the iterator state.
1545 pub fn peekPrevious(self: Self) ?Component {
15321546 var end_index = self.start_index;
15331547 while (true) {
15341548 if (end_index == self.root_end_index) return null;
......@@ -1542,11 +1556,9 @@ pub fn ComponentIterator(comptime path_type: PathType, comptime T: type) type {
15421556 start_index -= 1;
15431557 }
15441558 if (start_index == end_index) return null;
1545 self.start_index = start_index;
1546 self.end_index = end_index;
15471559 return .{
1548 .name = self.path[self.start_index..self.end_index],
1549 .path = self.path[0..self.end_index],
1560 .name = self.path[start_index..end_index],
1561 .path = self.path[0..end_index],
15501562 };
15511563 }
15521564 };
lib/std/fs/test.zig+12
......@@ -663,6 +663,18 @@ test "file operations on directories" {
663663 }.impl);
664664}
665665
666test "makeOpenPath parent dirs do not exist" {
667 var tmp_dir = tmpDir(.{});
668 defer tmp_dir.cleanup();
669
670 var dir = try tmp_dir.dir.makeOpenPath("root_dir/parent_dir/some_dir", .{});
671 dir.close();
672
673 // double check that the full directory structure was created
674 var dir_verification = try tmp_dir.dir.openDir("root_dir/parent_dir/some_dir", .{});
675 dir_verification.close();
676}
677
666678test "deleteDir" {
667679 try testWithAllSupportedPathTypes(struct {
668680 fn impl(ctx: *TestContext) !void {