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 {...@@ -1459,8 +1459,9 @@ pub const Dir = struct {
1459 try os.mkdiratW(self.fd, sub_path, default_new_dir_mode);1459 try os.mkdiratW(self.fd, sub_path, default_new_dir_mode);
1460 }1460 }
14611461
1462 /// Calls makeDir recursively to make an entire path. Returns success if the path1462 /// Calls makeDir iteratively to make an entire path
1463 /// already exists and is a directory.1463 /// (i.e. creating any parent directories that do not exist).
1464 /// Returns success if the path already exists and is a directory.
1464 /// This function is not atomic, and if it returns an error, the file system may1465 /// This function is not atomic, and if it returns an error, the file system may
1465 /// have been modified regardless.1466 /// have been modified regardless.
1466 pub fn makePath(self: Dir, sub_path: []const u8) !void {1467 pub fn makePath(self: Dir, sub_path: []const u8) !void {
...@@ -1483,22 +1484,89 @@ pub const Dir = struct {...@@ -1483,22 +1484,89 @@ pub const Dir = struct {
1483 }1484 }
1484 }1485 }
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
1486 /// This function performs `makePath`, followed by `openDir`.1521 /// This function performs `makePath`, followed by `openDir`.
1487 /// If supported by the OS, this operation is atomic. It is not atomic on1522 /// If supported by the OS, this operation is atomic. It is not atomic on
1488 /// all operating systems.1523 /// all operating systems.
1524 /// On Windows, this function performs `makeOpenPathAccessMaskW`.
1489 pub fn makeOpenPath(self: Dir, sub_path: []const u8, open_dir_options: OpenDirOptions) !Dir {1525 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 NtClose1526 return switch (builtin.os.tag) {
1491 try self.makePath(sub_path);1527 .windows => {
1492 return self.openDir(sub_path, open_dir_options);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 };
1493 }1544 }
14941545
1495 /// This function performs `makePath`, followed by `openIterableDir`.1546 /// This function performs `makePath`, followed by `openIterableDir`.
1496 /// If supported by the OS, this operation is atomic. It is not atomic on1547 /// If supported by the OS, this operation is atomic. It is not atomic on
1497 /// all operating systems.1548 /// all operating systems.
1498 pub fn makeOpenPathIterable(self: Dir, sub_path: []const u8, open_dir_options: OpenDirOptions) !IterableDir {1549 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 NtClose1550 return switch (builtin.os.tag) {
1500 try self.makePath(sub_path);1551 .windows => {
1501 return self.openIterableDir(sub_path, open_dir_options);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 };
1502 }1570 }
15031571
1504 /// This function returns the canonicalized absolute pathname of1572 /// This function returns the canonicalized absolute pathname of
...@@ -1742,7 +1810,10 @@ pub const Dir = struct {...@@ -1742,7 +1810,10 @@ pub const Dir = struct {
1742 const base_flags = w.STANDARD_RIGHTS_READ | w.FILE_READ_ATTRIBUTES | w.FILE_READ_EA |1810 const base_flags = w.STANDARD_RIGHTS_READ | w.FILE_READ_ATTRIBUTES | w.FILE_READ_EA |
1743 w.SYNCHRONIZE | w.FILE_TRAVERSE;1811 w.SYNCHRONIZE | w.FILE_TRAVERSE;
1744 const flags: u32 = if (iterable) base_flags | w.FILE_LIST_DIRECTORY else base_flags;1812 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 });
1746 return dir;1817 return dir;
1747 }1818 }
17481819
...@@ -1765,7 +1836,12 @@ pub const Dir = struct {...@@ -1765,7 +1836,12 @@ pub const Dir = struct {
1765 return Dir{ .fd = fd };1836 return Dir{ .fd = fd };
1766 }1837 }
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 {
1769 const w = os.windows;1845 const w = os.windows;
17701846
1771 var result = Dir{1847 var result = Dir{
...@@ -1786,7 +1862,7 @@ pub const Dir = struct {...@@ -1786,7 +1862,7 @@ pub const Dir = struct {
1786 .SecurityDescriptor = null,1862 .SecurityDescriptor = null,
1787 .SecurityQualityOfService = null,1863 .SecurityQualityOfService = null,
1788 };1864 };
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;
1790 var io: w.IO_STATUS_BLOCK = undefined;1866 var io: w.IO_STATUS_BLOCK = undefined;
1791 const rc = w.ntdll.NtCreateFile(1867 const rc = w.ntdll.NtCreateFile(
1792 &result.fd,1868 &result.fd,
...@@ -1794,13 +1870,14 @@ pub const Dir = struct {...@@ -1794,13 +1870,14 @@ pub const Dir = struct {
1794 &attr,1870 &attr,
1795 &io,1871 &io,
1796 null,1872 null,
1797 0,1873 w.FILE_ATTRIBUTE_NORMAL,
1798 w.FILE_SHARE_READ | w.FILE_SHARE_WRITE,1874 w.FILE_SHARE_READ | w.FILE_SHARE_WRITE,
1799 w.FILE_OPEN,1875 flags.create_disposition,
1800 w.FILE_DIRECTORY_FILE | w.FILE_SYNCHRONOUS_IO_NONALERT | w.FILE_OPEN_FOR_BACKUP_INTENT | open_reparse_point,1876 w.FILE_DIRECTORY_FILE | w.FILE_SYNCHRONOUS_IO_NONALERT | w.FILE_OPEN_FOR_BACKUP_INTENT | open_reparse_point,
1801 null,1877 null,
1802 0,1878 0,
1803 );1879 );
1880
1804 switch (rc) {1881 switch (rc) {
1805 .SUCCESS => return result,1882 .SUCCESS => return result,
1806 .OBJECT_NAME_INVALID => return error.BadPathName,1883 .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 {...@@ -1507,6 +1507,14 @@ pub fn ComponentIterator(comptime path_type: PathType, comptime T: type) type {
1507 /// For example, if the path is `/a/b/c` and the most recently returned component1507 /// For example, if the path is `/a/b/c` and the most recently returned component
1508 /// is `b`, then this will return the `c` component.1508 /// is `b`, then this will return the `c` component.
1509 pub fn next(self: *Self) ?Component {1509 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 {
1510 var start_index = self.end_index;1518 var start_index = self.end_index;
1511 while (start_index < self.path.len and path_type.isSep(T, self.path[start_index])) {1519 while (start_index < self.path.len and path_type.isSep(T, self.path[start_index])) {
1512 start_index += 1;1520 start_index += 1;
...@@ -1516,11 +1524,9 @@ pub fn ComponentIterator(comptime path_type: PathType, comptime T: type) type {...@@ -1516,11 +1524,9 @@ pub fn ComponentIterator(comptime path_type: PathType, comptime T: type) type {
1516 end_index += 1;1524 end_index += 1;
1517 }1525 }
1518 if (start_index == end_index) return null;1526 if (start_index == end_index) return null;
1519 self.start_index = start_index;
1520 self.end_index = end_index;
1521 return .{1527 return .{
1522 .name = self.path[self.start_index..self.end_index],1528 .name = self.path[start_index..end_index],
1523 .path = self.path[0..self.end_index],1529 .path = self.path[0..end_index],
1524 };1530 };
1525 }1531 }
15261532
...@@ -1529,6 +1535,14 @@ pub fn ComponentIterator(comptime path_type: PathType, comptime T: type) type {...@@ -1529,6 +1535,14 @@ pub fn ComponentIterator(comptime path_type: PathType, comptime T: type) type {
1529 /// For example, if the path is `/a/b/c` and the most recently returned component1535 /// For example, if the path is `/a/b/c` and the most recently returned component
1530 /// is `b`, then this will return the `a` component.1536 /// is `b`, then this will return the `a` component.
1531 pub fn previous(self: *Self) ?Component {1537 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 {
1532 var end_index = self.start_index;1546 var end_index = self.start_index;
1533 while (true) {1547 while (true) {
1534 if (end_index == self.root_end_index) return null;1548 if (end_index == self.root_end_index) return null;
...@@ -1542,11 +1556,9 @@ pub fn ComponentIterator(comptime path_type: PathType, comptime T: type) type {...@@ -1542,11 +1556,9 @@ pub fn ComponentIterator(comptime path_type: PathType, comptime T: type) type {
1542 start_index -= 1;1556 start_index -= 1;
1543 }1557 }
1544 if (start_index == end_index) return null;1558 if (start_index == end_index) return null;
1545 self.start_index = start_index;
1546 self.end_index = end_index;
1547 return .{1559 return .{
1548 .name = self.path[self.start_index..self.end_index],1560 .name = self.path[start_index..end_index],
1549 .path = self.path[0..self.end_index],1561 .path = self.path[0..end_index],
1550 };1562 };
1551 }1563 }
1552 };1564 };
lib/std/fs/test.zig+12
...@@ -663,6 +663,18 @@ test "file operations on directories" {...@@ -663,6 +663,18 @@ test "file operations on directories" {
663 }.impl);663 }.impl);
664}664}
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
666test "deleteDir" {678test "deleteDir" {
667 try testWithAllSupportedPathTypes(struct {679 try testWithAllSupportedPathTypes(struct {
668 fn impl(ctx: *TestContext) !void {680 fn impl(ctx: *TestContext) !void {