zig/lib/std/fs/wasi.zig
Jakub Konka 66e5205047 Refactor PreopenList.find()
This commit generalizes `std.fs.wasi.PreopenList.find(...)` allowing
search by `std.fs.wasi.PreopenType` union type rather than by dir
name. In the future releases of WASI, it is expected to have more
preopen types (or capabilities) than just directories. This commit
aligns itself with that vision.

This is a potentially breaking change. However, since `std.fs.wasi.PreopenList`
wasn't made part of any Zig release yet, I think we should be OK
to introduce those changes without pointing to any deprecations.
2020-06-23 21:54:36 +02:00

170 lines
5.5 KiB
Zig

const std = @import("std");
const os = std.os;
const mem = std.mem;
const Allocator = mem.Allocator;
usingnamespace std.os.wasi;
/// Type-tag of WASI preopen.
///
/// WASI currently offers only `Dir` as a valid preopen resource.
pub const PreopenTypeTag = enum {
Dir,
};
/// Type of WASI preopen.
///
/// WASI currently offers only `Dir` as a valid preopen resource.
pub const PreopenType = union(PreopenTypeTag) {
/// Preopened directory type.
Dir: []const u8,
const Self = @This();
pub fn eql(self: Self, other: PreopenType) bool {
if (!mem.eql(u8, @tagName(self), @tagName(other))) return false;
switch (self) {
PreopenTypeTag.Dir => |this_path| return mem.eql(u8, this_path, other.Dir),
}
}
pub fn format(self: Self, comptime fmt: []const u8, options: std.fmt.FormatOptions, out_stream: var) !void {
try out_stream.print("PreopenType{{ ", .{});
switch (self) {
PreopenType.Dir => |path| try out_stream.print(".Dir = '{}'", .{path}),
}
return out_stream.print(" }}", .{});
}
};
/// 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": PreopenType,
/// Construct new `Preopen` instance.
pub fn new(fd: fd_t, preopen_type: PreopenType) Preopen {
return Preopen{
.fd = fd,
.@"type" = preopen_type,
};
}
};
/// 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.new(fd, PreopenType{ .Dir = path_buf });
try self.buffer.append(preopen);
fd += 1;
}
}
/// Find preopen by type. If the preopen exists, return it.
/// Otherwise, return `null`.
pub fn find(self: Self, preopen_type: PreopenType) ?*const Preopen {
for (self.buffer.items) |*preopen| {
if (preopen.@"type".eql(preopen_type)) {
return preopen;
}
}
return null;
}
/// Return the inner buffer as read-only slice.
pub fn asSlice(self: 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();
}
};
test "extracting WASI preopens" {
if (@import("builtin").os.tag != .wasi) return error.SkipZigTest;
var preopens = PreopenList.init(std.testing.allocator);
defer preopens.deinit();
try preopens.populate();
std.testing.expectEqual(@as(usize, 1), preopens.asSlice().len);
const preopen = preopens.find(PreopenType{ .Dir = "." }) orelse unreachable;
std.debug.print("\n{}\n", .{preopen});
std.testing.expect(!preopen.@"type".eql(PreopenType{ .Dir = "." }));
std.testing.expectEqual(@as(usize, 3), preopen.fd);
}