Skip to content

Commit 262f4c7

Browse files
committed
std.fs: remove OpenDirOptions.iterate
1 parent 2b67f56 commit 262f4c7

File tree

5 files changed

+88
-54
lines changed

5 files changed

+88
-54
lines changed

lib/std/fs.zig

Lines changed: 29 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -956,14 +956,11 @@ pub const IterableDir = struct {
956956

957957
pub const Dir = struct {
958958
fd: os.fd_t,
959-
iterable: @TypeOf(iterable_safety) = iterable_safety,
960959

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'");
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'");
967964

968965
pub const OpenError = error{
969966
FileNotFound,
@@ -1381,6 +1378,15 @@ pub const Dir = struct {
13811378
return self.openDir(sub_path, open_dir_options);
13821379
}
13831380

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+
13841390
/// This function returns the canonicalized absolute pathname of
13851391
/// `pathname` relative to this `Dir`. If `pathname` is absolute, ignores this
13861392
/// `Dir` handle and returns the canonicalized absolute pathname of `pathname`
@@ -1530,10 +1536,6 @@ pub const Dir = struct {
15301536
/// such operations are Illegal Behavior.
15311537
access_sub_paths: bool = true,
15321538

1533-
/// `true` means the opened directory can be scanned for the files and sub-directories
1534-
/// of the result. It means the `iterate` function can be called.
1535-
iterate: bool = false,
1536-
15371539
/// `true` means it won't dereference the symlinks.
15381540
no_follow: bool = false,
15391541
};
@@ -1545,12 +1547,12 @@ pub const Dir = struct {
15451547
pub fn openDir(self: Dir, sub_path: []const u8, args: OpenDirOptions) OpenError!Dir {
15461548
if (builtin.os.tag == .windows) {
15471549
const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path);
1548-
return self.openDirW(sub_path_w.span().ptr, args);
1550+
return self.openDirW(sub_path_w.span().ptr, args, false);
15491551
} else if (builtin.os.tag == .wasi and !builtin.link_libc) {
15501552
return self.openDirWasi(sub_path, args);
15511553
} else {
15521554
const sub_path_c = try os.toPosixPath(sub_path);
1553-
return self.openDirZ(&sub_path_c, args);
1555+
return self.openDirZ(&sub_path_c, args, false);
15541556
}
15551557
}
15561558

@@ -1559,19 +1561,15 @@ pub const Dir = struct {
15591561
///
15601562
/// Asserts that the path parameter has no null bytes.
15611563
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);
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) };
15731572
}
1574-
return .{ .dir = self };
15751573
}
15761574

15771575
/// Same as `openDir` except only WASI.
@@ -1619,36 +1617,33 @@ pub const Dir = struct {
16191617
error.FileBusy => unreachable, // can't happen for directories
16201618
else => |e| return e,
16211619
};
1622-
return Dir{ .fd = fd, .iterable = if (builtin.mode == .Debug) args.iterate else {} };
1620+
return Dir{ .fd = fd };
16231621
}
16241622

