authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-10-16 22:39:46-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-10-29 06:20:50-07:00
log143127529b109971c931cce18cc7a328f6031df3
treebacf6f1125a67270add4c7e1ec5f08376ed0a86d
parentec9dfc540b95a5577074e8915670ac2920b32f64

std.Io.Threaded: implement dirMake for WASI


5 files changed, 59 insertions(+), 100 deletions(-)

lib/std/Io.zig+4
......@@ -883,6 +883,10 @@ pub const Timestamp = struct {
883883 return .{ .nanoseconds = t.nanoseconds, .clock = clock };
884884 }
885885
886 pub fn fromNanoseconds(x: i96) Timestamp {
887 return .{ .nanoseconds = x };
888 }
889
886890 pub fn toSeconds(t: Timestamp) i64 {
887891 return @intCast(@divTrunc(t.nanoseconds, std.time.ns_per_s));
888892 }
lib/std/Io/Threaded.zig+47-16
......@@ -167,7 +167,7 @@ pub fn io(t: *Threaded) Io {
167167
168168 .dirMake = switch (builtin.os.tag) {
169169 .windows => @panic("TODO"),
170 .wasi => @panic("TODO"),
170 .wasi => dirMakeWasi,
171171 else => dirMakePosix,
172172 },
173173 .dirStat = dirStat,
......@@ -906,6 +906,37 @@ fn dirMakePosix(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode:
906906 }
907907}
908908
909fn dirMakeWasi(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode: Io.Dir.Mode) Io.Dir.MakeError!void {
910 if (builtin.link_libc) return dirMakePosix(userdata, dir, sub_path, mode);
911 const t: *Threaded = @ptrCast(@alignCast(userdata));
912 while (true) {
913 try t.checkCancel();
914 switch (std.os.wasi.path_create_directory(dir.handle, sub_path.ptr, sub_path.len)) {
915 .SUCCESS => return,
916 .INTR => continue,
917 .CANCELED => return error.Canceled,
918
919 .ACCES => return error.AccessDenied,
920 .BADF => |err| return errnoBug(err),
921 .PERM => return error.PermissionDenied,
922 .DQUOT => return error.DiskQuota,
923 .EXIST => return error.PathAlreadyExists,
924 .FAULT => |err| return errnoBug(err),
925 .LOOP => return error.SymLinkLoop,
926 .MLINK => return error.LinkQuotaExceeded,
927 .NAMETOOLONG => return error.NameTooLong,
928 .NOENT => return error.FileNotFound,
929 .NOMEM => return error.SystemResources,
930 .NOSPC => return error.NoSpaceLeft,
931 .NOTDIR => return error.NotDir,
932 .ROFS => return error.ReadOnlyFileSystem,
933 .NOTCAPABLE => return error.AccessDenied,
934 .ILSEQ => return error.BadPathName,
935 else => |err| return posix.unexpectedErrno(err),
936 }
937 }
938}
939
909940fn dirStat(userdata: ?*anyopaque, dir: Io.Dir) Io.Dir.StatError!Io.Dir.Stat {
910941 const t: *Threaded = @ptrCast(@alignCast(userdata));
911942 try t.checkCancel();
......@@ -1005,13 +1036,13 @@ fn dirStatPathWasi(
10051036 const t: *Threaded = @ptrCast(@alignCast(userdata));
10061037 const wasi = std.os.wasi;
10071038 const flags: wasi.lookupflags_t = .{
1008 .SYMLINK_FOLLOW = @intFromBool(options.follow_symlinks),
1039 .SYMLINK_FOLLOW = options.follow_symlinks,
10091040 };
10101041 var stat: wasi.filestat_t = undefined;
10111042 while (true) {
10121043 try t.checkCancel();
10131044 switch (wasi.path_filestat_get(dir.handle, flags, sub_path.ptr, sub_path.len, &stat)) {
1014 .SUCCESS => return statFromWasi(stat),
1045 .SUCCESS => return statFromWasi(&stat),
10151046 .INTR => continue,
10161047 .CANCELED => return error.Canceled,
10171048
......@@ -1166,19 +1197,19 @@ fn dirAccessWasi(
11661197 userdata: ?*anyopaque,
11671198 dir: Io.Dir,
11681199 sub_path: []const u8,
1169 options: Io.File.OpenFlags,
1170) Io.File.AccessError!void {
1200 options: Io.Dir.AccessOptions,
1201) Io.Dir.AccessError!void {
11711202 if (builtin.link_libc) return dirAccessPosix(userdata, dir, sub_path, options);
11721203 const t: *Threaded = @ptrCast(@alignCast(userdata));
11731204 const wasi = std.os.wasi;
11741205 const flags: wasi.lookupflags_t = .{
1175 .SYMLINK_FOLLOW = @intFromBool(options.follow_symlinks),
1206 .SYMLINK_FOLLOW = options.follow_symlinks,
11761207 };
1177 const stat = while (true) {
1178 var stat: wasi.filestat_t = undefined;
1208 var stat: wasi.filestat_t = undefined;
1209 while (true) {
11791210 try t.checkCancel();
11801211 switch (wasi.path_filestat_get(dir.handle, flags, sub_path.ptr, sub_path.len, &stat)) {
1181 .SUCCESS => break statFromWasi(stat),
1212 .SUCCESS => break,
11821213 .INTR => continue,
11831214 .CANCELED => return error.Canceled,
11841215
......@@ -1194,9 +1225,9 @@ fn dirAccessWasi(
11941225 .ILSEQ => return error.BadPathName,
11951226 else => |err| return posix.unexpectedErrno(err),
11961227 }
1197 };
1228 }
11981229
1199 if (!options.mode.read and !options.mode.write and !options.mode.execute)
1230 if (!options.read and !options.write and !options.execute)
12001231 return;
12011232
12021233 var directory: wasi.fdstat_t = undefined;
......@@ -1204,14 +1235,14 @@ fn dirAccessWasi(
12041235 return error.AccessDenied;
12051236
12061237 var rights: wasi.rights_t = .{};
1207 if (options.mode.read) {
1238 if (options.read) {
12081239 if (stat.filetype == .DIRECTORY) {
12091240 rights.FD_READDIR = true;
12101241 } else {
12111242 rights.FD_READ = true;
12121243 }
12131244 }
1214 if (options.mode.write)
1245 if (options.write)
12151246 rights.FD_WRITE = true;
12161247
12171248 // No validation for execution.
......@@ -3262,9 +3293,9 @@ fn statFromWasi(st: *const std.os.wasi.filestat_t) Io.File.Stat {
32623293 .SOCKET_STREAM, .SOCKET_DGRAM => .unix_domain_socket,
32633294 else => .unknown,
32643295 },
3265 .atime = st.atim,
3266 .mtime = st.mtim,
3267 .ctime = st.ctim,
3296 .atime = .fromNanoseconds(st.atim),
3297 .mtime = .fromNanoseconds(st.mtim),
3298 .ctime = .fromNanoseconds(st.ctim),
32683299 };
32693300}
32703301
lib/std/os.zig+7-1
......@@ -201,7 +201,13 @@ pub fn getFdPath(fd: std.posix.fd_t, out_buffer: *[max_path_bytes]u8) std.posix.
201201 }
202202}
203203
204pub fn fstat_wasi(fd: posix.fd_t) posix.FStatError!wasi.filestat_t {
204pub const FstatError = error{
205 SystemResources,
206 AccessDenied,
207 Unexpected,
208};
209
210pub fn fstat_wasi(fd: posix.fd_t) FstatError!wasi.filestat_t {
205211 var stat: wasi.filestat_t = undefined;
206212 switch (wasi.fd_filestat_get(fd, &stat)) {
207213 .SUCCESS => return stat,
lib/std/posix.zig+1-25
......@@ -2809,37 +2809,13 @@ pub fn mkdirat(dir_fd: fd_t, sub_dir_path: []const u8, mode: mode_t) MakeDirErro
28092809 const sub_dir_path_w = try windows.sliceToPrefixedFileW(dir_fd, sub_dir_path);
28102810 return mkdiratW(dir_fd, sub_dir_path_w.span(), mode);
28112811 } else if (native_os == .wasi and !builtin.link_libc) {
2812 return mkdiratWasi(dir_fd, sub_dir_path, mode);
2812 @compileError("use std.Io instead");
28132813 } else {
28142814 const sub_dir_path_c = try toPosixPath(sub_dir_path);
28152815 return mkdiratZ(dir_fd, &sub_dir_path_c, mode);
28162816 }
28172817}
28182818
2819pub fn mkdiratWasi(dir_fd: fd_t, sub_dir_path: []const u8, mode: mode_t) MakeDirError!void {
2820 _ = mode;
2821 switch (wasi.path_create_directory(dir_fd, sub_dir_path.ptr, sub_dir_path.len)) {
2822 .SUCCESS => return,
2823 .ACCES => return error.AccessDenied,
2824 .BADF => unreachable,
2825 .PERM => return error.PermissionDenied,
2826 .DQUOT => return error.DiskQuota,
2827 .EXIST => return error.PathAlreadyExists,
2828 .FAULT => unreachable,
2829 .LOOP => return error.SymLinkLoop,
2830 .MLINK => return error.LinkQuotaExceeded,
2831 .NAMETOOLONG => return error.NameTooLong,
2832 .NOENT => return error.FileNotFound,
2833 .NOMEM => return error.SystemResources,
2834 .NOSPC => return error.NoSpaceLeft,
2835 .NOTDIR => return error.NotDir,
2836 .ROFS => return error.ReadOnlyFileSystem,
2837 .NOTCAPABLE => return error.AccessDenied,
2838 .ILSEQ => return error.BadPathName,
2839 else => |err| return unexpectedErrno(err),
2840 }
2841}
2842
28432819/// Same as `mkdirat` except the parameters are null-terminated.
28442820pub fn mkdiratZ(dir_fd: fd_t, sub_dir_path: [*:0]const u8, mode: mode_t) MakeDirError!void {
28452821 if (native_os == .windows) {
lib/std/posix/test.zig-58
......@@ -109,64 +109,6 @@ test "open smoke test" {
109109 }
110110}
111111
112test "openat smoke test" {
113 if (native_os == .windows) return error.SkipZigTest;
114
115 // TODO verify file attributes using `fstatat`
116
117 var tmp = tmpDir(.{});
118 defer tmp.cleanup();
119
120 var fd: posix.fd_t = undefined;
121 const mode: posix.mode_t = if (native_os == .windows) 0 else 0o666;
122
123 // Create some file using `openat`.
124 fd = try posix.openat(tmp.dir.fd, "some_file", CommonOpenFlags.lower(.{
125 .ACCMODE = .RDWR,
126 .CREAT = true,
127 .EXCL = true,
128 }), mode);
129 posix.close(fd);
130
131 // Try this again with the same flags. This op should fail with error.PathAlreadyExists.
132 try expectError(error.PathAlreadyExists, posix.openat(tmp.dir.fd, "some_file", CommonOpenFlags.lower(.{
133 .ACCMODE = .RDWR,
134 .CREAT = true,
135 .EXCL = true,
136 }), mode));
137
138 // Try opening without `EXCL` flag.
139 fd = try posix.openat(tmp.dir.fd, "some_file", CommonOpenFlags.lower(.{
140 .ACCMODE = .RDWR,
141 .CREAT = true,
142 }), mode);
143 posix.close(fd);
144
145 // Try opening as a directory which should fail.
146 try expectError(error.NotDir, posix.openat(tmp.dir.fd, "some_file", CommonOpenFlags.lower(.{
147 .ACCMODE = .RDWR,
148 .DIRECTORY = true,
149 }), mode));
150
151 // Create some directory
152 try posix.mkdirat(tmp.dir.fd, "some_dir", mode);
153
154 // Open dir using `open`
155 fd = try posix.openat(tmp.dir.fd, "some_dir", CommonOpenFlags.lower(.{
156 .ACCMODE = .RDONLY,
157 .DIRECTORY = true,
158 }), mode);
159 posix.close(fd);
160
161 // Try opening as file which should fail (skip on wasi+libc due to
162 // https://github.com/bytecodealliance/wasmtime/issues/9054)
163 if (native_os != .wasi or !builtin.link_libc) {
164 try expectError(error.IsDir, posix.openat(tmp.dir.fd, "some_dir", CommonOpenFlags.lower(.{
165 .ACCMODE = .RDWR,
166 }), mode));
167 }
168}
169
170112test "readlink on Windows" {
171113 if (native_os != .windows) return error.SkipZigTest;
172114