Skip to content

Commit 2b67f56

Browse files
committed
std.fs: split Dir into IterableDir
Also adds safety check for attempting to iterate directory not opened with `iterate = true`.
1 parent 577f9fd commit 2b67f56

File tree

10 files changed

+145
-104
lines changed

10 files changed

+145
-104
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: 96 additions & 56 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,52 @@ 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+
iterable: @TypeOf(iterable_safety) = iterable_safety,
960+
961+
const iterable_safety = if (builtin.mode == .Debug) false else {};
962+
963+
pub const iterate = @compileError("only 'IterableDir' can be iterated; 'IterableDir' can be obtained with 'openIterableDir' or by opening with 'iterate = true' and using 'intoIterable'");
964+
pub const walk = @compileError("only 'IterableDir' can be walked; 'IterableDir' can be obtained with 'openIterableDir' or by opening with 'iterate = true' and using 'intoIterable'");
965+
pub const chmod = @compileError("only 'IterableDir' can have its mode changed; 'IterableDir' can be obtained with 'openIterableDir' or by opening with 'iterate = true' and using 'intoIterable'");
966+
pub const chown = @compileError("only 'IterableDir' can have its owner changed; 'IterableDir' can be obtained with 'openIterableDir' or by opening with 'iterate = true' and using 'intoIterable'");
967+
921968
pub const OpenError = error{
922969
FileNotFound,
923970
NotDir,
@@ -1507,6 +1554,26 @@ pub const Dir = struct {
15071554
}
15081555
}
15091556

1557+
/// Opens an iterable directory at the given path. The directory is a system resource that remains
1558+
/// open until `close` is called on the result.
1559+
///
1560+
/// Asserts that the path parameter has no null bytes.
1561+
pub fn openIterableDir(self: Dir, sub_path: []const u8, args: OpenDirOptions) OpenError!IterableDir {
1562+
var adjusted_args = args;
1563+
adjusted_args.iterate = true;
1564+
const new_dir = try self.openDir(sub_path, adjusted_args);
1565+
return IterableDir{ .dir = new_dir };
1566+
}
1567+
1568+
/// Convert `self` into an iterable directory.
1569+
/// Asserts that `self` was opened with `iterate = true`.
1570+
pub fn intoIterable(self: Dir) IterableDir {
1571+
if (builtin.mode == .Debug) {
1572+
assert(self.iterable);
1573+
}
1574+
return .{ .dir = self };
1575+
}
1576+
15101577
/// Same as `openDir` except only WASI.
15111578
pub fn openDirWasi(self: Dir, sub_path: []const u8, args: OpenDirOptions) OpenError!Dir {
15121579
const w = os.wasi;
@@ -1552,7 +1619,7 @@ pub const Dir = struct {
15521619
error.FileBusy => unreachable, // can't happen for directories
15531620
else => |e| return e,
15541621
};
1555-
return Dir{ .fd = fd };
1622+
return Dir{ .fd = fd, .iterable = if (builtin.mode == .Debug) args.iterate else {} };
15561623
}
15571624

15581625
/// Same as `openDir` except the parameter is null-terminated.
@@ -1566,7 +1633,9 @@ pub const Dir = struct {
15661633
const O_PATH = if (@hasDecl(os.O, "PATH")) os.O.PATH else 0;
15671634
return self.openDirFlagsZ(sub_path_c, os.O.DIRECTORY | os.O.RDONLY | os.O.CLOEXEC | O_PATH | symlink_flags);
15681635
} else {
1569-
return self.openDirFlagsZ(sub_path_c, os.O.DIRECTORY | os.O.RDONLY | os.O.CLOEXEC | symlink_flags);
1636+
var dir = try self.openDirFlagsZ(sub_path_c, os.O.DIRECTORY | os.O.RDONLY | os.O.CLOEXEC | symlink_flags);
1637+
if (builtin.mode == .Debug) dir.iterable = true;
1638+
return dir;
15701639
}
15711640
}
15721641