16251623
/// Same as `openDir` except the parameter is null-terminated.
1626-
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 {
16271625
if (builtin.os.tag == .windows) {
16281626
const sub_path_w = try os.windows.cStrToPrefixedFileW(sub_path_c);
16291627
return self.openDirW(sub_path_w.span().ptr, args);
16301628
}
16311629
const symlink_flags: u32 = if (args.no_follow) os.O.NOFOLLOW else 0x0;
1632-
if (!args.iterate) {
1630+
if (!iterable) {
16331631
const O_PATH = if (@hasDecl(os.O, "PATH")) os.O.PATH else 0;
16341632
return self.openDirFlagsZ(sub_path_c, os.O.DIRECTORY | os.O.RDONLY | os.O.CLOEXEC | O_PATH | symlink_flags);
16351633
} else {
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;
1634+
return self.openDirFlagsZ(sub_path_c, os.O.DIRECTORY | os.O.RDONLY | os.O.CLOEXEC | symlink_flags);
16391635
}
16401636
}
16411637

16421638
/// Same as `openDir` except the path parameter is WTF-16 encoded, NT-prefixed.
16431639
/// This function asserts the target OS is Windows.
1644-
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 {
16451641
const w = os.windows;
16461642
// TODO remove some of these flags if args.access_sub_paths is false
16471643
const base_flags = w.STANDARD_RIGHTS_READ | w.FILE_READ_ATTRIBUTES | w.FILE_READ_EA |
16481644
w.SYNCHRONIZE | w.FILE_TRAVERSE;
1649-
const flags: u32 = if (args.iterate) base_flags | w.FILE_LIST_DIRECTORY else base_flags;
1645+
const flags: u32 = if (iterable) base_flags | w.FILE_LIST_DIRECTORY else base_flags;
16501646
var dir = try self.openDirAccessMaskW(sub_path_w, flags, args.no_follow);
1651-
if (builtin.mode == .Debug) dir.iterable = args.iterate;
16521647
return dir;
16531648
}
16541649

lib/std/fs/test.zig

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ const Dir = std.fs.Dir;
1111
const IterableDir = std.fs.IterableDir;
1212
const File = std.fs.File;
1313
const tmpDir = testing.tmpDir;
14+
const tmpIterableDir = testing.tmpIterableDir;
1415

1516
test "Dir.readLink" {
1617
var tmp = tmpDir(.{});
@@ -156,14 +157,14 @@ fn testReadLinkAbsolute(target_path: []const u8, symlink_path: []const u8) !void
156157
}
157158

158159
test "Dir.Iterator" {
159-
var tmp_dir = tmpDir(.{ .iterate = true });
160+
var tmp_dir = tmpIterableDir(.{});
160161
defer tmp_dir.cleanup();
161162

162163
// First, create a couple of entries to iterate over.
163-
const file = try tmp_dir.dir.createFile("some_file", .{});
164+
const file = try tmp_dir.iterable_dir.dir.createFile("some_file", .{});
164165
file.close();
165166

166-
try tmp_dir.dir.makeDir("some_dir");
167+
try tmp_dir.iterable_dir.dir.makeDir("some_dir");
167168

168169
var arena = ArenaAllocator.init(testing.allocator);
169170
defer arena.deinit();
@@ -172,7 +173,7 @@ test "Dir.Iterator" {
172173
var entries = std.ArrayList(IterableDir.Entry).init(allocator);
173174

174175
// Create iterator.
175-
var iter = tmp_dir.dir.intoIterable().iterate();
176+
var iter = tmp_dir.iterable_dir.iterate();
176177
while (try iter.next()) |entry| {
177178
// We cannot just store `entry` as on Windows, we're re-using the name buffer
178179
// which means we'll actually share the `name` pointer between entries!
@@ -186,14 +187,14 @@ test "Dir.Iterator" {
186187
}
187188

188189
test "Dir.Iterator twice" {
189-
var tmp_dir = tmpDir(.{ .iterate = true });
190+
var tmp_dir = tmpIterableDir(.{});
190191
defer tmp_dir.cleanup();
191192

192193
// First, create a couple of entries to iterate over.
193-
const file = try tmp_dir.dir.createFile("some_file", .{});
194+
const file = try tmp_dir.iterable_dir.dir.createFile("some_file", .{});
194195
file.close();
195196

196-
try tmp_dir.dir.makeDir("some_dir");
197+
try tmp_dir.iterable_dir.dir.makeDir("some_dir");
197198

198199
var arena = ArenaAllocator.init(testing.allocator);
199200
defer arena.deinit();
@@ -204,7 +205,7 @@ test "Dir.Iterator twice" {
204205
var entries = std.ArrayList(IterableDir.Entry).init(allocator);
205206

206207
// Create iterator.
207-
var iter = tmp_dir.dir.intoIterable().iterate();
208+
var iter = tmp_dir.iterable_dir.iterate();
208209
while (try iter.next()) |entry| {
209210
// We cannot just store `entry` as on Windows, we're re-using the name buffer
210211
// which means we'll actually share the `name` pointer between entries!
@@ -986,7 +987,7 @@ test "walker" {
986987
if (builtin.os.tag == .wasi and builtin.link_libc) return error.SkipZigTest;
987988
if (builtin.os.tag == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "/");
988989

989-
var tmp = tmpDir(.{ .iterate = true });
990+
var tmp = tmpIterableDir(.{});
990991
defer tmp.cleanup();
991992

992993
// iteration order of walker is undefined, so need lookup maps to check against
@@ -1012,10 +1013,10 @@ test "walker" {
10121013
});
10131014

10141015
for (expected_paths.kvs) |kv| {
1015-
try tmp.dir.makePath(kv.key);
1016+
try tmp.iterable_dir.dir.makePath(kv.key);
10161017
}
10171018

1018-
var walker = try tmp.dir.intoIterable().walk(testing.allocator);
1019+
var walker = try tmp.iterable_dir.walk(testing.allocator);
10191020
defer walker.deinit();
10201021

10211022
var num_walked: usize = 0;
@@ -1126,7 +1127,7 @@ test "chmod" {
11261127
defer iterable_dir.close();
11271128

11281129
try iterable_dir.chmod(0o700);
1129-
try testing.expect((try iterable_dir.stat()).mode & 0o7777 == 0o700);
1130+
try testing.expect((try iterable_dir.dir.stat()).mode & 0o7777 == 0o700);
11301131
}
11311132

11321133
test "chown" {
@@ -1142,7 +1143,7 @@ test "chown" {
11421143

11431144
try tmp.dir.makeDir("test_dir");
11441145

1145-
var iterable_dir = try tmp.dir.openDir("test_dir", .{});
1146+
var iterable_dir = try tmp.dir.openIterableDir("test_dir", .{});
11461147
defer iterable_dir.close();
11471148
try iterable_dir.chown(null, null);
11481149
}

lib/std/os.zig

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,7 @@ pub fn fchmod(fd: fd_t, mode: mode_t) FChmodError!void {
308308
switch (system.getErrno(res)) {
309309
.SUCCESS => return,
310310
.INTR => continue,
311-
.BADF => unreachable, // Can be reached if the fd refers to a directory opened without `OpenDirOptions{ .iterate = true }`
311+
.BADF => unreachable, // Can be reached if the fd refers to a non-iterable directory.
312312

313313
.FAULT => unreachable,
314314
.INVAL => unreachable,
@@ -349,7 +349,7 @@ pub fn fchown(fd: fd_t, owner: ?uid_t, group: ?gid_t) FChownError!void {
349349
switch (system.getErrno(res)) {
350350
.SUCCESS => return,
351351
.INTR => continue,
352-
.BADF => unreachable, // Can be reached if the fd refers to a directory opened without `OpenDirOptions{ .iterate = true }`
352+
.BADF => unreachable, // Can be reached if the fd refers to a non-iterable directory.
353353

354354
.FAULT => unreachable,
355355
.INVAL => unreachable,

lib/std/testing.zig

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,22 @@ pub const TmpDir = struct {
363363
}
364364
};
365365

366+
pub const TmpIterableDir = struct {
367+
iterable_dir: std.fs.IterableDir,
368+
parent_dir: std.fs.Dir,
369+
sub_path: [sub_path_len]u8,
370+
371+
const random_bytes_count = 12;
372+
const sub_path_len = std.fs.base64_encoder.calcSize(random_bytes_count);
373+
374+
pub fn cleanup(self: *TmpIterableDir) void {
375+
self.iterable_dir.close();
376+
self.parent_dir.deleteTree(&self.sub_path) catch {};
377+
self.parent_dir.close();
378+
self.* = undefined;
379+
}
380+
};
381+
366382
fn getCwdOrWasiPreopen() std.fs.Dir {
367383
if (builtin.os.tag == .wasi and !builtin.link_libc) {
368384
var preopens = std.fs.wasi.PreopenList.init(allocator);
@@ -400,6 +416,28 @@ pub fn tmpDir(opts: std.fs.Dir.OpenDirOptions) TmpDir {
400416
};
401417
}
402418

419+
pub fn tmpIterableDir(opts: std.fs.Dir.OpenDirOptions) TmpIterableDir {
420+
var random_bytes: [TmpIterableDir.random_bytes_count]u8 = undefined;
421+
std.crypto.random.bytes(&random_bytes);
422+
var sub_path: [TmpIterableDir.sub_path_len]u8 = undefined;
423+
_ = std.fs.base64_encoder.encode(&sub_path, &random_bytes);
424+
425+
var cwd = getCwdOrWasiPreopen();
426+
var cache_dir = cwd.makeOpenPath("zig-cache", .{}) catch
427+
@panic("unable to make tmp dir for testing: unable to make and open zig-cache dir");
428+
defer cache_dir.close();
429+
var parent_dir = cache_dir.makeOpenPath("tmp", .{}) catch
430+
@panic("unable to make tmp dir for testing: unable to make and open zig-cache/tmp dir");
431+
var dir = parent_dir.makeOpenPathIterable(&sub_path, opts) catch
432+
@panic("unable to make tmp dir for testing: unable to make and open the tmp dir");
433+
434+
return .{
435+
.iterable_dir = dir,
436+
.parent_dir = parent_dir,
437+
.sub_path = sub_path,
438+
};
439+
}
440+
403441
test "expectEqual nested array" {
404442
const a = [2][2]f32{
405443
[_]f32{ 1.0, 0.0 },

src/test.zig

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ test {
5454
std.fs.path.dirname(@src().file).?, "..", "test", "cases",
5555
});
5656

57-
var dir = try std.fs.cwd().openDir(dir_path, .{ .iterate = true });
57+
var dir = try std.fs.cwd().openIterableDir(dir_path, .{});
5858
defer dir.close();
5959

6060
ctx.addTestCasesFromDir(dir);
@@ -1080,7 +1080,7 @@ pub const TestContext = struct {
10801080
/// Each file should include a test manifest as a contiguous block of comments at
10811081
/// the end of the file. The first line should be the test type, followed by a set of
10821082
/// key-value config values, followed by a blank line, then the expected output.
1083-
pub fn addTestCasesFromDir(ctx: *TestContext, dir: std.fs.Dir) void {
1083+
pub fn addTestCasesFromDir(ctx: *TestContext, dir: std.fs.IterableDir) void {
10841084
var current_file: []const u8 = "none";
10851085
ctx.addTestCasesFromDirInner(dir, &current_file) catch |err| {
10861086
std.debug.panic("test harness failed to process file '{s}': {s}\n", .{
@@ -1091,12 +1091,12 @@ pub const TestContext = struct {
10911091

10921092
fn addTestCasesFromDirInner(
10931093
ctx: *TestContext,
1094-
dir: std.fs.Dir,
1094+
iterable_dir: std.fs.IterableDir,
10951095
/// This is kept up to date with the currently being processed file so
10961096
/// that if any errors occur the caller knows it happened during this file.
10971097
current_file: *[]const u8,
10981098
) !void {
1099-
var it = try dir.intoIterable().walk(ctx.arena);
1099+
var it = try iterable_dir.walk(ctx.arena);
11001100
var filenames = std.ArrayList([]const u8).init(ctx.arena);
11011101

11021102
while (try it.next()) |entry| {
@@ -1123,7 +1123,7 @@ pub const TestContext = struct {
11231123
current_file.* = filename;
11241124

11251125
const max_file_size = 10 * 1024 * 1024;
1126-
const src = try dir.readFileAllocOptions(ctx.arena, filename, max_file_size, null, 1, 0);
1126+
const src = try iterable_dir.dir.readFileAllocOptions(ctx.arena, filename, max_file_size, null, 1, 0);
11271127

11281128
// Parse the manifest
11291129
var manifest = try TestManifest.parse(ctx.arena, src);

0 commit comments

Comments
 (0)