mirror of
https://github.com/ziglang/zig.git
synced 2024-11-15 16:45:27 +00:00
d43c08a3e5
This rather large commit adds/fixes missing WASI functionality in `libstd` needed to pass the `libstd` tests. As such, now by default tests targeting `wasm32-wasi` target are enabled in `test/tests.zig` module. However, they can be disabled by passing the `-Dskip-wasi=true` flag when invoking the `zig build test` command. When the flag is set to `false`, i.e., when WASI tests are included, `wasmtime` with `--dir=.` is used as the default testing command. Since the majority of `libstd` tests were relying on `fs.cwd()` call to get current working directory handle wrapped in `Dir` struct, in order to make the tests WASI-friendly, `fs.cwd()` call was replaced with `testing.getTestDir()` function which resolved to either `fs.cwd()` for non-WASI targets, or tries to fetch the preopen list from the WASI runtime and extract a preopen for '.' path. The summary of changes introduced by this commit: * implement `Dir.makeDir` and `Dir.openDir` targeting WASI * implement `Dir.deleteFile` and `Dir.deleteDir` targeting WASI * fix `os.close` and map errors in `unlinkat` * move WASI-specific `mkdirat` and `unlinkat` from `std.fs.wasi` to `std.os` module * implement `lseek_{SET, CUR, END}` targeting WASI * implement `futimens` targeting WASI * implement `ftruncate` targeting WASI * implement `readv`, `writev`, `pread{v}`, `pwrite{v}` targeting WASI * make sure ANSI escape codes are _not_ used in stderr or stdout in WASI, as WASI always sanitizes stderr, and sanitizes stdout if fd is a TTY * fix specifying WASI rights when opening/creating files/dirs * tweak `AtomicFile` to be WASI-compatible * implement `os.renameatWasi` for WASI-compliant `os.renameat` function * implement sleep() targeting WASI * fix `process.getEnvMap` targeting WASI
141 lines
4.6 KiB
Zig
141 lines
4.6 KiB
Zig
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();
|
|
}
|
|
};
|