Skip to content

Commit 13ab298

Browse files
authored
io: implement dirCreateDirPath and dirCreateDirPathOpen (#381)
* io: implement dirCreateDirPath and dirCreateDirPathOpen Creates nested directory paths by iterating from the target back to existing ancestors, then forward creating each component. Follows the same algorithm as std.Io.Threaded. Includes tests for both functions. * io: simplify error handling with try instead of catch return * io: fix createDirPath tests for Windows by using platform separator Windows paths with \\?\ prefix don't auto-translate forward slashes, causing INVALID_NAME errors. Use Io.Dir.path.sep_str for portable paths.
1 parent 3dc1f32 commit 13ab298

1 file changed

Lines changed: 103 additions & 4 deletions

File tree

src/io.zig

Lines changed: 103 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -464,12 +464,45 @@ fn resolveSetTimestamp(t: Io.File.SetTimestamp) ?i96 {
464464
};
465465
}
466466

467-
fn dirCreateDirPathImpl(_: ?*anyopaque, _: Io.Dir, _: []const u8, _: Io.Dir.Permissions) Io.Dir.CreateDirPathError!Io.Dir.CreatePathStatus {
468-
@panic("TODO: dirCreateDirPath");
467+
fn dirCreateDirPathImpl(_: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, permissions: Io.Dir.Permissions) Io.Dir.CreateDirPathError!Io.Dir.CreatePathStatus {
468+
var it = Io.Dir.path.componentIterator(sub_path);
469+
var status: Io.Dir.CreatePathStatus = .existed;
470+
var component = it.last() orelse return error.BadPathName;
471+
while (true) {
472+
var op = ev.DirCreateDir.init(stdIoHandleToZio(dir.handle), component.path, permissionsToZioMode(permissions));
473+
try waitForIo(&op.c);
474+
if (op.getResult()) |_| {
475+
status = .created;
476+
} else |err| switch (err) {
477+
error.PathAlreadyExists => {
478+
const kind = try filePathKind(dir, component.path);
479+
if (kind != .directory) return error.NotDir;
480+
},
481+
error.FileNotFound => {
482+
component = it.previous() orelse return error.FileNotFound;
483+
continue;
484+
},
485+
else => |e| return e,
486+
}
487+
component = it.next() orelse return status;
488+
}
469489
}
470490

471-
fn dirCreateDirPathOpenImpl(_: ?*anyopaque, _: Io.Dir, _: []const u8, _: Io.Dir.Permissions, _: Io.Dir.OpenOptions) Io.Dir.CreateDirPathOpenError!Io.Dir {
472-
@panic("TODO: dirCreateDirPathOpen");
491+
fn filePathKind(dir: Io.Dir, sub_path: []const u8) Io.Dir.StatFileError!Io.File.Kind {
492+
var op = ev.FileStat.init(stdIoHandleToZio(dir.handle), sub_path, .{ .follow_symlinks = false });
493+
try waitForIo(&op.c);
494+
const info = op.getResult() catch |err| return statFileErrToStdErr(err);
495+
return zioKindToStdIoKind(info.kind);
496+
}
497+
498+
fn dirCreateDirPathOpenImpl(_: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, permissions: Io.Dir.Permissions, options: Io.Dir.OpenOptions) Io.Dir.CreateDirPathOpenError!Io.Dir {
499+
return dirOpenDirImpl(null, dir, sub_path, options) catch |err| switch (err) {
500+
error.FileNotFound => {
501+
_ = try dirCreateDirPathImpl(null, dir, sub_path, permissions);
502+
return dirOpenDirImpl(null, dir, sub_path, options);
503+
},
504+
else => |e| return e,
505+
};
473506
}
474507

475508
fn dirOpenDirImpl(_: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, options: Io.Dir.OpenOptions) Io.Dir.OpenError!Io.Dir {
@@ -2656,6 +2689,72 @@ test "io: dir create/delete" {
26562689
try std.testing.expectError(error.FileNotFound, dir.deleteDir(io, dir_path));
26572690
}
26582691

2692+
test "io: dir createDirPath creates nested directories" {
2693+
const rt = try Runtime.init(std.testing.allocator, .{});
2694+
defer rt.deinit();
2695+
const io = rt.io();
2696+
2697+
const dir: Io.Dir = .cwd();
2698+
const sep = Io.Dir.path.sep_str;
2699+
const nested_path = "test_io_createDirPath" ++ sep ++ "a" ++ sep ++ "b" ++ sep ++ "c";
2700+
const base_path = "test_io_createDirPath";
2701+
2702+
// Clean up from previous failed runs.
2703+
dir.deleteDir(io, "test_io_createDirPath" ++ sep ++ "a" ++ sep ++ "b" ++ sep ++ "c") catch {};
2704+
dir.deleteDir(io, "test_io_createDirPath" ++ sep ++ "a" ++ sep ++ "b") catch {};
2705+
dir.deleteDir(io, "test_io_createDirPath" ++ sep ++ "a") catch {};
2706+
dir.deleteDir(io, base_path) catch {};
2707+
2708+
defer {
2709+
dir.deleteDir(io, "test_io_createDirPath" ++ sep ++ "a" ++ sep ++ "b" ++ sep ++ "c") catch {};
2710+
dir.deleteDir(io, "test_io_createDirPath" ++ sep ++ "a" ++ sep ++ "b") catch {};
2711+
dir.deleteDir(io, "test_io_createDirPath" ++ sep ++ "a") catch {};
2712+
dir.deleteDir(io, base_path) catch {};
2713+
}
2714+
2715+
// Create nested path from scratch.
2716+
const status = try dir.createDirPathStatus(io, nested_path, .default_dir);
2717+
try std.testing.expectEqual(Io.Dir.CreatePathStatus.created, status);
2718+
2719+
// Verify it exists.
2720+
var sub = try dir.openDir(io, nested_path, .{});
2721+
sub.close(io);
2722+
2723+
// Creating again should return .existed.
2724+
const status2 = try dir.createDirPathStatus(io, nested_path, .default_dir);
2725+
try std.testing.expectEqual(Io.Dir.CreatePathStatus.existed, status2);
2726+
}
2727+
2728+
test "io: dir createDirPathOpen creates and opens" {
2729+
const rt = try Runtime.init(std.testing.allocator, .{});
2730+
defer rt.deinit();
2731+
const io = rt.io();
2732+
2733+
const dir: Io.Dir = .cwd();
2734+
const sep = Io.Dir.path.sep_str;
2735+
const nested_path = "test_io_createDirPathOpen" ++ sep ++ "x" ++ sep ++ "y";
2736+
const base_path = "test_io_createDirPathOpen";
2737+
2738+
// Clean up from previous failed runs.
2739+
dir.deleteDir(io, "test_io_createDirPathOpen" ++ sep ++ "x" ++ sep ++ "y") catch {};
2740+
dir.deleteDir(io, "test_io_createDirPathOpen" ++ sep ++ "x") catch {};
2741+
dir.deleteDir(io, base_path) catch {};
2742+
2743+
defer {
2744+
dir.deleteDir(io, "test_io_createDirPathOpen" ++ sep ++ "x" ++ sep ++ "y") catch {};
2745+
dir.deleteDir(io, "test_io_createDirPathOpen" ++ sep ++ "x") catch {};
2746+
dir.deleteDir(io, base_path) catch {};
2747+
}
2748+
2749+
// Create and open nested path.
2750+
var sub = try dir.createDirPathOpen(io, nested_path, .{});
2751+
sub.close(io);
2752+
2753+
// Should be able to open it again.
2754+
var sub2 = try dir.openDir(io, nested_path, .{});
2755+
sub2.close(io);
2756+
}
2757+
26592758
test "io: dir iterate over files" {
26602759
// NetBSD's getdirentries can return dirents with either 32-bit or
26612760
// 64-bit d_fileno depending on the filesystem, shifting all field

0 commit comments

Comments
 (0)