diff options
| -rw-r--r-- | lib/std/fs.zig | 59 | ||||
| -rw-r--r-- | lib/std/fs/wasi.zig | 151 |
2 files changed, 210 insertions, 0 deletions
diff --git a/lib/std/fs.zig b/lib/std/fs.zig index 063b2f3fdc..862fc84ef1 100644 --- a/lib/std/fs.zig +++ b/lib/std/fs.zig @@ -12,6 +12,7 @@ const is_darwin = std.Target.current.os.tag.isDarwin(); pub const path = @import("fs/path.zig"); pub const File = @import("fs/file.zig").File; +pub const wasi = @import("fs/wasi.zig"); // TODO audit these APIs with respect to Dir and absolute paths @@ -584,10 +585,37 @@ pub const Dir = struct { const path_w = try os.windows.sliceToPrefixedFileW(sub_path); return self.openFileW(path_w.span(), flags); } + if (builtin.os.tag == .wasi) { + return self.openFileWasi(sub_path, flags); + } const path_c = try os.toPosixPath(sub_path); return self.openFileZ(&path_c, flags); } + /// Save as `openFile` but WASI only. + pub fn openFileWasi(self: Dir, sub_path: []const u8, flags: File.OpenFlags) File.OpenError!File { + const w = os.wasi; + var fdflags: w.fdflag_t = 0x0; + var rights: w.rights_t = 0x0; + if (flags.read) { + rights |= w.FD_READ | w.FD_TELL | w.FD_FILESTAT_GET; + } + if (flags.write) { + fdflags |= w.FDFLAG_APPEND; + rights |= w.FD_WRITE | + w.FD_DATASYNC | + w.FD_SEEK | + w.FD_FDSTAT_SET_FLAGS | + w.FD_SYNC | + w.FD_ALLOCATE | + w.FD_ADVISE | + w.FD_FILESTAT_SET_TIMES | + w.FD_FILESTAT_SET_SIZE; + } + const fd = try wasi.openat(self.fd, sub_path, 0x0, fdflags, rights); + return File{ .handle = fd }; + } + pub const openFileC = @compileError("deprecated: renamed to openFileZ"); /// Same as `openFile` but the path parameter is null-terminated. @@ -671,12 +699,41 @@ pub const Dir = struct { const path_w = try os.windows.sliceToPrefixedFileW(sub_path); return self.createFileW(path_w.span(), flags); } + if (builtin.os.tag == .wasi) { + return self.createFileWasi(sub_path, flags); + } const path_c = try os.toPosixPath(sub_path); return self.createFileZ(&path_c, flags); } pub const createFileC = @compileError("deprecated: renamed to createFileZ"); + /// Same as `createFile` but WASI only. + pub fn createFileWasi(self: Dir, sub_path: []const u8, flags: File.CreateFlags) File.OpenError!File { + const w = os.wasi; + var oflags = w.O_CREAT; + var rights = w.RIGHT_FD_WRITE | + w.RIGHT_FD_DATASYNC | + w.RIGHT_FD_SEEK | + w.RIGHT_FD_FDSTAT_SET_FLAGS | + w.RIGHT_FD_SYNC | + w.RIGHT_FD_ALLOCATE | + w.RIGHT_FD_ADVISE | + w.RIGHT_FD_FILESTAT_SET_TIMES | + w.RIGHT_FD_FILESTAT_SET_SIZE; + if (flags.read) { + rights |= w.RIGHT_FD_READ | w.RIGHT_FD_TELL | w.RIGHT_FD_FILESTAT_GET; + } + if (flags.truncate) { + oflags |= w.O_TRUNC; + } + if (flags.exclusive) { + oflags |= w.O_EXCL; + } + const fd = try wasi.openat(self.fd, sub_path, oflags, 0x0, rights); + return File{ .handle = fd }; + } + /// Same as `createFile` but the path parameter is null-terminated. pub fn createFileZ(self: Dir, sub_path_c: [*:0]const u8, flags: File.CreateFlags) File.OpenError!File { if (builtin.os.tag == .windows) { @@ -1390,6 +1447,8 @@ pub const Dir = struct { pub fn cwd() Dir { if (builtin.os.tag == .windows) { return Dir{ .fd = os.windows.peb().ProcessParameters.CurrentDirectory.Handle }; + } else if (builtin.os.tag == .wasi) { + @compileError("WASI doesn't have a concept of cwd; use TODO instead"); } else { return Dir{ .fd = os.AT_FDCWD }; } diff --git a/lib/std/fs/wasi.zig b/lib/std/fs/wasi.zig new file mode 100644 index 0000000000..1cf56e6c7b --- /dev/null +++ b/lib/std/fs/wasi.zig @@ -0,0 +1,151 @@ +const std = @import("std"); +const os = std.os; +const mem = std.mem; +const Allocator = mem.Allocator; + +usingnamespace std.os.wasi; + +/// Type of WASI preopen. +/// +/// WASI currently offers only `Dir` as a valid preopen resource. +pub const PreopenType = enum { + Dir, +}; + +/// WASI preopen struct. This struct consists of a WASI file descriptor +/// and type of WASI preopen. It can be obtained directly from the WASI +/// runtime using `PreopenList.populate()` method. +pub const Preopen = struct { + /// WASI file descriptor. + fd: fd_t, + + /// Type of the preopen. + @"type": union(PreopenType) { + /// Path to a preopened directory. + Dir: []const u8, + }, + + const Self = @This(); + + /// Construct new `Preopen` instance of type `PreopenType.Dir` from + /// WASI file descriptor and WASI path. + pub fn newDir(fd: fd_t, path: []const u8) Self { + return Self{ + .fd = fd, + .@"type" = .{ .Dir = path }, + }; + } +}; + +/// Dynamically-sized array list of WASI preopens. This struct is a +/// convenience wrapper for issuing `std.os.wasi.fd_prestat_get` and +/// `std.os.wasi.fd_prestat_dir_name` syscalls to the WASI runtime, and +/// collecting the returned preopens. +/// +/// This struct is intended to be used in any WASI program which intends +/// to use the capabilities as passed on by the user of the runtime. +pub const PreopenList = struct { + const InnerList = std.ArrayList(Preopen); + + /// Internal dynamically-sized buffer for storing the gathered preopens. + buffer: InnerList, + + const Self = @This(); + + pub const Error = os.UnexpectedError || Allocator.Error; + + /// Deinitialize with `deinit`. + pub fn init(allocator: *Allocator) Self { + return Self{ .buffer = InnerList.init(allocator) }; + } + + /// Release all allocated memory. + pub fn deinit(pm: Self) void { + for (pm.buffer.items) |preopen| { + switch (preopen.@"type") { + PreopenType.Dir => |path| pm.buffer.allocator.free(path), + } + } + pm.buffer.deinit(); + } + + /// Populate the list with the preopens by issuing `std.os.wasi.fd_prestat_get` + /// and `std.os.wasi.fd_prestat_dir_name` syscalls to the runtime. + /// + /// If called more than once, it will clear its contents every time before + /// issuing the syscalls. + pub fn populate(self: *Self) Error!void { + // Clear contents if we're being called again + for (self.toOwnedSlice()) |preopen| { + switch (preopen.@"type") { + PreopenType.Dir => |path| self.buffer.allocator.free(path), + } + } + errdefer self.deinit(); + var fd: fd_t = 3; // start fd has to be beyond stdio fds + + while (true) { + var buf: prestat_t = undefined; + switch (fd_prestat_get(fd, &buf)) { + ESUCCESS => {}, + ENOTSUP => { + // not a preopen, so keep going + continue; + }, + EBADF => { + // OK, no more fds available + break; + }, + else => |err| return os.unexpectedErrno(err), + } + const preopen_len = buf.u.dir.pr_name_len; + const path_buf = try self.buffer.allocator.alloc(u8, preopen_len); + mem.set(u8, path_buf, 0); + switch (fd_prestat_dir_name(fd, path_buf.ptr, preopen_len)) { + ESUCCESS => {}, + else => |err| return os.unexpectedErrno(err), + } + const preopen = Preopen.newDir(fd, path_buf); + try self.buffer.append(preopen); + fd += 1; + } + } + + /// Find preopen by path. If the preopen exists, return it. + /// Otherwise, return `null`. + /// + /// TODO make the function more generic by searching by `PreopenType` union. This will + /// be needed in the future when WASI extends its capabilities to resources + /// other than preopened directories. + pub fn find(self: *const Self, path: []const u8) ?*const Preopen { + for (self.buffer.items) |preopen| { + switch (preopen.@"type") { + PreopenType.Dir => |preopen_path| { + if (mem.eql(u8, path, preopen_path)) return &preopen; + }, + } + } + return null; + } + + /// Return the inner buffer as read-only slice. + pub fn asSlice(self: *const Self) []const Preopen { + return self.buffer.items; + } + + /// The caller owns the returned memory. ArrayList becomes empty. + pub fn toOwnedSlice(self: *Self) []Preopen { + return self.buffer.toOwnedSlice(); + } +}; + +/// Convenience wrapper for `std.os.wasi.path_open` syscall. +pub fn openat(dir_fd: fd_t, file_path: []const u8, oflags: oflags_t, fdflags: fdflags_t, rights: rights_t) os.OpenError!fd_t { + var fd: fd_t = undefined; + switch (path_open(dir_fd, 0x0, file_path.ptr, file_path.len, oflags, rights, 0x0, fdflags, &fd)) { + 0 => {}, + // TODO map errors + else => |err| return std.os.unexpectedErrno(err), + } + return fd; +} |
