authorgravatar for qusai.hroub@outlook.comQusai Hroub <qusai.hroub@outlook.com> 2023-03-04 13:10:33+02:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-10-18 20:36:03-07:00
logf6a6cdbba372414e441bbcea8c680e71d12c4a9a
tree9c98fa685051d4cd8ede559695a20d5ac21fbb95
parent1bbe521074727e0ac474413aa23fa1f6121c577c

std.fs.Dir.makeOpenPath: optimize when path already exists

Uses a single NtCreateFile syscall on windows. Closes #12474. Thanks to @joedavis and @matu3ba.

2 files changed, 101 insertions(+), 11 deletions(-)

lib/std/fs.zig+89-11
...@@ -1483,22 +1483,91 @@ pub const Dir = struct {...@@ -1483,22 +1483,91 @@ pub const Dir = struct {
1483 }1483 }
1484 }1484 }
14851485
1486 /// Calls makeOpenDirAccessMaskW recursively to make an entire path
1487 /// (i.e. falling back if the parent directory does not exist). Opens the dir if the path
1488 /// already exists and is a directory.
1489 /// This function is not atomic, and if it returns an error, the file system may
1490 /// have been modified regardless.
1491 fn makeOpenPathAccessMaskW(self: Dir, sub_path: []const u8, access_mask: u32, no_follow: bool) OpenError!Dir {
1492 const w = os.windows;
1493 var end_index: usize = sub_path.len;
1494
1495 return while (true) {
1496 const sub_path_w = try w.sliceToPrefixedFileW(sub_path[0..end_index]);
1497 const result = self.makeOpenDirAccessMaskW(sub_path_w.span().ptr, access_mask, .{
1498 .no_follow = no_follow,
1499 .create_disposition = if (end_index == sub_path.len) w.FILE_OPEN_IF else w.FILE_CREATE,
1500 }) catch |err| switch (err) {
1501 error.FileNotFound => {
1502 // march end_index backward until next path component
1503 while (true) {
1504 if (end_index == 0) return err;
1505 end_index -= 1;
1506 if (path.isSep(sub_path[end_index])) break;
1507 }
1508 continue;
1509 },
1510 else => return err,
1511 };
1512
1513 if (end_index == sub_path.len) return result;
1514 // march end_index forward until next path component
1515 while (true) {
1516 end_index += 1;
1517 if (end_index == sub_path.len or path.isSep(sub_path[end_index])) break;
1518 }
1519 };
1520 }
1521
1486 /// This function performs `makePath`, followed by `openDir`.1522 /// This function performs `makePath`, followed by `openDir`.
1487 /// If supported by the OS, this operation is atomic. It is not atomic on1523 /// If supported by the OS, this operation is atomic. It is not atomic on
1488 /// all operating systems.1524 /// all operating systems.
1525 /// On Windows, this function performs `makeOpenPathAccessMaskW`.
1489 pub fn makeOpenPath(self: Dir, sub_path: []const u8, open_dir_options: OpenDirOptions) !Dir {1526 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 NtClose1527 return switch (builtin.os.tag) {
1491 try self.makePath(sub_path);1528 .windows => {
1492 return self.openDir(sub_path, open_dir_options);1529 const w = os.windows;
1530 const base_flags = w.STANDARD_RIGHTS_READ | w.FILE_READ_ATTRIBUTES | w.FILE_READ_EA |
1531 w.SYNCHRONIZE | w.FILE_TRAVERSE;
1532
1533 return self.makeOpenPathAccessMaskW(sub_path, base_flags, open_dir_options.no_follow);
1534 },
1535 else => {
1536 return self.openDir(sub_path, open_dir_options) catch |err| switch (err) {
1537 error.FileNotFound => {
1538 try self.makePath(sub_path);
1539 return self.openDir(sub_path, open_dir_options);
1540 },
1541 else => |e| return e,
1542 };
1543 },
1544 };
1493 }1545 }
14941546
1495 /// This function performs `makePath`, followed by `openIterableDir`.1547 /// This function performs `makePath`, followed by `openIterableDir`.
1496 /// If supported by the OS, this operation is atomic. It is not atomic on1548 /// If supported by the OS, this operation is atomic. It is not atomic on
1497 /// all operating systems.1549 /// all operating systems.
1498 pub fn makeOpenPathIterable(self: Dir, sub_path: []const u8, open_dir_options: OpenDirOptions) !IterableDir {1550 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 NtClose1551 return switch (builtin.os.tag) {
1500 try self.makePath(sub_path);1552 .windows => {
1501 return self.openIterableDir(sub_path, open_dir_options);1553 const w = os.windows;
1554 const base_flags = w.STANDARD_RIGHTS_READ | w.FILE_READ_ATTRIBUTES | w.FILE_READ_EA |
1555 w.SYNCHRONIZE | w.FILE_TRAVERSE | w.FILE_LIST_DIRECTORY;
1556
1557 return IterableDir{
1558 .dir = try self.makeOpenPathAccessMaskW(sub_path, base_flags, open_dir_options.no_follow),
1559 };
1560 },
1561 else => {
1562 return self.openIterableDir(sub_path, open_dir_options) catch |err| switch (err) {
1563 error.FileNotFound => {
1564 try self.makePath(sub_path);
1565 return self.openIterableDir(sub_path, open_dir_options);
1566 },
1567 else => |e| return e,
1568 };
1569 },
1570 };
1502 }1571 }
15031572
1504 /// This function returns the canonicalized absolute pathname of1573 /// This function returns the canonicalized absolute pathname of
...@@ -1742,7 +1811,10 @@ pub const Dir = struct {...@@ -1742,7 +1811,10 @@ pub const Dir = struct {
1742 const base_flags = w.STANDARD_RIGHTS_READ | w.FILE_READ_ATTRIBUTES | w.FILE_READ_EA |1811 const base_flags = w.STANDARD_RIGHTS_READ | w.FILE_READ_ATTRIBUTES | w.FILE_READ_EA |
1743 w.SYNCHRONIZE | w.FILE_TRAVERSE;1812 w.SYNCHRONIZE | w.FILE_TRAVERSE;
1744 const flags: u32 = if (iterable) base_flags | w.FILE_LIST_DIRECTORY else base_flags;1813 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);1814 var dir = try self.makeOpenDirAccessMaskW(sub_path_w, flags, .{
1815 .no_follow = args.no_follow,
1816 .create_disposition = w.FILE_OPEN,
1817 });
1746 return dir;1818 return dir;
1747 }1819 }
17481820
...@@ -1765,7 +1837,12 @@ pub const Dir = struct {...@@ -1765,7 +1837,12 @@ pub const Dir = struct {
1765 return Dir{ .fd = fd };1837 return Dir{ .fd = fd };
1766 }1838 }
17671839
1768 fn openDirAccessMaskW(self: Dir, sub_path_w: [*:0]const u16, access_mask: u32, no_follow: bool) OpenError!Dir {1840 const MakeOpenDirAccessMaskWOptions = struct {
1841 no_follow: bool,
1842 create_disposition: u32,
1843 };
1844
1845 fn makeOpenDirAccessMaskW(self: Dir, sub_path_w: [*:0]const u16, access_mask: u32, flags: MakeOpenDirAccessMaskWOptions) OpenError!Dir {
1769 const w = os.windows;1846 const w = os.windows;
17701847
1771 var result = Dir{1848 var result = Dir{
...@@ -1786,7 +1863,7 @@ pub const Dir = struct {...@@ -1786,7 +1863,7 @@ pub const Dir = struct {
1786 .SecurityDescriptor = null,1863 .SecurityDescriptor = null,
1787 .SecurityQualityOfService = null,1864 .SecurityQualityOfService = null,
1788 };1865 };
1789 const open_reparse_point: w.DWORD = if (no_follow) w.FILE_OPEN_REPARSE_POINT else 0x0;1866 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;1867 var io: w.IO_STATUS_BLOCK = undefined;
1791 const rc = w.ntdll.NtCreateFile(1868 const rc = w.ntdll.NtCreateFile(
1792 &result.fd,1869 &result.fd,
...@@ -1794,13 +1871,14 @@ pub const Dir = struct {...@@ -1794,13 +1871,14 @@ pub const Dir = struct {
1794 &attr,1871 &attr,
1795 &io,1872 &io,
1796 null,1873 null,
1797 0,1874 w.FILE_ATTRIBUTE_NORMAL,
1798 w.FILE_SHARE_READ | w.FILE_SHARE_WRITE,1875 w.FILE_SHARE_READ | w.FILE_SHARE_WRITE,
1799 w.FILE_OPEN,1876 flags.create_disposition,
1800 w.FILE_DIRECTORY_FILE | w.FILE_SYNCHRONOUS_IO_NONALERT | w.FILE_OPEN_FOR_BACKUP_INTENT | open_reparse_point,1877 w.FILE_DIRECTORY_FILE | w.FILE_SYNCHRONOUS_IO_NONALERT | w.FILE_OPEN_FOR_BACKUP_INTENT | open_reparse_point,
1801 null,1878 null,
1802 0,1879 0,
1803 );1880 );
1881
1804 switch (rc) {1882 switch (rc) {
1805 .SUCCESS => return result,1883 .SUCCESS => return result,
1806 .OBJECT_NAME_INVALID => return error.BadPathName,1884 .OBJECT_NAME_INVALID => return error.BadPathName,
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 {