| author | |
| committer | |
| log | cf34480f2a3a1e52c09fe762dad34c45d5485717 |
| tree | e2ef01f71df812e419ee422616cc341c7a610ea2 |
| parent | 7dc29fdee19ae18270e73a4468bfd394dc6eef73 |
| parent | 81d824bf80b71fceeb01238824c272d899a364fc |
| signature |
Add mechanism for extracting preopens from the runtime2 files changed, 210 insertions(+), 0 deletions(-)
lib/std/fs.zig+59| ... | @@ -12,6 +12,7 @@ const is_darwin = std.Target.current.os.tag.isDarwin(); | ... | @@ -12,6 +12,7 @@ const is_darwin = std.Target.current.os.tag.isDarwin(); |
| 12 | 12 | ||
| 13 | pub const path = @import("fs/path.zig"); | 13 | pub const path = @import("fs/path.zig"); |
| 14 | pub const File = @import("fs/file.zig").File; | 14 | pub const File = @import("fs/file.zig").File; |
| 15 | pub const wasi = @import("fs/wasi.zig"); | ||
| 15 | 16 | ||
| 16 | // TODO audit these APIs with respect to Dir and absolute paths | 17 | // TODO audit these APIs with respect to Dir and absolute paths |
| 17 | 18 | ||
| ... | @@ -584,10 +585,37 @@ pub const Dir = struct { | ... | @@ -584,10 +585,37 @@ pub const Dir = struct { |
| 584 | const path_w = try os.windows.sliceToPrefixedFileW(sub_path); | 585 | const path_w = try os.windows.sliceToPrefixedFileW(sub_path); |
| 585 | return self.openFileW(path_w.span(), flags); | 586 | return self.openFileW(path_w.span(), flags); |
| 586 | } | 587 | } |
| 588 | if (builtin.os.tag == .wasi) { | ||
| 589 | return self.openFileWasi(sub_path, flags); | ||
| 590 | } | ||
| 587 | const path_c = try os.toPosixPath(sub_path); | 591 | const path_c = try os.toPosixPath(sub_path); |
| 588 | return self.openFileZ(&path_c, flags); | 592 | return self.openFileZ(&path_c, flags); |
| 589 | } | 593 | } |
| 590 | 594 | ||
| 595 | /// Save as `openFile` but WASI only. | ||
| 596 | pub fn openFileWasi(self: Dir, sub_path: []const u8, flags: File.OpenFlags) File.OpenError!File { | ||
| 597 | const w = os.wasi; | ||
| 598 | var fdflags: w.fdflag_t = 0x0; | ||
| 599 | var rights: w.rights_t = 0x0; | ||
| 600 | if (flags.read) { | ||
| 601 | rights |= w.FD_READ | w.FD_TELL | w.FD_FILESTAT_GET; | ||
| 602 | } | ||
| 603 | if (flags.write) { | ||
| 604 | fdflags |= w.FDFLAG_APPEND; | ||
| 605 | rights |= w.FD_WRITE | | ||
| 606 | w.FD_DATASYNC | | ||
| 607 | w.FD_SEEK | | ||
| 608 | w.FD_FDSTAT_SET_FLAGS | | ||
| 609 | w.FD_SYNC | | ||
| 610 | w.FD_ALLOCATE | | ||
| 611 | w.FD_ADVISE | | ||
| 612 | w.FD_FILESTAT_SET_TIMES | | ||
| 613 | w.FD_FILESTAT_SET_SIZE; | ||
| 614 | } | ||
| 615 | const fd = try wasi.openat(self.fd, sub_path, 0x0, fdflags, rights); | ||
| 616 | return File{ .handle = fd }; | ||
| 617 | } | ||
| 618 | |||
| 591 | pub const openFileC = @compileError("deprecated: renamed to openFileZ"); | 619 | pub const openFileC = @compileError("deprecated: renamed to openFileZ"); |
| 592 | 620 | ||
| 593 | /// Same as `openFile` but the path parameter is null-terminated. | 621 | /// Same as `openFile` but the path parameter is null-terminated. |
| ... | @@ -671,12 +699,41 @@ pub const Dir = struct { | ... | @@ -671,12 +699,41 @@ pub const Dir = struct { |
| 671 | const path_w = try os.windows.sliceToPrefixedFileW(sub_path); | 699 | const path_w = try os.windows.sliceToPrefixedFileW(sub_path); |
| 672 | return self.createFileW(path_w.span(), flags); | 700 | return self.createFileW(path_w.span(), flags); |
| 673 | } | 701 | } |
| 702 | if (builtin.os.tag == .wasi) { | ||
| 703 | return self.createFileWasi(sub_path, flags); | ||
| 704 | } | ||
| 674 | const path_c = try os.toPosixPath(sub_path); | 705 | const path_c = try os.toPosixPath(sub_path); |
| 675 | return self.createFileZ(&path_c, flags); | 706 | return self.createFileZ(&path_c, flags); |
| 676 | } | 707 | } |
| 677 | 708 | ||
| 678 | pub const createFileC = @compileError("deprecated: renamed to createFileZ"); | 709 | pub const createFileC = @compileError("deprecated: renamed to createFileZ"); |
| 679 | 710 | ||
| 711 | /// Same as `createFile` but WASI only. | ||
| 712 | pub fn createFileWasi(self: Dir, sub_path: []const u8, flags: File.CreateFlags) File.OpenError!File { | ||
| 713 | const w = os.wasi; | ||
| 714 | var oflags = w.O_CREAT; | ||
| 715 | var rights = w.RIGHT_FD_WRITE | | ||
| 716 | w.RIGHT_FD_DATASYNC | | ||
| 717 | w.RIGHT_FD_SEEK | | ||
| 718 | w.RIGHT_FD_FDSTAT_SET_FLAGS | | ||
| 719 | w.RIGHT_FD_SYNC | | ||
| 720 | w.RIGHT_FD_ALLOCATE | | ||
| 721 | w.RIGHT_FD_ADVISE | | ||
| 722 | w.RIGHT_FD_FILESTAT_SET_TIMES | | ||
| 723 | w.RIGHT_FD_FILESTAT_SET_SIZE; | ||
| 724 | if (flags.read) { | ||
| 725 | rights |= w.RIGHT_FD_READ | w.RIGHT_FD_TELL | w.RIGHT_FD_FILESTAT_GET; | ||
| 726 | } | ||
| 727 | if (flags.truncate) { | ||
| 728 | oflags |= w.O_TRUNC; | ||
| 729 | } | ||
| 730 | if (flags.exclusive) { | ||
| 731 | oflags |= w.O_EXCL; | ||
| 732 | } | ||
| 733 | const fd = try wasi.openat(self.fd, sub_path, oflags, 0x0, rights); | ||
| 734 | return File{ .handle = fd }; | ||
| 735 | } | ||
| 736 | |||
| 680 | /// Same as `createFile` but the path parameter is null-terminated. | 737 | /// Same as `createFile` but the path parameter is null-terminated. |
| 681 | pub fn createFileZ(self: Dir, sub_path_c: [*:0]const u8, flags: File.CreateFlags) File.OpenError!File { | 738 | pub fn createFileZ(self: Dir, sub_path_c: [*:0]const u8, flags: File.CreateFlags) File.OpenError!File { |
| 682 | if (builtin.os.tag == .windows) { | 739 | if (builtin.os.tag == .windows) { |
| ... | @@ -1390,6 +1447,8 @@ pub const Dir = struct { | ... | @@ -1390,6 +1447,8 @@ pub const Dir = struct { |
| 1390 | pub fn cwd() Dir { | 1447 | pub fn cwd() Dir { |
| 1391 | if (builtin.os.tag == .windows) { | 1448 | if (builtin.os.tag == .windows) { |
| 1392 | return Dir{ .fd = os.windows.peb().ProcessParameters.CurrentDirectory.Handle }; | 1449 | return Dir{ .fd = os.windows.peb().ProcessParameters.CurrentDirectory.Handle }; |
| 1450 | } else if (builtin.os.tag == .wasi) { | ||
| 1451 | @compileError("WASI doesn't have a concept of cwd; use TODO instead"); | ||
| 1393 | } else { | 1452 | } else { |
| 1394 | return Dir{ .fd = os.AT_FDCWD }; | 1453 | return Dir{ .fd = os.AT_FDCWD }; |
| 1395 | } | 1454 | } |
lib/std/fs/wasi.zig created+151| ... | @@ -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 | } | ||