Merge pull request #5231 from kubkon/wasi-preopens
Add mechanism for extracting preopens from the runtime
This commit is contained in:
commit
cf34480f2a
@ -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 };
|
||||
}
|
||||
|
151
lib/std/fs/wasi.zig
Normal file
151
lib/std/fs/wasi.zig
Normal file
@ -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;
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user