authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-12-19 18:16:47-08:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-12-23 22:15:11-08:00
log99f25bfc23a902d7cf047ae2b95e1d9d7e3b09ee
treebd03c0bd6f07f0b9472bf00525589b83994e3600
parent98f05a0f53d08683e4b8127210448c0de00dc9cf

std.Io: implement directory reading for WASI


2 files changed, 107 insertions(+), 6 deletions(-)

lib/std/Io/Dir.zig+2
...@@ -106,6 +106,8 @@ pub const Reader = struct {...@@ -106,6 +106,8 @@ pub const Reader = struct {
106 .linux => std.mem.alignForward(usize, @sizeOf(std.os.linux.dirent64), 8) +106 .linux => std.mem.alignForward(usize, @sizeOf(std.os.linux.dirent64), 8) +
107 std.mem.alignForward(usize, max_name_bytes, 8),107 std.mem.alignForward(usize, max_name_bytes, 8),
108 .windows => std.mem.alignForward(usize, max_name_bytes, @alignOf(usize)),108 .windows => std.mem.alignForward(usize, max_name_bytes, @alignOf(usize)),
109 .wasi => @sizeOf(std.os.wasi.dirent_t) +
110 std.mem.alignForward(usize, max_name_bytes, @alignOf(std.os.wasi.dirent_t)),
109 else => if (builtin.link_libc) @sizeOf(std.c.dirent) else std.mem.alignForward(usize, max_name_bytes, @alignOf(usize)),111 else => if (builtin.link_libc) @sizeOf(std.c.dirent) else std.mem.alignForward(usize, max_name_bytes, @alignOf(usize)),
110 };112 };
111113
lib/std/Io/Threaded.zig+105-6
...@@ -3378,7 +3378,7 @@ fn dirReadLinux(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir...@@ -3378,7 +3378,7 @@ fn dirReadLinux(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir
3378 // To be consistent across platforms, iteration3378 // To be consistent across platforms, iteration
3379 // ends if the directory being iterated is deleted3379 // ends if the directory being iterated is deleted
3380 // during iteration. This matches the behavior of3380 // during iteration. This matches the behavior of
3381 // non-Linux UNIX platforms.3381 // non-Linux, non-WASI UNIX platforms.
3382 .NOENT => {3382 .NOENT => {
3383 dr.state = .finished;3383 dr.state = .finished;
3384 return 0;3384 return 0;
...@@ -3481,7 +3481,7 @@ fn dirReadDarwin(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Di...@@ -3481,7 +3481,7 @@ fn dirReadDarwin(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Di
3481 else => |e| {3481 else => |e| {
3482 current_thread.endSyscall();3482 current_thread.endSyscall();
3483 switch (e) {3483 switch (e) {
3484 .BADF => |err| return errnoBug(err), // Dir is invalid or was opened without iteration ability3484 .BADF => |err| return errnoBug(err), // Dir is invalid or was opened without iteration ability.
3485 .FAULT => |err| return errnoBug(err),3485 .FAULT => |err| return errnoBug(err),
3486 .NOTDIR => |err| return errnoBug(err),3486 .NOTDIR => |err| return errnoBug(err),
3487 .INVAL => |err| return errnoBug(err),3487 .INVAL => |err| return errnoBug(err),
...@@ -3839,10 +3839,109 @@ fn dirReadWindows(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) D...@@ -3839,10 +3839,109 @@ fn dirReadWindows(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) D
3839}3839}
38403840
3841fn dirReadWasi(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize {3841fn dirReadWasi(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize {
3842 _ = userdata;3842 // We intentinally use fd_readdir even when linked with libc, since its
3843 _ = dr;3843 // implementation is exactly the same as below, and we avoid the code
3844 _ = buffer;3844 // complexity here.
3845 @panic("TODO");3845 const wasi = std.os.wasi;
3846 const t: *Threaded = @ptrCast(@alignCast(userdata));
3847 const current_thread = Thread.getCurrent(t);
3848 const Header = extern struct {
3849 cookie: u64,
3850 };
3851 const header: *align(@alignOf(usize)) Header = @ptrCast(dr.buffer.ptr);
3852 const header_end: usize = @sizeOf(Header);
3853 if (dr.index < header_end) {
3854 // Initialize header.
3855 dr.index = header_end;
3856 dr.end = header_end;
3857 header.* = .{ .cookie = wasi.DIRCOOKIE_START };
3858 }
3859 var buffer_index: usize = 0;
3860 while (buffer.len - buffer_index != 0) {
3861 // According to the WASI spec, the last entry might be truncated, so we
3862 // need to check if the remaining buffer contains the whole dirent.
3863 if (dr.end - dr.index < @sizeOf(wasi.dirent_t)) {
3864 // Refill the buffer, unless we've already created references to
3865 // buffered data.
3866 if (buffer_index != 0) break;
3867 if (dr.state == .reset) {
3868 header.* = .{ .cookie = wasi.DIRCOOKIE_START };
3869 dr.state = .reading;
3870 }
3871 const dents_buffer = dr.buffer[header_end..];
3872 var n: usize = undefined;
3873 try current_thread.beginSyscall();
3874 while (true) {
3875 switch (wasi.fd_readdir(dr.dir.handle, dents_buffer.ptr, dents_buffer.len, header.cookie, &n)) {
3876 .SUCCESS => {
3877 current_thread.endSyscall();
3878 break;
3879 },
3880 .INTR => {
3881 try current_thread.checkCancel();
3882 continue;
3883 },
3884 else => |e| {
3885 current_thread.endSyscall();
3886 switch (e) {
3887 .BADF => |err| return errnoBug(err), // Dir is invalid or was opened without iteration ability.
3888 .FAULT => |err| return errnoBug(err),
3889 .NOTDIR => |err| return errnoBug(err),
3890 .INVAL => |err| return errnoBug(err),
3891 // To be consistent across platforms, iteration
3892 // ends if the directory being iterated is deleted
3893 // during iteration. This matches the behavior of
3894 // non-Linux, non-WASI UNIX platforms.
3895 .NOENT => {
3896 dr.state = .finished;
3897 return 0;
3898 },
3899 .NOTCAPABLE => return error.AccessDenied,
3900 else => |err| return posix.unexpectedErrno(err),
3901 }
3902 },
3903 }
3904 }
3905 if (n == 0) {
3906 dr.state = .finished;
3907 return 0;
3908 }
3909 dr.index = header_end;
3910 dr.end = header_end + n;
3911 }
3912 const entry: *align(1) wasi.dirent_t = @ptrCast(&dr.buffer[dr.index]);
3913 const entry_size = @sizeOf(wasi.dirent_t);
3914 const name_index = dr.index + entry_size;
3915 if (name_index + entry.namlen > dr.end) {
3916 // This case, the name is truncated, so we need to call readdir to store the entire name.
3917 dr.end = dr.index; // Force fd_readdir in the next loop.
3918 continue;
3919 }
3920 const name = dr.buffer[name_index..][0..entry.namlen];
3921 const next_index = name_index + entry.namlen;
3922 dr.index = next_index;
3923 header.cookie = entry.next;
3924
3925 if (std.mem.eql(u8, name, ".") or std.mem.eql(u8, name, ".."))
3926 continue;
3927
3928 const entry_kind: File.Kind = switch (entry.type) {
3929 .BLOCK_DEVICE => .block_device,
3930 .CHARACTER_DEVICE => .character_device,
3931 .DIRECTORY => .directory,
3932 .SYMBOLIC_LINK => .sym_link,
3933 .REGULAR_FILE => .file,
3934 .SOCKET_STREAM, .SOCKET_DGRAM => .unix_domain_socket,
3935 else => .unknown,
3936 };
3937 buffer[buffer_index] = .{
3938 .name = name,
3939 .kind = entry_kind,
3940 .inode = entry.ino,
3941 };
3942 buffer_index += 1;
3943 }
3944 return buffer_index;
3846}3945}
38473946
3848fn dirReadUnimplemented(userdata: ?*anyopaque, dir_reader: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize {3947fn dirReadUnimplemented(userdata: ?*anyopaque, dir_reader: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize {