| ... | ... | @@ -0,0 +1,151 @@ |
| 1 | const std = @import("std"); |
| 2 | const os = std.os; |
| 3 | const mem = std.mem; |
| 4 | const Allocator = mem.Allocator; |
| 5 | |
| 6 | usingnamespace std.os.wasi; |
| 7 | |
| 8 | /// Type of WASI preopen. |
| 9 | /// |
| 10 | /// WASI currently offers only `Dir` as a valid preopen resource. |
| 11 | pub const PreopenType = enum { |
| 12 | Dir, |
| 13 | }; |
| 14 | |
| 15 | /// WASI preopen struct. This struct consists of a WASI file descriptor |
| 16 | /// and type of WASI preopen. It can be obtained directly from the WASI |
| 17 | /// runtime using `PreopenList.populate()` method. |
| 18 | pub const Preopen = struct { |
| 19 | /// WASI file descriptor. |
| 20 | fd: fd_t, |
| 21 | |
| 22 | /// Type of the preopen. |
| 23 | @"type": union(PreopenType) { |
| 24 | /// Path to a preopened directory. |
| 25 | Dir: []const u8, |
| 26 | }, |
| 27 | |
| 28 | const Self = @This(); |
| 29 | |
| 30 | /// Construct new `Preopen` instance of type `PreopenType.Dir` from |
| 31 | /// WASI file descriptor and WASI path. |
| 32 | pub fn newDir(fd: fd_t, path: []const u8) Self { |
| 33 | return Self{ |
| 34 | .fd = fd, |
| 35 | .@"type" = .{ .Dir = path }, |
| 36 | }; |
| 37 | } |
| 38 | }; |
| 39 | |
| 40 | /// Dynamically-sized array list of WASI preopens. This struct is a |
| 41 | /// convenience wrapper for issuing `std.os.wasi.fd_prestat_get` and |
| 42 | /// `std.os.wasi.fd_prestat_dir_name` syscalls to the WASI runtime, and |
| 43 | /// collecting the returned preopens. |
| 44 | /// |
| 45 | /// This struct is intended to be used in any WASI program which intends |
| 46 | /// to use the capabilities as passed on by the user of the runtime. |
| 47 | pub const PreopenList = struct { |
| 48 | const InnerList = std.ArrayList(Preopen); |
| 49 | |
| 50 | /// Internal dynamically-sized buffer for storing the gathered preopens. |
| 51 | buffer: InnerList, |
| 52 | |
| 53 | const Self = @This(); |
| 54 | |
| 55 | pub const Error = os.UnexpectedError || Allocator.Error; |
| 56 | |
| 57 | /// Deinitialize with `deinit`. |
| 58 | pub fn init(allocator: *Allocator) Self { |
| 59 | return Self{ .buffer = InnerList.init(allocator) }; |
| 60 | } |
| 61 | |
| 62 | /// Release all allocated memory. |
| 63 | pub fn deinit(pm: Self) void { |
| 64 | for (pm.buffer.items) |preopen| { |
| 65 | switch (preopen.@"type") { |
| 66 | PreopenType.Dir => |path| pm.buffer.allocator.free(path), |
| 67 | } |
| 68 | } |
| 69 | pm.buffer.deinit(); |
| 70 | } |
| 71 | |
| 72 | /// Populate the list with the preopens by issuing `std.os.wasi.fd_prestat_get` |
| 73 | /// and `std.os.wasi.fd_prestat_dir_name` syscalls to the runtime. |
| 74 | /// |
| 75 | /// If called more than once, it will clear its contents every time before |
| 76 | /// issuing the syscalls. |
| 77 | pub fn populate(self: *Self) Error!void { |
| 78 | // Clear contents if we're being called again |
| 79 | for (self.toOwnedSlice()) |preopen| { |
| 80 | switch (preopen.@"type") { |
| 81 | PreopenType.Dir => |path| self.buffer.allocator.free(path), |
| 82 | } |
| 83 | } |
| 84 | errdefer self.deinit(); |
| 85 | var fd: fd_t = 3; // start fd has to be beyond stdio fds |
| 86 | |
| 87 | while (true) { |
| 88 | var buf: prestat_t = undefined; |
| 89 | switch (fd_prestat_get(fd, &buf)) { |
| 90 | ESUCCESS => {}, |
| 91 | ENOTSUP => { |
| 92 | // not a preopen, so keep going |
| 93 | continue; |
| 94 | }, |
| 95 | EBADF => { |
| 96 | // OK, no more fds available |
| 97 | break; |
| 98 | }, |
| 99 | else => |err| return os.unexpectedErrno(err), |
| 100 | } |
| 101 | const preopen_len = buf.u.dir.pr_name_len; |
| 102 | const path_buf = try self.buffer.allocator.alloc(u8, preopen_len); |
| 103 | mem.set(u8, path_buf, 0); |
| 104 | switch (fd_prestat_dir_name(fd, path_buf.ptr, preopen_len)) { |
| 105 | ESUCCESS => {}, |
| 106 | else => |err| return os.unexpectedErrno(err), |
| 107 | } |
| 108 | const preopen = Preopen.newDir(fd, path_buf); |
| 109 | try self.buffer.append(preopen); |
| 110 | fd += 1; |
| 111 | } |
| 112 | } |
| 113 | |
| 114 | /// Find preopen by path. If the preopen exists, return it. |
| 115 | /// Otherwise, return `null`. |
| 116 | /// |
| 117 | /// TODO make the function more generic by searching by `PreopenType` union. This will |
| 118 | /// be needed in the future when WASI extends its capabilities to resources |
| 119 | /// other than preopened directories. |
| 120 | pub fn find(self: *const Self, path: []const u8) ?*const Preopen { |
| 121 | for (self.buffer.items) |preopen| { |
| 122 | switch (preopen.@"type") { |
| 123 | PreopenType.Dir => |preopen_path| { |
| 124 | if (mem.eql(u8, path, preopen_path)) return &preopen; |
| 125 | }, |
| 126 | } |
| 127 | } |
| 128 | return null; |
| 129 | } |
| 130 | |
| 131 | /// Return the inner buffer as read-only slice. |
| 132 | pub fn asSlice(self: *const Self) []const Preopen { |
| 133 | return self.buffer.items; |
| 134 | } |
| 135 | |
| 136 | /// The caller owns the returned memory. ArrayList becomes empty. |
| 137 | pub fn toOwnedSlice(self: *Self) []Preopen { |
| 138 | return self.buffer.toOwnedSlice(); |
| 139 | } |
| 140 | }; |
| 141 | |
| 142 | /// Convenience wrapper for `std.os.wasi.path_open` syscall. |
| 143 | pub fn openat(dir_fd: fd_t, file_path: []const u8, oflags: oflags_t, fdflags: fdflags_t, rights: rights_t) os.OpenError!fd_t { |
| 144 | var fd: fd_t = undefined; |
| 145 | switch (path_open(dir_fd, 0x0, file_path.ptr, file_path.len, oflags, rights, 0x0, fdflags, &fd)) { |
| 146 | 0 => {}, |
| 147 | // TODO map errors |
| 148 | else => |err| return std.os.unexpectedErrno(err), |
| 149 | } |
| 150 | return fd; |
| 151 | } |