@@ -1578,7 +1647,9 @@ pub const Dir = struct {
15781647
const base_flags = w.STANDARD_RIGHTS_READ | w.FILE_READ_ATTRIBUTES | w.FILE_READ_EA |
15791648
w.SYNCHRONIZE | w.FILE_TRAVERSE;
15801649
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);
1650+
var dir = try self.openDirAccessMaskW(sub_path_w, flags, args.no_follow);
1651+
if (builtin.mode == .Debug) dir.iterable = args.iterate;
1652+
return dir;
15821653
}
15831654

15841655
/// `flags` must contain `os.O.DIRECTORY`.
@@ -1958,7 +2029,7 @@ pub const Dir = struct {
19582029
error.Unexpected,
19592030
=> |e| return e,
19602031
}
1961-
var dir = self.openDir(sub_path, .{ .iterate = true, .no_follow = true }) catch |err| switch (err) {
2032+
var iterable_dir = self.openIterableDir(sub_path, .{ .no_follow = true }) catch |err| switch (err) {
19622033
error.NotDir => {
19632034
if (got_access_denied) {
19642035
return error.AccessDenied;
@@ -1984,11 +2055,11 @@ pub const Dir = struct {
19842055
error.DeviceBusy,
19852056
=> |e| return e,
19862057
};
1987-
var cleanup_dir_parent: ?Dir = null;
2058+
var cleanup_dir_parent: ?IterableDir = null;
19882059
defer if (cleanup_dir_parent) |*d| d.close();
19892060

19902061
var cleanup_dir = true;
1991-
defer if (cleanup_dir) dir.close();
2062+
defer if (cleanup_dir) iterable_dir.close();
19922063

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

20032074
scan_dir: while (true) {
2004-
var dir_it = dir.iterate();
2075+
var dir_it = iterable_dir.iterate();
20052076
while (try dir_it.next()) |entry| {
2006-
if (dir.deleteFile(entry.name)) {
2077+
if (iterable_dir.dir.deleteFile(entry.name)) {
20072078
continue;
20082079
} else |err| switch (err) {
20092080
error.FileNotFound => continue,
@@ -2026,7 +2097,7 @@ pub const Dir = struct {
20262097
=> |e| return e,
20272098
}
20282099

2029-
const new_dir = dir.openDir(entry.name, .{ .iterate = true, .no_follow = true }) catch |err| switch (err) {
2100+
const new_dir = iterable_dir.dir.openIterableDir(entry.name, .{ .no_follow = true }) catch |err| switch (err) {
20302101
error.NotDir => {
20312102
if (got_access_denied) {
20322103
return error.AccessDenied;
@@ -2053,19 +2124,19 @@ pub const Dir = struct {
20532124
=> |e| return e,
20542125
};
20552126
if (cleanup_dir_parent) |*d| d.close();
2056-
cleanup_dir_parent = dir;
2057-
dir = new_dir;
2127+
cleanup_dir_parent = iterable_dir;
2128+
iterable_dir = new_dir;
20582129
mem.copy(u8, &dir_name_buf, entry.name);
20592130
dir_name = dir_name_buf[0..entry.name.len];
20602131
continue :scan_dir;
20612132
}
20622133
// Reached the end of the directory entries, which means we successfully deleted all of them.
20632134
// Now to remove the directory itself.
2064-
dir.close();
2135+
iterable_dir.close();
20652136
cleanup_dir = false;
20662137

20672138
if (cleanup_dir_parent) |d| {
2068-
d.deleteDir(dir_name) catch |err| switch (err) {
2139+
d.dir.deleteDir(dir_name) catch |err| switch (err) {
20692140
// These two things can happen due to file system race conditions.
20702141
error.FileNotFound, error.DirNotEmpty => continue :start_over,
20712142
else => |e| return e,
@@ -2246,37 +2317,6 @@ pub const Dir = struct {
22462317
return file.stat();
22472318
}
22482319

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-
22802320
const Permissions = File.Permissions;
22812321
pub const SetPermissionsError = File.SetPermissionsError;
22822322

0 commit comments

Comments
 (0)