Skip to content

Commit da94227

Browse files
authored
Merge pull request #12060 from Vexu/IterableDir
std.fs: split `Dir` into `IterableDir`
2 parents 8f943b3 + 262f4c7 commit da94227

File tree

12 files changed

+201
-126
lines changed

12 files changed

+201
-126
lines changed

lib/std/build.zig

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3245,7 +3245,7 @@ pub const LibExeObjStep = struct {
32453245
const build_output_dir = mem.trimRight(u8, output_dir_nl, "\r\n");
32463246

32473247
if (self.output_dir) |output_dir| {
3248-
var src_dir = try std.fs.cwd().openDir(build_output_dir, .{ .iterate = true });
3248+
var src_dir = try std.fs.cwd().openIterableDir(build_output_dir, .{});
32493249
defer src_dir.close();
32503250

32513251
// Create the output directory if it doesn't exist.
@@ -3265,7 +3265,7 @@ pub const LibExeObjStep = struct {
32653265
mem.eql(u8, entry.name, "zld.id") or
32663266
mem.eql(u8, entry.name, "lld.id")) continue;
32673267

3268-
_ = try src_dir.updateFile(entry.name, dest_dir, entry.name, .{});
3268+
_ = try src_dir.dir.updateFile(entry.name, dest_dir, entry.name, .{});
32693269
}
32703270
} else {
32713271
self.output_dir = build_output_dir;
@@ -3480,7 +3480,7 @@ pub const InstallDirStep = struct {
34803480
const self = @fieldParentPtr(InstallDirStep, "step", step);
34813481
const dest_prefix = self.builder.getInstallPath(self.options.install_dir, self.options.install_subdir);
34823482
const full_src_dir = self.builder.pathFromRoot(self.options.source_dir);
3483-
var src_dir = try std.fs.cwd().openDir(full_src_dir, .{ .iterate = true });
3483+
var src_dir = try std.fs.cwd().openIterableDir(full_src_dir, .{});
34843484
defer src_dir.close();
34853485
var it = try src_dir.walk(self.builder.allocator);
34863486
next_entry: while (try it.next()) |entry| {

lib/std/fs.zig

Lines changed: 99 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -283,8 +283,10 @@ pub fn renameW(old_dir: Dir, old_sub_path_w: []const u16, new_dir: Dir, new_sub_
283283
return os.renameatW(old_dir.fd, old_sub_path_w, new_dir.fd, new_sub_path_w);
284284
}
285285

286-
pub const Dir = struct {
287-
fd: os.fd_t,
286+
/// A directory that can be iterated. It is *NOT* legal to initialize this with a regular `Dir`
287+
/// that has been opened without iteration permission.
288+
pub const IterableDir = struct {
289+
dir: Dir,
288290

289291
pub const Entry = struct {
290292
name: []const u8,
@@ -779,7 +781,7 @@ pub const Dir = struct {
779781
else => @compileError("unimplemented"),
780782
};
781783

782-
pub fn iterate(self: Dir) Iterator {
784+
pub fn iterate(self: IterableDir) Iterator {
783785
switch (builtin.os.tag) {
784786
.macos,
785787
.ios,
@@ -789,30 +791,30 @@ pub const Dir = struct {
789791
.openbsd,
790792
.solaris,
791793
=> return Iterator{
792-
.dir = self,
794+
.dir = self.dir,
793795
.seek = 0,
794796
.index = 0,
795797
.end_index = 0,
796798
.buf = undefined,
797799
.first_iter = true,
798800
},
799801
.linux, .haiku => return Iterator{
800-
.dir = self,
802+
.dir = self.dir,
801803
.index = 0,
802804
.end_index = 0,
803805
.buf = undefined,
804806
.first_iter = true,
805807
},
806808
.windows => return Iterator{
807-
.dir = self,
809+
.dir = self.dir,
808810
.index = 0,
809811
.end_index = 0,
810812
.first_iter = true,
811813
.buf = undefined,
812814
.name_data = undefined,
813815
},
814816
.wasi => return Iterator{
815-
.dir = self,
817+
.dir = self.dir,
816818
.cookie = os.wasi.DIRCOOKIE_START,
817819
.index = 0,
818820
.end_index = 0,
@@ -833,11 +835,11 @@ pub const Dir = struct {
833835
dir: Dir,
834836
basename: []const u8,
835837
path: []const u8,
836-
kind: Dir.Entry.Kind,
838+
kind: IterableDir.Entry.Kind,
837839
};
838840

839841
const StackItem = struct {
840-
iter: Dir.Iterator,
842+
iter: IterableDir.Iterator,
841843
dirname_len: usize,
842844
};
843845

@@ -857,7 +859,7 @@ pub const Dir = struct {
857859
}
858860
try self.name_buffer.appendSlice(base.name);
859861
if (base.kind == .Directory) {
860-
var new_dir = top.iter.dir.openDir(base.name, .{ .iterate = true }) catch |err| switch (err) {
862+
var new_dir = top.iter.dir.openIterableDir(base.name, .{}) catch |err| switch (err) {
861863
error.NameTooLong => unreachable, // no path sep in base.name
862864
else => |e| return e,
863865
};
@@ -896,11 +898,10 @@ pub const Dir = struct {
896898
};
897899

898900
/// Recursively iterates over a directory.
899-
/// `self` must have been opened with `OpenDirOptions{.iterate = true}`.
900901
/// Must call `Walker.deinit` when done.
901902
/// The order of returned file system entries is undefined.
902903
/// `self` will not be closed after walking it.
903-
pub fn walk(self: Dir, allocator: Allocator) !Walker {
904+
pub fn walk(self: IterableDir, allocator: Allocator) !Walker {
904905
var name_buffer = std.ArrayList(u8).init(allocator);
905906
errdefer name_buffer.deinit();
906907

@@ -918,6 +919,49 @@ pub const Dir = struct {
918919
};
919920
}
920921

922+
pub fn close(self: *IterableDir) void {
923+
self.dir.close();
924+
self.* = undefined;
925+
}
926+
927+
pub const ChmodError = File.ChmodError;
928+
929+
/// Changes the mode of the directory.
930+
/// The process must have the correct privileges in order to do this
931+
/// successfully, or must have the effective user ID matching the owner
932+
/// of the directory.
933+
pub fn chmod(self: IterableDir, new_mode: File.Mode) ChmodError!void {
934+
const file: File = .{
935+
.handle = self.dir.fd,
936+
.capable_io_mode = .blocking,
937+
};
938+
try file.chmod(new_mode);
939+
}
940+
941+
/// Changes the owner and group of the directory.
942+
/// The process must have the correct privileges in order to do this
943+
/// successfully. The group may be changed by the owner of the directory to
944+
/// any group of which the owner is a member. If the
945+
/// owner or group is specified as `null`, the ID is not changed.
946+
pub fn chown(self: IterableDir, owner: ?File.Uid, group: ?File.Gid) ChownError!void {
947+
const file: File = .{
948+
.handle = self.dir.fd,
949+
.capable_io_mode = .blocking,
950+
};
951+
try file.chown(owner, group);
952+
}
953+
954+
pub const ChownError = File.ChownError;
955+
};
956+
957+
pub const Dir = struct {
958+
fd: os.fd_t,
959+
960+
pub const iterate = @compileError("only 'IterableDir' can be iterated; 'IterableDir' can be obtained with 'openIterableDir'");
961+
pub const walk = @compileError("only 'IterableDir' can be walked; 'IterableDir' can be obtained with 'openIterableDir'");
962+
pub const chmod = @compileError("only 'IterableDir' can have its mode changed; 'IterableDir' can be obtained with 'openIterableDir'");
963+
pub const chown = @compileError("only 'IterableDir' can have its owner changed; 'IterableDir' can be obtained with 'openIterableDir'");
964+
921965
pub const OpenError = error{
922966
FileNotFound,
923967
NotDir,
@@ -1334,6 +1378,15 @@ pub const Dir = struct {
13341378
return self.openDir(sub_path, open_dir_options);
13351379
}
13361380

1381+
/// This function performs `makePath`, followed by `openIterableDir`.
1382+
/// If supported by the OS, this operation is atomic. It is not atomic on
1383+
/// all operating systems.
1384+
pub fn makeOpenPathIterable(self: Dir, sub_path: []const u8, open_dir_options: OpenDirOptions) !IterableDir {
1385+
// TODO improve this implementation on Windows; we can avoid 1 call to NtClose
1386+
try self.makePath(sub_path);
1387+
return self.openIterableDir(sub_path, open_dir_options);
1388+
}
1389+
13371390
/// This function returns the canonicalized absolute pathname of
13381391
/// `pathname` relative to this `Dir`. If `pathname` is absolute, ignores this
13391392
/// `Dir` handle and returns the canonicalized absolute pathname of `pathname`
@@ -1483,10 +1536,6 @@ pub const Dir = struct {
14831536
/// such operations are Illegal Behavior.
14841537
access_sub_paths: bool = true,
14851538

1486-
/// `true` means the opened directory can be scanned for the files and sub-directories
1487-
/// of the result. It means the `iterate` function can be called.
1488-
iterate: bool = false,
1489-
14901539
/// `true` means it won't dereference the symlinks.
14911540
no_follow: bool = false,
14921541
};
@@ -1498,12 +1547,28 @@ pub const Dir = struct {
14981547
pub fn openDir(self: Dir, sub_path: []const u8, args: OpenDirOptions) OpenError!Dir {
14991548
if (builtin.os.tag == .windows) {
15001549
const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path);
1501-
return self.openDirW(sub_path_w.span().ptr, args);
1550+
return self.openDirW(sub_path_w.span().ptr, args, false);
15021551
} else if (builtin.os.tag == .wasi and !builtin.link_libc) {
15031552
return self.openDirWasi(sub_path, args);
15041553
} else {
15051554
const sub_path_c = try os.toPosixPath(sub_path);
1506-
return self.openDirZ(&sub_path_c, args);
1555+
return self.openDirZ(&sub_path_c, args, false);
1556+
}
1557+
}
1558+
1559+
/// Opens an iterable directory at the given path. The directory is a system resource that remains
1560+
/// open until `close` is called on the result.
1561+
///
1562+
/// Asserts that the path parameter has no null bytes.
1563+
pub fn openIterableDir(self: Dir, sub_path: []const u8, args: OpenDirOptions) OpenError!IterableDir {
1564+
if (builtin.os.tag == .windows) {
1565+
const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path);
1566+
return IterableDir{ .dir = try self.openDirW(sub_path_w.span().ptr, args, true) };
1567+
} else if (builtin.os.tag == .wasi and !builtin.link_libc) {
1568+
return IterableDir{ .dir = try self.openDirWasi(sub_path, args) };
1569+
} else {
1570+
const sub_path_c = try os.toPosixPath(sub_path);
1571+
return IterableDir{ .dir = try self.openDirZ(&sub_path_c, args, true) };
15071572
}
15081573
}
15091574

@@ -1556,13 +1621,13 @@ pub const Dir = struct {
15561621
}
15571622

15581623
/// Same as `openDir` except the parameter is null-terminated.
1559-
pub fn openDirZ(self: Dir, sub_path_c: [*:0]const u8, args: OpenDirOptions) OpenError!Dir {
1624+
pub fn openDirZ(self: Dir, sub_path_c: [*:0]const u8, args: OpenDirOptions, iterable: bool) OpenError!Dir {
15601625
if (builtin.os.tag == .windows) {
15611626
const sub_path_w = try os.windows.cStrToPrefixedFileW(sub_path_c);
15621627
return self.openDirW(sub_path_w.span().ptr, args);
15631628
}
15641629
const symlink_flags: u32 = if (args.no_follow) os.O.NOFOLLOW else 0x0;
1565-
if (!args.iterate) {
1630+
if (!iterable) {
15661631
const O_PATH = if (@hasDecl(os.O, "PATH")) os.O.PATH else 0;
15671632
return self.openDirFlagsZ(sub_path_c, os.O.DIRECTORY | os.O.RDONLY | os.O.CLOEXEC | O_PATH | symlink_flags);
15681633
} else {
@@ -1572,13 +1637,14 @@ pub const Dir = struct {
15721637

15731638
/// Same as `openDir` except the path parameter is WTF-16 encoded, NT-prefixed.
15741639
/// This function asserts the target OS is Windows.
1575-
pub fn openDirW(self: Dir, sub_path_w: [*:0]const u16, args: OpenDirOptions) OpenError!Dir {
1640+
pub fn openDirW(self: Dir, sub_path_w: [*:0]const u16, args: OpenDirOptions, iterable: bool) OpenError!Dir {
15761641
const w = os.windows;
15771642
// TODO remove some of these flags if args.access_sub_paths is false
15781643
const base_flags = w.STANDARD_RIGHTS_READ | w.FILE_READ_ATTRIBUTES | w.FILE_READ_EA |
15791644
w.SYNCHRONIZE | w.FILE_TRAVERSE;
1580-
const flags: u32 = if (args.iterate) base_flags | w.FILE_LIST_DIRECTORY else base_flags;
1581-
return self.openDirAccessMaskW(sub_path_w, flags, args.no_follow);
1645+
const flags: u32 = if (iterable) base_flags | w.FILE_LIST_DIRECTORY else base_flags;
1646+
var dir = try self.openDirAccessMaskW(sub_path_w, flags, args.no_follow);
1647+
return dir;
15821648
}
15831649

15841650
/// `flags` must contain `os.O.DIRECTORY`.
@@ -1958,7 +2024,7 @@ pub const Dir = struct {
19582024
error.Unexpected,
19592025
=> |e| return e,
19602026
}
1961-
var dir = self.openDir(sub_path, .{ .iterate = true, .no_follow = true }) catch |err| switch (err) {
2027+
var iterable_dir = self.openIterableDir(sub_path, .{ .no_follow = true }) catch |err| switch (err) {
19622028
error.NotDir => {
19632029
if (got_access_denied) {
19642030
return error.AccessDenied;
@@ -1984,11 +2050,11 @@ pub const Dir = struct {
19842050
error.DeviceBusy,
19852051
=> |e| return e,
19862052
};
1987-
var cleanup_dir_parent: ?Dir = null;
2053+
var cleanup_dir_parent: ?IterableDir = null;
19882054
defer if (cleanup_dir_parent) |*d| d.close();
19892055

19902056
var cleanup_dir = true;
1991-
defer if (cleanup_dir) dir.close();
2057+
defer if (cleanup_dir) iterable_dir.close();
19922058

19932059
// Valid use of MAX_PATH_BYTES because dir_name_buf will only
19942060
// ever store a single path component that was returned from the
@@ -2001,9 +2067,9 @@ pub const Dir = struct {
20012067
// open it, and close the original directory. Repeat. Then start the entire operation over.
20022068

20032069
scan_dir: while (true) {
2004-
var dir_it = dir.iterate();
2070+
var dir_it = iterable_dir.iterate();
20052071
while (try dir_it.next()) |entry| {
2006-
if (dir.deleteFile(entry.name)) {
2072+
if (iterable_dir.dir.deleteFile(entry.name)) {
20072073
continue;
20082074
} else |err| switch (err) {
20092075
error.FileNotFound => continue,
@@ -2026,7 +2092,7 @@ pub const Dir = struct {
20262092
=> |e| return e,
20272093
}
20282094

2029-
const new_dir = dir.openDir(entry.name, .{ .iterate = true, .no_follow = true }) catch |err| switch (err) {
2095+
const new_dir = iterable_dir.dir.openIterableDir(entry.name, .{ .no_follow = true }) catch |err| switch (err) {
20302096
error.NotDir => {
20312097
if (got_access_denied) {
20322098
return error.AccessDenied;
@@ -2053,19 +2119,19 @@ pub const Dir = struct {
20532119
=> |e| return e,
20542120
};
20552121
if (cleanup_dir_parent) |*d| d.close();
2056-
cleanup_dir_parent = dir;
2057-
dir = new_dir;
2122+
cleanup_dir_parent = iterable_dir;
2123+
iterable_dir = new_dir;
20582124
mem.copy(u8, &dir_name_buf, entry.name);
20592125
dir_name = dir_name_buf[0..entry.name.len];
20602126
continue :scan_dir;
20612127
}
20622128
// Reached the end of the directory entries, which means we successfully deleted all of them.
20632129
// Now to remove the directory itself.
2064-
dir.close();
2130+
iterable_dir.close();
20652131
cleanup_dir = false;
20662132

20672133
if (cleanup_dir_parent) |d| {
2068-
d.deleteDir(dir_name) catch |err| switch (err) {
2134+
d.dir.deleteDir(dir_name) catch |err| switch (err) {
20692135
// These two things can happen due to file system race conditions.
20702136
error.FileNotFound, error.DirNotEmpty => continue :start_over,
20712137
else => |e| return e,
@@ -2246,37 +2312,6 @@ pub const Dir = struct {
22462312
return file.stat();
22472313
}
22482314

2249-
pub const ChmodError = File.ChmodError;
2250-
2251-
/// Changes the mode of the directory.
2252-
/// The process must have the correct privileges in order to do this
2253-
/// successfully, or must have the effective user ID matching the owner
2254-
/// of the directory. Additionally, the directory must have been opened
2255-
/// with `OpenDirOptions{ .iterate = true }`.
2256-
pub fn chmod(self: Dir, new_mode: File.Mode) ChmodError!void {
2257-
const file: File = .{
2258-
.handle = self.fd,
2259-
.capable_io_mode = .blocking,
2260-
};
2261-
try file.chmod(new_mode);
2262-
}
2263-
2264-
/// Changes the owner and group of the directory.
2265-
/// The process must have the correct privileges in order to do this
2266-
/// successfully. The group may be changed by the owner of the directory to
2267-
/// any group of which the owner is a member. Additionally, the directory
2268-
/// must have been opened with `OpenDirOptions{ .iterate = true }`. If the
2269-
/// owner or group is specified as `null`, the ID is not changed.
2270-
pub fn chown(self: Dir, owner: ?File.Uid, group: ?File.Gid) ChownError!void {
2271-
const file: File = .{
2272-
.handle = self.fd,
2273-
.capable_io_mode = .blocking,
2274-
};
2275-
try file.chown(owner, group);
2276-
}
2277-
2278-
pub const ChownError = File.ChownError;
2279-
22802315
const Permissions = File.Permissions;
22812316
pub const SetPermissionsError = File.SetPermissionsError;
22822317

0 commit comments

Comments
 (0)