Commit 143127529b

Andrew Kelley <andrew@ziglang.org>
2025-10-17 07:39:46
std.Io.Threaded: implement dirMake for WASI
1 parent ec9dfc5
Changed files (5)
lib/std/Io/Threaded.zig
@@ -167,7 +167,7 @@ pub fn io(t: *Threaded) Io {
 
             .dirMake = switch (builtin.os.tag) {
                 .windows => @panic("TODO"),
-                .wasi => @panic("TODO"),
+                .wasi => dirMakeWasi,
                 else => dirMakePosix,
             },
             .dirStat = dirStat,
@@ -906,6 +906,37 @@ fn dirMakePosix(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode:
     }
 }
 
+fn dirMakeWasi(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode: Io.Dir.Mode) Io.Dir.MakeError!void {
+    if (builtin.link_libc) return dirMakePosix(userdata, dir, sub_path, mode);
+    const t: *Threaded = @ptrCast(@alignCast(userdata));
+    while (true) {
+        try t.checkCancel();
+        switch (std.os.wasi.path_create_directory(dir.handle, sub_path.ptr, sub_path.len)) {
+            .SUCCESS => return,
+            .INTR => continue,
+            .CANCELED => return error.Canceled,
+
+            .ACCES => return error.AccessDenied,
+            .BADF => |err| return errnoBug(err),
+            .PERM => return error.PermissionDenied,
+            .DQUOT => return error.DiskQuota,
+            .EXIST => return error.PathAlreadyExists,
+            .FAULT => |err| return errnoBug(err),
+            .LOOP => return error.SymLinkLoop,
+            .MLINK => return error.LinkQuotaExceeded,
+            .NAMETOOLONG => return error.NameTooLong,
+            .NOENT => return error.FileNotFound,
+            .NOMEM => return error.SystemResources,
+            .NOSPC => return error.NoSpaceLeft,
+            .NOTDIR => return error.NotDir,
+            .ROFS => return error.ReadOnlyFileSystem,
+            .NOTCAPABLE => return error.AccessDenied,
+            .ILSEQ => return error.BadPathName,
+            else => |err| return posix.unexpectedErrno(err),
+        }
+    }
+}
+
 fn dirStat(userdata: ?*anyopaque, dir: Io.Dir) Io.Dir.StatError!Io.Dir.Stat {
     const t: *Threaded = @ptrCast(@alignCast(userdata));
     try t.checkCancel();
@@ -1005,13 +1036,13 @@ fn dirStatPathWasi(
     const t: *Threaded = @ptrCast(@alignCast(userdata));
     const wasi = std.os.wasi;
     const flags: wasi.lookupflags_t = .{
-        .SYMLINK_FOLLOW = @intFromBool(options.follow_symlinks),
+        .SYMLINK_FOLLOW = options.follow_symlinks,
     };
     var stat: wasi.filestat_t = undefined;
     while (true) {
         try t.checkCancel();
         switch (wasi.path_filestat_get(dir.handle, flags, sub_path.ptr, sub_path.len, &stat)) {
-            .SUCCESS => return statFromWasi(stat),
+            .SUCCESS => return statFromWasi(&stat),
             .INTR => continue,
             .CANCELED => return error.Canceled,
 
@@ -1166,19 +1197,19 @@ fn dirAccessWasi(
     userdata: ?*anyopaque,
     dir: Io.Dir,
     sub_path: []const u8,
-    options: Io.File.OpenFlags,
-) Io.File.AccessError!void {
+    options: Io.Dir.AccessOptions,
+) Io.Dir.AccessError!void {
     if (builtin.link_libc) return dirAccessPosix(userdata, dir, sub_path, options);
     const t: *Threaded = @ptrCast(@alignCast(userdata));
     const wasi = std.os.wasi;
     const flags: wasi.lookupflags_t = .{
-        .SYMLINK_FOLLOW = @intFromBool(options.follow_symlinks),
+        .SYMLINK_FOLLOW = options.follow_symlinks,
     };
-    const stat = while (true) {
-        var stat: wasi.filestat_t = undefined;
+    var stat: wasi.filestat_t = undefined;
+    while (true) {
         try t.checkCancel();
         switch (wasi.path_filestat_get(dir.handle, flags, sub_path.ptr, sub_path.len, &stat)) {
-            .SUCCESS => break statFromWasi(stat),
+            .SUCCESS => break,
             .INTR => continue,
             .CANCELED => return error.Canceled,
 
@@ -1194,9 +1225,9 @@ fn dirAccessWasi(
             .ILSEQ => return error.BadPathName,
             else => |err| return posix.unexpectedErrno(err),
         }
-    };
+    }
 
-    if (!options.mode.read and !options.mode.write and !options.mode.execute)
+    if (!options.read and !options.write and !options.execute)
         return;
 
     var directory: wasi.fdstat_t = undefined;
@@ -1204,14 +1235,14 @@ fn dirAccessWasi(
         return error.AccessDenied;
 
     var rights: wasi.rights_t = .{};
-    if (options.mode.read) {
+    if (options.read) {
         if (stat.filetype == .DIRECTORY) {
             rights.FD_READDIR = true;
         } else {
             rights.FD_READ = true;
         }
     }
-    if (options.mode.write)
+    if (options.write)
         rights.FD_WRITE = true;
 
     // No validation for execution.
@@ -3262,9 +3293,9 @@ fn statFromWasi(st: *const std.os.wasi.filestat_t) Io.File.Stat {
             .SOCKET_STREAM, .SOCKET_DGRAM => .unix_domain_socket,
             else => .unknown,
         },
-        .atime = st.atim,
-        .mtime = st.mtim,
-        .ctime = st.ctim,
+        .atime = .fromNanoseconds(st.atim),
+        .mtime = .fromNanoseconds(st.mtim),
+        .ctime = .fromNanoseconds(st.ctim),
     };
 }
 
lib/std/posix/test.zig
@@ -109,64 +109,6 @@ test "open smoke test" {
     }
 }
 
-test "openat smoke test" {
-    if (native_os == .windows) return error.SkipZigTest;
-
-    // TODO verify file attributes using `fstatat`
-
-    var tmp = tmpDir(.{});
-    defer tmp.cleanup();
-
-    var fd: posix.fd_t = undefined;
-    const mode: posix.mode_t = if (native_os == .windows) 0 else 0o666;
-
-    // Create some file using `openat`.
-    fd = try posix.openat(tmp.dir.fd, "some_file", CommonOpenFlags.lower(.{
-        .ACCMODE = .RDWR,
-        .CREAT = true,
-        .EXCL = true,
-    }), mode);
-    posix.close(fd);
-
-    // Try this again with the same flags. This op should fail with error.PathAlreadyExists.
-    try expectError(error.PathAlreadyExists, posix.openat(tmp.dir.fd, "some_file", CommonOpenFlags.lower(.{
-        .ACCMODE = .RDWR,
-        .CREAT = true,
-        .EXCL = true,
-    }), mode));
-
-    // Try opening without `EXCL` flag.
-    fd = try posix.openat(tmp.dir.fd, "some_file", CommonOpenFlags.lower(.{
-        .ACCMODE = .RDWR,
-        .CREAT = true,
-    }), mode);
-    posix.close(fd);
-
-    // Try opening as a directory which should fail.
-    try expectError(error.NotDir, posix.openat(tmp.dir.fd, "some_file", CommonOpenFlags.lower(.{
-        .ACCMODE = .RDWR,
-        .DIRECTORY = true,
-    }), mode));
-
-    // Create some directory
-    try posix.mkdirat(tmp.dir.fd, "some_dir", mode);
-
-    // Open dir using `open`
-    fd = try posix.openat(tmp.dir.fd, "some_dir", CommonOpenFlags.lower(.{
-        .ACCMODE = .RDONLY,
-        .DIRECTORY = true,
-    }), mode);
-    posix.close(fd);
-
-    // Try opening as file which should fail (skip on wasi+libc due to
-    // https://github.com/bytecodealliance/wasmtime/issues/9054)
-    if (native_os != .wasi or !builtin.link_libc) {
-        try expectError(error.IsDir, posix.openat(tmp.dir.fd, "some_dir", CommonOpenFlags.lower(.{
-            .ACCMODE = .RDWR,
-        }), mode));
-    }
-}
-
 test "readlink on Windows" {
     if (native_os != .windows) return error.SkipZigTest;
 
lib/std/Io.zig
@@ -883,6 +883,10 @@ pub const Timestamp = struct {
         return .{ .nanoseconds = t.nanoseconds, .clock = clock };
     }
 
+    pub fn fromNanoseconds(x: i96) Timestamp {
+        return .{ .nanoseconds = x };
+    }
+
     pub fn toSeconds(t: Timestamp) i64 {
         return @intCast(@divTrunc(t.nanoseconds, std.time.ns_per_s));
     }
lib/std/os.zig
@@ -201,7 +201,13 @@ pub fn getFdPath(fd: std.posix.fd_t, out_buffer: *[max_path_bytes]u8) std.posix.
     }
 }
 
-pub fn fstat_wasi(fd: posix.fd_t) posix.FStatError!wasi.filestat_t {
+pub const FstatError = error{
+    SystemResources,
+    AccessDenied,
+    Unexpected,
+};
+
+pub fn fstat_wasi(fd: posix.fd_t) FstatError!wasi.filestat_t {
     var stat: wasi.filestat_t = undefined;
     switch (wasi.fd_filestat_get(fd, &stat)) {
         .SUCCESS => return stat,
lib/std/posix.zig
@@ -2809,37 +2809,13 @@ pub fn mkdirat(dir_fd: fd_t, sub_dir_path: []const u8, mode: mode_t) MakeDirErro
         const sub_dir_path_w = try windows.sliceToPrefixedFileW(dir_fd, sub_dir_path);
         return mkdiratW(dir_fd, sub_dir_path_w.span(), mode);
     } else if (native_os == .wasi and !builtin.link_libc) {
-        return mkdiratWasi(dir_fd, sub_dir_path, mode);
+        @compileError("use std.Io instead");
     } else {
         const sub_dir_path_c = try toPosixPath(sub_dir_path);
         return mkdiratZ(dir_fd, &sub_dir_path_c, mode);
     }
 }
 
-pub fn mkdiratWasi(dir_fd: fd_t, sub_dir_path: []const u8, mode: mode_t) MakeDirError!void {
-    _ = mode;
-    switch (wasi.path_create_directory(dir_fd, sub_dir_path.ptr, sub_dir_path.len)) {
-        .SUCCESS => return,
-        .ACCES => return error.AccessDenied,
-        .BADF => unreachable,
-        .PERM => return error.PermissionDenied,
-        .DQUOT => return error.DiskQuota,
-        .EXIST => return error.PathAlreadyExists,
-        .FAULT => unreachable,
-        .LOOP => return error.SymLinkLoop,
-        .MLINK => return error.LinkQuotaExceeded,
-        .NAMETOOLONG => return error.NameTooLong,
-        .NOENT => return error.FileNotFound,
-        .NOMEM => return error.SystemResources,
-        .NOSPC => return error.NoSpaceLeft,
-        .NOTDIR => return error.NotDir,
-        .ROFS => return error.ReadOnlyFileSystem,
-        .NOTCAPABLE => return error.AccessDenied,
-        .ILSEQ => return error.BadPathName,
-        else => |err| return unexpectedErrno(err),
-    }
-}
-
 /// Same as `mkdirat` except the parameters are null-terminated.
 pub fn mkdiratZ(dir_fd: fd_t, sub_dir_path: [*:0]const u8, mode: mode_t) MakeDirError!void {
     if (native_os == .windows) {