authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-05-16 12:49:08-04:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2020-05-16 12:49:08-04:00
logcf34480f2a3a1e52c09fe762dad34c45d5485717
treee2ef01f71df812e419ee422616cc341c7a610ea2
parent7dc29fdee19ae18270e73a4468bfd394dc6eef73
parent81d824bf80b71fceeb01238824c272d899a364fc
signature Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #5231 from kubkon/wasi-preopens

Add mechanism for extracting preopens from the runtime

2 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();
1212
13pub const path = @import("fs/path.zig");13pub const path = @import("fs/path.zig");
14pub const File = @import("fs/file.zig").File;14pub const File = @import("fs/file.zig").File;
15pub const wasi = @import("fs/wasi.zig");
1516
16// TODO audit these APIs with respect to Dir and absolute paths17// TODO audit these APIs with respect to Dir and absolute paths
1718
...@@ -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 }
590594
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");
592620
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 }
677708
678 pub const createFileC = @compileError("deprecated: renamed to createFileZ");709 pub const createFileC = @compileError("deprecated: renamed to createFileZ");
679710
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 {
1390pub fn cwd() Dir {1447pub 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 @@
1const std = @import("std");
2const os = std.os;
3const mem = std.mem;
4const Allocator = mem.Allocator;
5
6usingnamespace std.os.wasi;
7
8/// Type of WASI preopen.
9///
10/// WASI currently offers only `Dir` as a valid preopen resource.
11pub 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.
18pub 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.
47pub 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.
143pub 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}