authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-11-22 12:35:33-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-11-22 12:35:33-07:00
log519ba9bb654a4d5caf7440160f018b7a3ae1e95a
tree2308d007f7e1bf1a8aed846ad4faef8e729e0598
parente4977f3e89fcc164a4d02cd38eb066cfe1a1124f

Revert "Merge pull request #12060 from Vexu/IterableDir"

This reverts commit da94227f783ec3c92859c4713b80a668f1183f96, reversing changes made to 8f943b3d33432a26b7e242c1181e4220ed400501. I was against this change originally, but decided to approve it to keep an open mind. After a year of trying it in practice, I firmly believe that the previous way of doing it was better.

10 files changed, 146 insertions(+), 244 deletions(-)

lib/std/fs.zig+88-146
......@@ -310,10 +310,8 @@ pub fn renameW(old_dir: Dir, old_sub_path_w: []const u16, new_dir: Dir, new_sub_
310310 return os.renameatW(old_dir.fd, old_sub_path_w, new_dir.fd, new_sub_path_w);
311311}
312312
313/// A directory that can be iterated. It is *NOT* legal to initialize this with a regular `Dir`
314/// that has been opened without iteration permission.
315pub const IterableDir = struct {
316 dir: Dir,
313pub const Dir = struct {
314 fd: os.fd_t,
317315
318316 pub const Entry = struct {
319317 name: []const u8,
......@@ -879,18 +877,18 @@ pub const IterableDir = struct {
879877 else => @compileError("unimplemented"),
880878 };
881879
882 pub fn iterate(self: IterableDir) Iterator {
880 pub fn iterate(self: Dir) Iterator {
883881 return self.iterateImpl(true);
884882 }
885883
886884 /// Like `iterate`, but will not reset the directory cursor before the first
887885 /// iteration. This should only be used in cases where it is known that the
888 /// `IterableDir` has not had its cursor modified yet (e.g. it was just opened).
889 pub fn iterateAssumeFirstIteration(self: IterableDir) Iterator {
886 /// `Dir` has not had its cursor modified yet (e.g. it was just opened).
887 pub fn iterateAssumeFirstIteration(self: Dir) Iterator {
890888 return self.iterateImpl(false);
891889 }
892890
893 fn iterateImpl(self: IterableDir, first_iter_start_value: bool) Iterator {
891 fn iterateImpl(self: Dir, first_iter_start_value: bool) Iterator {
894892 switch (builtin.os.tag) {
895893 .macos,
896894 .ios,
......@@ -901,7 +899,7 @@ pub const IterableDir = struct {
901899 .solaris,
902900 .illumos,
903901 => return Iterator{
904 .dir = self.dir,
902 .dir = self,
905903 .seek = 0,
906904 .index = 0,
907905 .end_index = 0,
......@@ -909,14 +907,14 @@ pub const IterableDir = struct {
909907 .first_iter = first_iter_start_value,
910908 },
911909 .linux, .haiku => return Iterator{
912 .dir = self.dir,
910 .dir = self,
913911 .index = 0,
914912 .end_index = 0,
915913 .buf = undefined,
916914 .first_iter = first_iter_start_value,
917915 },
918916 .windows => return Iterator{
919 .dir = self.dir,
917 .dir = self,
920918 .index = 0,
921919 .end_index = 0,
922920 .first_iter = first_iter_start_value,
......@@ -924,7 +922,7 @@ pub const IterableDir = struct {
924922 .name_data = undefined,
925923 },
926924 .wasi => return Iterator{
927 .dir = self.dir,
925 .dir = self,
928926 .cookie = os.wasi.DIRCOOKIE_START,
929927 .index = 0,
930928 .end_index = 0,
......@@ -945,11 +943,11 @@ pub const IterableDir = struct {
945943 dir: Dir,
946944 basename: []const u8,
947945 path: []const u8,
948 kind: IterableDir.Entry.Kind,
946 kind: Dir.Entry.Kind,
949947 };
950948
951949 const StackItem = struct {
952 iter: IterableDir.Iterator,
950 iter: Dir.Iterator,
953951 dirname_len: usize,
954952 };
955953
......@@ -980,7 +978,7 @@ pub const IterableDir = struct {
980978 }
981979 try self.name_buffer.appendSlice(base.name);
982980 if (base.kind == .directory) {
983 var new_dir = top.iter.dir.openIterableDir(base.name, .{}) catch |err| switch (err) {
981 var new_dir = top.iter.dir.openDir(base.name, .{ .iterate = true }) catch |err| switch (err) {
984982 error.NameTooLong => unreachable, // no path sep in base.name
985983 else => |e| return e,
986984 };
......@@ -1023,10 +1021,11 @@ pub const IterableDir = struct {
10231021 };
10241022
10251023 /// Recursively iterates over a directory.
1024 /// `self` must have been opened with `OpenDirOptions{.iterate = true}`.
10261025 /// Must call `Walker.deinit` when done.
10271026 /// The order of returned file system entries is undefined.
10281027 /// `self` will not be closed after walking it.
1029 pub fn walk(self: IterableDir, allocator: Allocator) !Walker {
1028 pub fn walk(self: Dir, allocator: Allocator) !Walker {
10301029 var name_buffer = std.ArrayList(u8).init(allocator);
10311030 errdefer name_buffer.deinit();
10321031
......@@ -1044,49 +1043,6 @@ pub const IterableDir = struct {
10441043 };
10451044 }
10461045
1047 pub fn close(self: *IterableDir) void {
1048 self.dir.close();
1049 self.* = undefined;
1050 }
1051
1052 pub const ChmodError = File.ChmodError;
1053
1054 /// Changes the mode of the directory.
1055 /// The process must have the correct privileges in order to do this
1056 /// successfully, or must have the effective user ID matching the owner
1057 /// of the directory.
1058 pub fn chmod(self: IterableDir, new_mode: File.Mode) ChmodError!void {
1059 const file: File = .{
1060 .handle = self.dir.fd,
1061 .capable_io_mode = .blocking,
1062 };
1063 try file.chmod(new_mode);
1064 }
1065
1066 /// Changes the owner and group of the directory.
1067 /// The process must have the correct privileges in order to do this
1068 /// successfully. The group may be changed by the owner of the directory to
1069 /// any group of which the owner is a member. If the
1070 /// owner or group is specified as `null`, the ID is not changed.
1071 pub fn chown(self: IterableDir, owner: ?File.Uid, group: ?File.Gid) ChownError!void {
1072 const file: File = .{
1073 .handle = self.dir.fd,
1074 .capable_io_mode = .blocking,
1075 };
1076 try file.chown(owner, group);
1077 }
1078
1079 pub const ChownError = File.ChownError;
1080};
1081
1082pub const Dir = struct {
1083 fd: os.fd_t,
1084
1085 pub const iterate = @compileError("only 'IterableDir' can be iterated; 'IterableDir' can be obtained with 'openIterableDir'");
1086 pub const walk = @compileError("only 'IterableDir' can be walked; 'IterableDir' can be obtained with 'openIterableDir'");
1087 pub const chmod = @compileError("only 'IterableDir' can have its mode changed; 'IterableDir' can be obtained with 'openIterableDir'");
1088 pub const chown = @compileError("only 'IterableDir' can have its owner changed; 'IterableDir' can be obtained with 'openIterableDir'");
1089
10901046 pub const OpenError = error{
10911047 FileNotFound,
10921048 NotDir,
......@@ -1529,7 +1485,8 @@ pub const Dir = struct {
15291485 .windows => {
15301486 const w = os.windows;
15311487 const base_flags = w.STANDARD_RIGHTS_READ | w.FILE_READ_ATTRIBUTES | w.FILE_READ_EA |
1532 w.SYNCHRONIZE | w.FILE_TRAVERSE;
1488 w.SYNCHRONIZE | w.FILE_TRAVERSE |
1489 (if (open_dir_options.iterate) w.FILE_LIST_DIRECTORY else 0);
15331490
15341491 return self.makeOpenPathAccessMaskW(sub_path, base_flags, open_dir_options.no_follow);
15351492 },
......@@ -1545,32 +1502,6 @@ pub const Dir = struct {
15451502 };
15461503 }
15471504
1548 /// This function performs `makePath`, followed by `openIterableDir`.
1549 /// If supported by the OS, this operation is atomic. It is not atomic on
1550 /// all operating systems.
1551 pub fn makeOpenPathIterable(self: Dir, sub_path: []const u8, open_dir_options: OpenDirOptions) !IterableDir {
1552 return switch (builtin.os.tag) {
1553 .windows => {
1554 const w = os.windows;
1555 const base_flags = w.STANDARD_RIGHTS_READ | w.FILE_READ_ATTRIBUTES | w.FILE_READ_EA |
1556 w.SYNCHRONIZE | w.FILE_TRAVERSE | w.FILE_LIST_DIRECTORY;
1557
1558 return IterableDir{
1559 .dir = try self.makeOpenPathAccessMaskW(sub_path, base_flags, open_dir_options.no_follow),
1560 };
1561 },
1562 else => {
1563 return self.openIterableDir(sub_path, open_dir_options) catch |err| switch (err) {
1564 error.FileNotFound => {
1565 try self.makePath(sub_path);
1566 return self.openIterableDir(sub_path, open_dir_options);
1567 },
1568 else => |e| return e,
1569 };
1570 },
1571 };
1572 }
1573
15741505 /// This function returns the canonicalized absolute pathname of
15751506 /// `pathname` relative to this `Dir`. If `pathname` is absolute, ignores this
15761507 /// `Dir` handle and returns the canonicalized absolute pathname of `pathname`
......@@ -1706,39 +1637,28 @@ pub const Dir = struct {
17061637 /// such operations are Illegal Behavior.
17071638 access_sub_paths: bool = true,
17081639
1640 /// `true` means the opened directory can be scanned for the files and sub-directories
1641 /// of the result. It means the `iterate` function can be called.
1642 iterate: bool = false,
1643
17091644 /// `true` means it won't dereference the symlinks.
17101645 no_follow: bool = false,
17111646 };
17121647
17131648 /// Opens a directory at the given path. The directory is a system resource that remains
17141649 /// open until `close` is called on the result.
1650 /// The directory cannot be iterated unless the `iterate` option is set to `true`.
17151651 ///
17161652 /// Asserts that the path parameter has no null bytes.
17171653 pub fn openDir(self: Dir, sub_path: []const u8, args: OpenDirOptions) OpenError!Dir {
17181654 if (builtin.os.tag == .windows) {
17191655 const sub_path_w = try os.windows.sliceToPrefixedFileW(self.fd, sub_path);
1720 return self.openDirW(sub_path_w.span().ptr, args, false);
1721 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
1722 return self.openDirWasi(sub_path, args);
1723 } else {
1724 const sub_path_c = try os.toPosixPath(sub_path);
1725 return self.openDirZ(&sub_path_c, args, false);
1726 }
1727 }
1728
1729 /// Opens an iterable directory at the given path. The directory is a system resource that remains
1730 /// open until `close` is called on the result.
1731 ///
1732 /// Asserts that the path parameter has no null bytes.
1733 pub fn openIterableDir(self: Dir, sub_path: []const u8, args: OpenDirOptions) OpenError!IterableDir {
1734 if (builtin.os.tag == .windows) {
1735 const sub_path_w = try os.windows.sliceToPrefixedFileW(self.fd, sub_path);
1736 return IterableDir{ .dir = try self.openDirW(sub_path_w.span().ptr, args, true) };
1656 return .{ .dir = try self.openDirW(sub_path_w.span().ptr, args) };
17371657 } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
1738 return IterableDir{ .dir = try self.openDirWasi(sub_path, args) };
1658 return .{ .dir = try self.openDirWasi(sub_path, args) };
17391659 } else {
17401660 const sub_path_c = try os.toPosixPath(sub_path);
1741 return IterableDir{ .dir = try self.openDirZ(&sub_path_c, args, true) };
1661 return .{ .dir = try self.openDirZ(&sub_path_c, args) };
17421662 }
17431663 }
17441664
......@@ -1790,13 +1710,13 @@ pub const Dir = struct {
17901710 }
17911711
17921712 /// Same as `openDir` except the parameter is null-terminated.
1793 pub fn openDirZ(self: Dir, sub_path_c: [*:0]const u8, args: OpenDirOptions, iterable: bool) OpenError!Dir {
1713 pub fn openDirZ(self: Dir, sub_path_c: [*:0]const u8, args: OpenDirOptions) OpenError!Dir {
17941714 if (builtin.os.tag == .windows) {
17951715 const sub_path_w = try os.windows.cStrToPrefixedFileW(self.fd, sub_path_c);
1796 return self.openDirW(sub_path_w.span().ptr, args, iterable);
1716 return self.openDirW(sub_path_w.span().ptr, args);
17971717 }
17981718 const symlink_flags: u32 = if (args.no_follow) os.O.NOFOLLOW else 0x0;
1799 if (!iterable) {
1719 if (!args.iterate) {
18001720 const O_PATH = if (@hasDecl(os.O, "PATH")) os.O.PATH else 0;
18011721 return self.openDirFlagsZ(sub_path_c, os.O.DIRECTORY | os.O.RDONLY | os.O.CLOEXEC | O_PATH | symlink_flags);
18021722 } else {
......@@ -1806,12 +1726,12 @@ pub const Dir = struct {
18061726
18071727 /// Same as `openDir` except the path parameter is WTF-16 encoded, NT-prefixed.
18081728 /// This function asserts the target OS is Windows.
1809 pub fn openDirW(self: Dir, sub_path_w: [*:0]const u16, args: OpenDirOptions, iterable: bool) OpenError!Dir {
1729 pub fn openDirW(self: Dir, sub_path_w: [*:0]const u16, args: OpenDirOptions) OpenError!Dir {
18101730 const w = os.windows;
18111731 // TODO remove some of these flags if args.access_sub_paths is false
18121732 const base_flags = w.STANDARD_RIGHTS_READ | w.FILE_READ_ATTRIBUTES | w.FILE_READ_EA |
18131733 w.SYNCHRONIZE | w.FILE_TRAVERSE;
1814 const flags: u32 = if (iterable) base_flags | w.FILE_LIST_DIRECTORY else base_flags;
1734 const flags: u32 = if (args.iterate) base_flags | w.FILE_LIST_DIRECTORY else base_flags;
18151735 const dir = try self.makeOpenDirAccessMaskW(sub_path_w, flags, .{
18161736 .no_follow = args.no_follow,
18171737 .create_disposition = w.FILE_OPEN,
......@@ -2203,7 +2123,7 @@ pub const Dir = struct {
22032123 const StackItem = struct {
22042124 name: []const u8,
22052125 parent_dir: Dir,
2206 iter: IterableDir.Iterator,
2126 iter: Dir.Iterator,
22072127
22082128 fn closeAll(items: []@This()) void {
22092129 for (items) |*item| item.iter.dir.close();
......@@ -2227,7 +2147,10 @@ pub const Dir = struct {
22272147 handle_entry: while (true) {
22282148 if (treat_as_dir) {
22292149 if (stack.unusedCapacitySlice().len >= 1) {
2230 var iterable_dir = top.iter.dir.openIterableDir(entry.name, .{ .no_follow = true }) catch |err| switch (err) {
2150 var iterable_dir = top.iter.dir.openDir(entry.name, .{
2151 .no_follow = true,
2152 .iterate = true,
2153 }) catch |err| switch (err) {
22312154 error.NotDir => {
22322155 treat_as_dir = false;
22332156 continue :handle_entry;
......@@ -2318,7 +2241,10 @@ pub const Dir = struct {
23182241 var treat_as_dir = true;
23192242 handle_entry: while (true) {
23202243 if (treat_as_dir) {
2321 break :iterable_dir parent_dir.openIterableDir(name, .{ .no_follow = true }) catch |err| switch (err) {
2244 break :iterable_dir parent_dir.openDir(name, .{
2245 .no_follow = true,
2246 .iterate = true,
2247 }) catch |err| switch (err) {
23222248 error.NotDir => {
23232249 treat_as_dir = false;
23242250 continue :handle_entry;
......@@ -2393,12 +2319,12 @@ pub const Dir = struct {
23932319
23942320 fn deleteTreeMinStackSizeWithKindHint(self: Dir, sub_path: []const u8, kind_hint: File.Kind) DeleteTreeError!void {
23952321 start_over: while (true) {
2396 var iterable_dir = (try self.deleteTreeOpenInitialSubpath(sub_path, kind_hint)) orelse return;
2397 var cleanup_dir_parent: ?IterableDir = null;
2322 var dir = (try self.deleteTreeOpenInitialSubpath(sub_path, kind_hint)) orelse return;
2323 var cleanup_dir_parent: ?Dir = null;
23982324 defer if (cleanup_dir_parent) |*d| d.close();
23992325
24002326 var cleanup_dir = true;
2401 defer if (cleanup_dir) iterable_dir.close();
2327 defer if (cleanup_dir) dir.close();
24022328
24032329 // Valid use of MAX_PATH_BYTES because dir_name_buf will only
24042330 // ever store a single path component that was returned from the
......@@ -2411,12 +2337,15 @@ pub const Dir = struct {
24112337 // open it, and close the original directory. Repeat. Then start the entire operation over.
24122338
24132339 scan_dir: while (true) {
2414 var dir_it = iterable_dir.iterateAssumeFirstIteration();
2340 var dir_it = dir.iterateAssumeFirstIteration();
24152341 dir_it: while (try dir_it.next()) |entry| {
24162342 var treat_as_dir = entry.kind == .directory;
24172343 handle_entry: while (true) {
24182344 if (treat_as_dir) {
2419 const new_dir = iterable_dir.dir.openIterableDir(entry.name, .{ .no_follow = true }) catch |err| switch (err) {
2345 const new_dir = dir.openDir(entry.name, .{
2346 .no_follow = true,
2347 .iterate = true,
2348 }) catch |err| switch (err) {
24202349 error.NotDir => {
24212350 treat_as_dir = false;
24222351 continue :handle_entry;
......@@ -2442,14 +2371,14 @@ pub const Dir = struct {
24422371 => |e| return e,
24432372 };
24442373 if (cleanup_dir_parent) |*d| d.close();
2445 cleanup_dir_parent = iterable_dir;
2446 iterable_dir = new_dir;
2374 cleanup_dir_parent = dir;
2375 dir = new_dir;
24472376 const result = dir_name_buf[0..entry.name.len];
24482377 @memcpy(result, entry.name);
24492378 dir_name = result;
24502379 continue :scan_dir;
24512380 } else {
2452 if (iterable_dir.dir.deleteFile(entry.name)) {
2381 if (dir.deleteFile(entry.name)) {
24532382 continue :dir_it;
24542383 } else |err| switch (err) {
24552384 error.FileNotFound => continue :dir_it,
......@@ -2480,11 +2409,11 @@ pub const Dir = struct {
24802409 }
24812410 // Reached the end of the directory entries, which means we successfully deleted all of them.
24822411 // Now to remove the directory itself.
2483 iterable_dir.close();
2412 dir.close();
24842413 cleanup_dir = false;
24852414
24862415 if (cleanup_dir_parent) |d| {
2487 d.dir.deleteDir(dir_name) catch |err| switch (err) {
2416 d.deleteDir(dir_name) catch |err| switch (err) {
24882417 // These two things can happen due to file system race conditions.
24892418 error.FileNotFound, error.DirNotEmpty => continue :start_over,
24902419 else => |e| return e,
......@@ -2503,14 +2432,17 @@ pub const Dir = struct {
25032432 }
25042433
25052434 /// On successful delete, returns null.
2506 fn deleteTreeOpenInitialSubpath(self: Dir, sub_path: []const u8, kind_hint: File.Kind) !?IterableDir {
2435 fn deleteTreeOpenInitialSubpath(self: Dir, sub_path: []const u8, kind_hint: File.Kind) !?Dir {
25072436 return iterable_dir: {
25082437 // Treat as a file by default
25092438 var treat_as_dir = kind_hint == .directory;
25102439
25112440 handle_entry: while (true) {
25122441 if (treat_as_dir) {
2513 break :iterable_dir self.openIterableDir(sub_path, .{ .no_follow = true }) catch |err| switch (err) {
2442 break :iterable_dir self.openDir(sub_path, .{
2443 .no_follow = true,
2444 .iterate = true,
2445 }) catch |err| switch (err) {
25142446 error.NotDir => {
25152447 treat_as_dir = false;
25162448 continue :handle_entry;
......@@ -2764,6 +2696,37 @@ pub const Dir = struct {
27642696 return Stat.fromSystem(st);
27652697 }
27662698
2699 pub const ChmodError = File.ChmodError;
2700
2701 /// Changes the mode of the directory.
2702 /// The process must have the correct privileges in order to do this
2703 /// successfully, or must have the effective user ID matching the owner
2704 /// of the directory. Additionally, the directory must have been opened
2705 /// with `OpenDirOptions{ .iterate = true }`.
2706 pub fn chmod(self: Dir, new_mode: File.Mode) ChmodError!void {
2707 const file: File = .{
2708 .handle = self.fd,
2709 .capable_io_mode = .blocking,
2710 };
2711 try file.chmod(new_mode);
2712 }
2713
2714 /// Changes the owner and group of the directory.
2715 /// The process must have the correct privileges in order to do this
2716 /// successfully. The group may be changed by the owner of the directory to
2717 /// any group of which the owner is a member. Additionally, the directory
2718 /// must have been opened with `OpenDirOptions{ .iterate = true }`. If the
2719 /// owner or group is specified as `null`, the ID is not changed.
2720 pub fn chown(self: Dir, owner: ?File.Uid, group: ?File.Gid) ChownError!void {
2721 const file: File = .{
2722 .handle = self.fd,
2723 .capable_io_mode = .blocking,
2724 };
2725 try file.chown(owner, group);
2726 }
2727
2728 pub const ChownError = File.ChownError;
2729
27672730 const Permissions = File.Permissions;
27682731 pub const SetPermissionsError = File.SetPermissionsError;
27692732
......@@ -2829,27 +2792,6 @@ pub fn openDirAbsoluteW(absolute_path_c: [*:0]const u16, flags: Dir.OpenDirOptio
28292792 return cwd().openDirW(absolute_path_c, flags, false);
28302793}
28312794
2832/// Opens a directory at the given path. The directory is a system resource that remains
2833/// open until `close` is called on the result.
2834/// See `openIterableDirAbsoluteZ` for a function that accepts a null-terminated path.
2835///
2836/// Asserts that the path parameter has no null bytes.
2837pub fn openIterableDirAbsolute(absolute_path: []const u8, flags: Dir.OpenDirOptions) File.OpenError!IterableDir {
2838 assert(path.isAbsolute(absolute_path));
2839 return cwd().openIterableDir(absolute_path, flags);
2840}
2841
2842/// Same as `openIterableDirAbsolute` but the path parameter is null-terminated.
2843pub fn openIterableDirAbsoluteZ(absolute_path_c: [*:0]const u8, flags: Dir.OpenDirOptions) File.OpenError!IterableDir {
2844 assert(path.isAbsoluteZ(absolute_path_c));
2845 return IterableDir{ .dir = try cwd().openDirZ(absolute_path_c, flags, true) };
2846}
2847/// Same as `openIterableDirAbsolute` but the path parameter is null-terminated.
2848pub fn openIterableDirAbsoluteW(absolute_path_c: [*:0]const u16, flags: Dir.OpenDirOptions) File.OpenError!IterableDir {
2849 assert(path.isAbsoluteWindowsW(absolute_path_c));
2850 return IterableDir{ .dir = try cwd().openDirW(absolute_path_c, flags, true) };
2851}
2852
28532795/// Opens a file for reading or writing, without attempting to create a new file, based on an absolute path.
28542796/// Call `File.close` to release the resource.
28552797/// Asserts that the path is absolute. See `Dir.openFile` for a function that
lib/std/fs/test.zig+33-35
......@@ -8,10 +8,8 @@ const wasi = std.os.wasi;
88
99const ArenaAllocator = std.heap.ArenaAllocator;
1010const Dir = std.fs.Dir;
11const IterableDir = std.fs.IterableDir;
1211const File = std.fs.File;
1312const tmpDir = testing.tmpDir;
14const tmpIterableDir = testing.tmpIterableDir;
1513
1614const PathType = enum {
1715 relative,
......@@ -76,11 +74,11 @@ const TestContext = struct {
7674 arena: ArenaAllocator,
7775 tmp: testing.TmpIterableDir,
7876 dir: std.fs.Dir,
79 iterable_dir: std.fs.IterableDir,
77 iterable_dir: std.fs.Dir,
8078 transform_fn: *const PathType.TransformFn,
8179
8280 pub fn init(path_type: PathType, allocator: mem.Allocator, transform_fn: *const PathType.TransformFn) TestContext {
83 const tmp = tmpIterableDir(.{});
81 const tmp = tmpDir(.{ .iterate = true });
8482 return .{
8583 .path_type = path_type,
8684 .arena = ArenaAllocator.init(allocator),
......@@ -323,28 +321,28 @@ fn testReadLinkAbsolute(target_path: []const u8, symlink_path: []const u8) !void
323321}
324322
325323test "Dir.Iterator" {
326 var tmp_dir = tmpIterableDir(.{});
324 var tmp_dir = tmpDir(.{ .iterate = true });
327325 defer tmp_dir.cleanup();
328326
329327 // First, create a couple of entries to iterate over.
330 const file = try tmp_dir.iterable_dir.dir.createFile("some_file", .{});
328 const file = try tmp_dir.dir.createFile("some_file", .{});
331329 file.close();
332330
333 try tmp_dir.iterable_dir.dir.makeDir("some_dir");
331 try tmp_dir.dir.makeDir("some_dir");
334332
335333 var arena = ArenaAllocator.init(testing.allocator);
336334 defer arena.deinit();
337335 const allocator = arena.allocator();
338336
339 var entries = std.ArrayList(IterableDir.Entry).init(allocator);
337 var entries = std.ArrayList(Dir.Entry).init(allocator);
340338
341339 // Create iterator.
342 var iter = tmp_dir.iterable_dir.iterate();
340 var iter = tmp_dir.dir.iterate();
343341 while (try iter.next()) |entry| {
344342 // We cannot just store `entry` as on Windows, we're re-using the name buffer
345343 // which means we'll actually share the `name` pointer between entries!
346344 const name = try allocator.dupe(u8, entry.name);
347 try entries.append(.{ .name = name, .kind = entry.kind });
345 try entries.append(Dir.Entry{ .name = name, .kind = entry.kind });
348346 }
349347
350348 try testing.expectEqual(@as(usize, 2), entries.items.len); // note that the Iterator skips '.' and '..'
......@@ -353,7 +351,7 @@ test "Dir.Iterator" {
353351}
354352
355353test "Dir.Iterator many entries" {
356 var tmp_dir = tmpIterableDir(.{});
354 var tmp_dir = tmpDir(.{ .iterate = true });
357355 defer tmp_dir.cleanup();
358356
359357 const num = 1024;
......@@ -369,7 +367,7 @@ test "Dir.Iterator many entries" {
369367 defer arena.deinit();
370368 const allocator = arena.allocator();
371369
372 var entries = std.ArrayList(IterableDir.Entry).init(allocator);
370 var entries = std.ArrayList(Dir.Entry).init(allocator);
373371
374372 // Create iterator.
375373 var iter = tmp_dir.iterable_dir.iterate();
......@@ -388,14 +386,14 @@ test "Dir.Iterator many entries" {
388386}
389387
390388test "Dir.Iterator twice" {
391 var tmp_dir = tmpIterableDir(.{});
389 var tmp_dir = tmpDir(.{ .iterate = true });
392390 defer tmp_dir.cleanup();
393391
394392 // First, create a couple of entries to iterate over.
395 const file = try tmp_dir.iterable_dir.dir.createFile("some_file", .{});
393 const file = try tmp_dir.dir.createFile("some_file", .{});
396394 file.close();
397395
398 try tmp_dir.iterable_dir.dir.makeDir("some_dir");
396 try tmp_dir.dir.makeDir("some_dir");
399397
400398 var arena = ArenaAllocator.init(testing.allocator);
401399 defer arena.deinit();
......@@ -403,15 +401,15 @@ test "Dir.Iterator twice" {
403401
404402 var i: u8 = 0;
405403 while (i < 2) : (i += 1) {
406 var entries = std.ArrayList(IterableDir.Entry).init(allocator);
404 var entries = std.ArrayList(Dir.Entry).init(allocator);
407405
408406 // Create iterator.
409 var iter = tmp_dir.iterable_dir.iterate();
407 var iter = tmp_dir.dir.iterate();
410408 while (try iter.next()) |entry| {
411409 // We cannot just store `entry` as on Windows, we're re-using the name buffer
412410 // which means we'll actually share the `name` pointer between entries!
413411 const name = try allocator.dupe(u8, entry.name);
414 try entries.append(.{ .name = name, .kind = entry.kind });
412 try entries.append(Dir.Entry{ .name = name, .kind = entry.kind });
415413 }
416414
417415 try testing.expectEqual(@as(usize, 2), entries.items.len); // note that the Iterator skips '.' and '..'
......@@ -421,7 +419,7 @@ test "Dir.Iterator twice" {
421419}
422420
423421test "Dir.Iterator reset" {
424 var tmp_dir = tmpIterableDir(.{});
422 var tmp_dir = tmpDir(.{ .iterate = true });
425423 defer tmp_dir.cleanup();
426424
427425 // First, create a couple of entries to iterate over.
......@@ -439,7 +437,7 @@ test "Dir.Iterator reset" {
439437
440438 var i: u8 = 0;
441439 while (i < 2) : (i += 1) {
442 var entries = std.ArrayList(IterableDir.Entry).init(allocator);
440 var entries = std.ArrayList(Dir.Entry).init(allocator);
443441
444442 while (try iter.next()) |entry| {
445443 // We cannot just store `entry` as on Windows, we're re-using the name buffer
......@@ -485,11 +483,11 @@ test "Dir.Iterator but dir is deleted during iteration" {
485483 }
486484}
487485
488fn entryEql(lhs: IterableDir.Entry, rhs: IterableDir.Entry) bool {
486fn entryEql(lhs: Dir.Entry, rhs: Dir.Entry) bool {
489487 return mem.eql(u8, lhs.name, rhs.name) and lhs.kind == rhs.kind;
490488}
491489
492fn contains(entries: *const std.ArrayList(IterableDir.Entry), el: IterableDir.Entry) bool {
490fn contains(entries: *const std.ArrayList(Dir.Entry), el: Dir.Entry) bool {
493491 for (entries.items) |entry| {
494492 if (entryEql(entry, el)) return true;
495493 }
......@@ -963,7 +961,7 @@ test "makePath in a directory that no longer exists" {
963961 try testing.expectError(error.FileNotFound, tmp.dir.makePath("sub-path"));
964962}
965963
966fn testFilenameLimits(iterable_dir: IterableDir, maxed_filename: []const u8) !void {
964fn testFilenameLimits(iterable_dir: Dir, maxed_filename: []const u8) !void {
967965 // setup, create a dir and a nested file both with maxed filenames, and walk the dir
968966 {
969967 var maxed_dir = try iterable_dir.dir.makeOpenPath(maxed_filename, .{});
......@@ -987,7 +985,7 @@ fn testFilenameLimits(iterable_dir: IterableDir, maxed_filename: []const u8) !vo
987985}
988986
989987test "max file name component lengths" {
990 var tmp = tmpIterableDir(.{});
988 var tmp = tmpDir(.{ .iterate = true });
991989 defer tmp.cleanup();
992990
993991 if (builtin.os.tag == .windows) {
......@@ -1384,7 +1382,7 @@ test "open file with exclusive nonblocking lock twice (absolute paths)" {
13841382test "walker" {
13851383 if (builtin.os.tag == .wasi and builtin.link_libc) return error.SkipZigTest;
13861384
1387 var tmp = tmpIterableDir(.{});
1385 var tmp = tmpDir(.{ .iterate = true });
13881386 defer tmp.cleanup();
13891387
13901388 // iteration order of walker is undefined, so need lookup maps to check against
......@@ -1410,10 +1408,10 @@ test "walker" {
14101408 });
14111409
14121410 for (expected_paths.kvs) |kv| {
1413 try tmp.iterable_dir.dir.makePath(kv.key);
1411 try tmp.dir.makePath(kv.key);
14141412 }
14151413
1416 var walker = try tmp.iterable_dir.walk(testing.allocator);
1414 var walker = try tmp.dir.walk(testing.allocator);
14171415 defer walker.deinit();
14181416
14191417 var num_walked: usize = 0;
......@@ -1437,7 +1435,7 @@ test "walker" {
14371435test "walker without fully iterating" {
14381436 if (builtin.os.tag == .wasi and builtin.link_libc) return error.SkipZigTest;
14391437
1440 var tmp = tmpIterableDir(.{});
1438 var tmp = tmpDir(.{ .iterate = true });
14411439 defer tmp.cleanup();
14421440
14431441 var walker = try tmp.iterable_dir.walk(testing.allocator);
......@@ -1556,11 +1554,11 @@ test "chmod" {
15561554 try testing.expectEqual(@as(File.Mode, 0o644), (try file.stat()).mode & 0o7777);
15571555
15581556 try tmp.dir.makeDir("test_dir");
1559 var iterable_dir = try tmp.dir.openIterableDir("test_dir", .{});
1560 defer iterable_dir.close();
1557 var dir = try tmp.dir.openDir("test_dir", .{ .iterate = true });
1558 defer dir.close();
15611559
1562 try iterable_dir.chmod(0o700);
1563 try testing.expectEqual(@as(File.Mode, 0o700), (try iterable_dir.dir.stat()).mode & 0o7777);
1560 try dir.chmod(0o700);
1561 try testing.expectEqual(@as(File.Mode, 0o700), (try dir.stat()).mode & 0o7777);
15641562}
15651563
15661564test "chown" {
......@@ -1576,9 +1574,9 @@ test "chown" {
15761574
15771575 try tmp.dir.makeDir("test_dir");
15781576
1579 var iterable_dir = try tmp.dir.openIterableDir("test_dir", .{});
1580 defer iterable_dir.close();
1581 try iterable_dir.chown(null, null);
1577 var dir = try tmp.dir.openDir("test_dir", .{ .iterate = true });
1578 defer dir.close();
1579 try dir.chown(null, null);
15821580}
15831581
15841582test "File.Metadata" {
lib/std/os.zig+1-1
......@@ -402,7 +402,7 @@ pub fn fchown(fd: fd_t, owner: ?uid_t, group: ?gid_t) FChownError!void {
402402 switch (system.getErrno(res)) {
403403 .SUCCESS => return,
404404 .INTR => continue,
405 .BADF => unreachable, // Can be reached if the fd refers to a non-iterable directory.
405 .BADF => unreachable, // Can be reached if the fd refers to a directory opened without `OpenDirOptions{ .iterate = true }`
406406
407407 .FAULT => unreachable,
408408 .INVAL => unreachable,
lib/std/testing.zig-38
......@@ -543,22 +543,6 @@ pub const TmpDir = struct {
543543 }
544544};
545545
546pub const TmpIterableDir = struct {
547 iterable_dir: std.fs.IterableDir,
548 parent_dir: std.fs.Dir,
549 sub_path: [sub_path_len]u8,
550
551 const random_bytes_count = 12;
552 const sub_path_len = std.fs.base64_encoder.calcSize(random_bytes_count);
553
554 pub fn cleanup(self: *TmpIterableDir) void {
555 self.iterable_dir.close();
556 self.parent_dir.deleteTree(&self.sub_path) catch {};
557 self.parent_dir.close();
558 self.* = undefined;
559 }
560};
561
562546pub fn tmpDir(opts: std.fs.Dir.OpenDirOptions) TmpDir {
563547 var random_bytes: [TmpDir.random_bytes_count]u8 = undefined;
564548 std.crypto.random.bytes(&random_bytes);
......@@ -581,28 +565,6 @@ pub fn tmpDir(opts: std.fs.Dir.OpenDirOptions) TmpDir {
581565 };
582566}
583567
584pub fn tmpIterableDir(opts: std.fs.Dir.OpenDirOptions) TmpIterableDir {
585 var random_bytes: [TmpIterableDir.random_bytes_count]u8 = undefined;
586 std.crypto.random.bytes(&random_bytes);
587 var sub_path: [TmpIterableDir.sub_path_len]u8 = undefined;
588 _ = std.fs.base64_encoder.encode(&sub_path, &random_bytes);
589
590 const cwd = std.fs.cwd();
591 var cache_dir = cwd.makeOpenPath("zig-cache", .{}) catch
592 @panic("unable to make tmp dir for testing: unable to make and open zig-cache dir");
593 defer cache_dir.close();
594 const parent_dir = cache_dir.makeOpenPath("tmp", .{}) catch
595 @panic("unable to make tmp dir for testing: unable to make and open zig-cache/tmp dir");
596 const dir = parent_dir.makeOpenPathIterable(&sub_path, opts) catch
597 @panic("unable to make tmp dir for testing: unable to make and open the tmp dir");
598
599 return .{
600 .iterable_dir = dir,
601 .parent_dir = parent_dir,
602 .sub_path = sub_path,
603 };
604}
605
606568test "expectEqual nested array" {
607569 const a = [2][2]f32{
608570 [_]f32{ 1.0, 0.0 },
src/main.zig+6-6
......@@ -5698,13 +5698,13 @@ fn fmtPathDir(
56985698 parent_dir: fs.Dir,
56995699 parent_sub_path: []const u8,
57005700) FmtError!void {
5701 var iterable_dir = try parent_dir.openIterableDir(parent_sub_path, .{});
5702 defer iterable_dir.close();
5701 var dir = try parent_dir.openDir(parent_sub_path, .{ .iterate = true });
5702 defer dir.close();
57035703
5704 const stat = try iterable_dir.dir.stat();
5704 const stat = try dir.stat();
57055705 if (try fmt.seen.fetchPut(stat.inode, {})) |_| return;
57065706
5707 var dir_it = iterable_dir.iterate();
5707 var dir_it = dir.iterate();
57085708 while (try dir_it.next()) |entry| {
57095709 const is_dir = entry.kind == .directory;
57105710
......@@ -5715,9 +5715,9 @@ fn fmtPathDir(
57155715 defer fmt.gpa.free(full_path);
57165716
57175717 if (is_dir) {
5718 try fmtPathDir(fmt, full_path, check_mode, iterable_dir.dir, entry.name);
5718 try fmtPathDir(fmt, full_path, check_mode, dir, entry.name);
57195719 } else {
5720 fmtPathFile(fmt, full_path, check_mode, iterable_dir.dir, entry.name) catch |err| {
5720 fmtPathFile(fmt, full_path, check_mode, dir, entry.name) catch |err| {
57215721 warn("unable to format '{s}': {s}", .{ full_path, @errorName(err) });
57225722 fmt.any_error = true;
57235723 return;
tools/process_headers.zig+3-3
......@@ -382,14 +382,14 @@ pub fn main() !void {
382382 try dir_stack.append(target_include_dir);
383383
384384 while (dir_stack.popOrNull()) |full_dir_name| {
385 var iterable_dir = std.fs.cwd().openIterableDir(full_dir_name, .{}) catch |err| switch (err) {
385 var dir = std.fs.cwd().openDir(full_dir_name, .{ .iterate = true }) catch |err| switch (err) {
386386 error.FileNotFound => continue :search,
387387 error.AccessDenied => continue :search,
388388 else => return err,
389389 };
390 defer iterable_dir.close();
390 defer dir.close();
391391
392 var dir_it = iterable_dir.iterate();
392 var dir_it = dir.iterate();
393393
394394 while (try dir_it.next()) |entry| {
395395 const full_path = try std.fs.path.join(allocator, &[_][]const u8{ full_dir_name, entry.name });
tools/update-license-headers.zig+4-4
......@@ -14,9 +14,9 @@ pub fn main() !void {
1414
1515 const args = try std.process.argsAlloc(arena);
1616 const path_to_walk = args[1];
17 const iterable_dir = try std.fs.cwd().openIterableDir(path_to_walk, .{});
17 const dir = try std.fs.cwd().openDir(path_to_walk, .{ .iterate = true });
1818
19 var walker = try iterable_dir.walk(arena);
19 var walker = try dir.walk(arena);
2020 defer walker.deinit();
2121
2222 var buffer: [500]u8 = undefined;
......@@ -30,7 +30,7 @@ pub fn main() !void {
3030 node.activate();
3131 defer node.end();
3232
33 const source = try iterable_dir.dir.readFileAlloc(arena, entry.path, 20 * 1024 * 1024);
33 const source = try dir.readFileAlloc(arena, entry.path, 20 * 1024 * 1024);
3434 if (!std.mem.startsWith(u8, source, expected_header)) {
3535 std.debug.print("no match: {s}\n", .{entry.path});
3636 continue;
......@@ -42,6 +42,6 @@ pub fn main() !void {
4242 std.mem.copy(u8, new_source, new_header);
4343 std.mem.copy(u8, new_source[new_header.len..], truncated_source);
4444
45 try iterable_dir.dir.writeFile(entry.path, new_source);
45 try dir.writeFile(entry.path, new_source);
4646 }
4747}
tools/update-linux-headers.zig+3-3
......@@ -190,14 +190,14 @@ pub fn main() !void {
190190 try dir_stack.append(target_include_dir);
191191
192192 while (dir_stack.popOrNull()) |full_dir_name| {
193 var iterable_dir = std.fs.cwd().openIterableDir(full_dir_name, .{}) catch |err| switch (err) {
193 var dir = std.fs.cwd().openDir(full_dir_name, .{ .iterate = true }) catch |err| switch (err) {
194194 error.FileNotFound => continue :search,
195195 error.AccessDenied => continue :search,
196196 else => return err,
197197 };
198 defer iterable_dir.close();
198 defer dir.close();
199199
200 var dir_it = iterable_dir.iterate();
200 var dir_it = dir.iterate();
201201
202202 while (try dir_it.next()) |entry| {
203203 const full_path = try std.fs.path.join(arena, &[_][]const u8{ full_dir_name, entry.name });
tools/update_glibc.zig+5-5
......@@ -47,7 +47,7 @@ pub fn main() !void {
4747
4848 const dest_dir_path = try std.fmt.allocPrint(arena, "{s}/lib/libc/glibc", .{zig_src_path});
4949
50 var dest_dir = fs.cwd().openIterableDir(dest_dir_path, .{}) catch |err| {
50 var dest_dir = fs.cwd().openDir(dest_dir_path, .{ .iterate = true }) catch |err| {
5151 fatal("unable to open destination directory '{s}': {s}", .{
5252 dest_dir_path, @errorName(err),
5353 });
......@@ -72,14 +72,14 @@ pub fn main() !void {
7272 if (mem.endsWith(u8, entry.path, ext)) continue :walk;
7373 }
7474
75 glibc_src_dir.copyFile(entry.path, dest_dir.dir, entry.path, .{}) catch |err| {
75 glibc_src_dir.copyFile(entry.path, dest_dir, entry.path, .{}) catch |err| {
7676 log.warn("unable to copy '{s}/{s}' to '{s}/{s}': {s}", .{
7777 glibc_src_path, entry.path,
7878 dest_dir_path, entry.path,
7979 @errorName(err),
8080 });
8181 if (err == error.FileNotFound) {
82 try dest_dir.dir.deleteFile(entry.path);
82 try dest_dir.deleteFile(entry.path);
8383 }
8484 };
8585 }
......@@ -88,7 +88,7 @@ pub fn main() !void {
8888 // Warn about duplicated files inside glibc/include/* that can be omitted
8989 // because they are already in generic-glibc/*.
9090
91 var include_dir = dest_dir.dir.openIterableDir("include", .{}) catch |err| {
91 var include_dir = dest_dir.openDir("include", .{ .iterate = true }) catch |err| {
9292 fatal("unable to open directory '{s}/include': {s}", .{
9393 dest_dir_path, @errorName(err),
9494 });
......@@ -125,7 +125,7 @@ pub fn main() !void {
125125 generic_glibc_path, entry.path, @errorName(e),
126126 }),
127127 };
128 const glibc_include_contents = include_dir.dir.readFileAlloc(
128 const glibc_include_contents = include_dir.readFileAlloc(
129129 arena,
130130 entry.path,
131131 max_file_size,
tools/update_spirv_features.zig+3-3
......@@ -226,7 +226,7 @@ pub fn main() !void {
226226/// TODO: Unfortunately, neither repository contains a machine-readable list of extension dependencies.
227227fn gather_extensions(allocator: Allocator, spirv_registry_root: []const u8) ![]const []const u8 {
228228 const extensions_path = try fs.path.join(allocator, &.{ spirv_registry_root, "extensions" });
229 var extensions_dir = try fs.cwd().openIterableDir(extensions_path, .{});
229 var extensions_dir = try fs.cwd().openDir(extensions_path, .{ .iterate = true });
230230 defer extensions_dir.close();
231231
232232 var extensions = std.ArrayList([]const u8).init(allocator);
......@@ -235,7 +235,7 @@ fn gather_extensions(allocator: Allocator, spirv_registry_root: []const u8) ![]c
235235 while (try vendor_it.next()) |vendor_entry| {
236236 std.debug.assert(vendor_entry.kind == .directory); // If this fails, the structure of SPIRV-Registry has changed.
237237
238 const vendor_dir = try extensions_dir.dir.openIterableDir(vendor_entry.name, .{});
238 const vendor_dir = try extensions_dir.openDir(vendor_entry.name, .{ .iterate = true });
239239 var ext_it = vendor_dir.iterate();
240240 while (try ext_it.next()) |ext_entry| {
241241 // There is both a HTML and asciidoc version of every spec (as well as some other directories),
......@@ -258,7 +258,7 @@ fn gather_extensions(allocator: Allocator, spirv_registry_root: []const u8) ![]c
258258 // SPV_EXT_name
259259 // ```
260260
261 const ext_spec = try vendor_dir.dir.readFileAlloc(allocator, ext_entry.name, std.math.maxInt(usize));
261 const ext_spec = try vendor_dir.readFileAlloc(allocator, ext_entry.name, std.math.maxInt(usize));
262262 const name_strings = "Name Strings";
263263
264264 const name_strings_offset = std.mem.indexOf(u8, ext_spec, name_strings) orelse return error.InvalidRegistry;