aboutsummaryrefslogtreecommitdiff
path: root/lib/std
diff options
context:
space:
mode:
authorJakub Konka <kubkon@jakubkonka.com>2022-03-05 20:31:44 +0100
committerGitHub <noreply@github.com>2022-03-05 20:31:44 +0100
commit908f41a67cbd377eb15d07a27778dbbca0a4309f (patch)
tree7037fdf5c2352d72115f5b42b131ebfd1387abc7 /lib/std
parentac936c0aba94c2cf5ff8537c1808445eacf38a15 (diff)
parentaafcd8eab3c6046a915f26aed766090ee219d920 (diff)
downloadzig-908f41a67cbd377eb15d07a27778dbbca0a4309f.tar.gz
zig-908f41a67cbd377eb15d07a27778dbbca0a4309f.zip
Merge pull request #11021 from topolarity/wasi-cwd
stdlib: Add emulated CWD to std.os for WASI targets
Diffstat (limited to 'lib/std')
-rw-r--r--lib/std/c/wasi.zig10
-rw-r--r--lib/std/child_process.zig1
-rw-r--r--lib/std/fs.zig72
-rw-r--r--lib/std/fs/path.zig13
-rw-r--r--lib/std/fs/test.zig22
-rw-r--r--lib/std/fs/wasi.zig66
-rw-r--r--lib/std/os.zig550
-rw-r--r--lib/std/os/test.zig133
-rw-r--r--lib/std/os/wasi.zig5
-rw-r--r--lib/std/zig/system/NativeTargetInfo.zig2
10 files changed, 778 insertions, 96 deletions
diff --git a/lib/std/c/wasi.zig b/lib/std/c/wasi.zig
index c3635784dd..e1940054b6 100644
--- a/lib/std/c/wasi.zig
+++ b/lib/std/c/wasi.zig
@@ -62,21 +62,21 @@ pub const Stat = extern struct {
/// https://github.com/WebAssembly/wasi-libc/blob/main/expected/wasm32-wasi/predefined-macros.txt
pub const O = struct {
pub const ACCMODE = (EXEC | RDWR | SEARCH);
- pub const APPEND = FDFLAG.APPEND;
+ pub const APPEND = @as(u32, FDFLAG.APPEND);
pub const CLOEXEC = (0);
pub const CREAT = ((1 << 0) << 12); // = __WASI_OFLAGS_CREAT << 12
pub const DIRECTORY = ((1 << 1) << 12); // = __WASI_OFLAGS_DIRECTORY << 12
- pub const DSYNC = FDFLAG.DSYNC;
+ pub const DSYNC = @as(u32, FDFLAG.DSYNC);
pub const EXCL = ((1 << 2) << 12); // = __WASI_OFLAGS_EXCL << 12
pub const EXEC = (0x02000000);
pub const NOCTTY = (0);
pub const NOFOLLOW = (0x01000000);
- pub const NONBLOCK = (1 << FDFLAG.NONBLOCK);
+ pub const NONBLOCK = @as(u32, FDFLAG.NONBLOCK);
pub const RDONLY = (0x04000000);
pub const RDWR = (RDONLY | WRONLY);
- pub const RSYNC = (1 << FDFLAG.RSYNC);
+ pub const RSYNC = @as(u32, FDFLAG.RSYNC);
pub const SEARCH = (0x08000000);
- pub const SYNC = (1 << FDFLAG.SYNC);
+ pub const SYNC = @as(u32, FDFLAG.SYNC);
pub const TRUNC = ((1 << 3) << 12); // = __WASI_OFLAGS_TRUNC << 12
pub const TTY_INIT = (0);
pub const WRONLY = (0x10000000);
diff --git a/lib/std/child_process.zig b/lib/std/child_process.zig
index 236ce45619..e8e348d67b 100644
--- a/lib/std/child_process.zig
+++ b/lib/std/child_process.zig
@@ -563,6 +563,7 @@ pub const ChildProcess = struct {
error.DeviceBusy => unreachable,
error.FileLocksNotSupported => unreachable,
error.BadPathName => unreachable, // Windows-only
+ error.InvalidHandle => unreachable, // WASI-only
error.WouldBlock => unreachable,
else => |e| return e,
}
diff --git a/lib/std/fs.zig b/lib/std/fs.zig
index 758a2bd132..7a41bdf6a1 100644
--- a/lib/std/fs.zig
+++ b/lib/std/fs.zig
@@ -923,6 +923,7 @@ pub const Dir = struct {
pub const OpenError = error{
FileNotFound,
NotDir,
+ InvalidHandle,
AccessDenied,
SymLinkLoop,
ProcessFdQuotaExceeded,
@@ -981,6 +982,13 @@ pub const Dir = struct {
w.RIGHT.FD_FILESTAT_SET_TIMES |
w.RIGHT.FD_FILESTAT_SET_SIZE;
}
+ if (self.fd == os.wasi.AT.FDCWD or path.isAbsolute(sub_path)) {
+ // Resolve absolute or CWD-relative paths to a path within a Preopen
+ var resolved_path_buf: [MAX_PATH_BYTES]u8 = undefined;
+ const resolved_path = try os.resolvePathWasi(sub_path, &resolved_path_buf);
+ const fd = try os.openatWasi(resolved_path.dir_fd, resolved_path.relative_path, 0x0, 0x0, fdflags, base, 0x0);
+ return File{ .handle = fd };
+ }
const fd = try os.openatWasi(self.fd, sub_path, 0x0, 0x0, fdflags, base, 0x0);
return File{ .handle = fd };
}
@@ -1145,6 +1153,13 @@ pub const Dir = struct {
if (flags.exclusive) {
oflags |= w.O.EXCL;
}
+ if (self.fd == os.wasi.AT.FDCWD or path.isAbsolute(sub_path)) {
+ // Resolve absolute or CWD-relative paths to a path within a Preopen
+ var resolved_path_buf: [MAX_PATH_BYTES]u8 = undefined;
+ const resolved_path = try os.resolvePathWasi(sub_path, &resolved_path_buf);
+ const fd = try os.openatWasi(resolved_path.dir_fd, resolved_path.relative_path, 0x0, oflags, 0x0, base, 0x0);
+ return File{ .handle = fd };
+ }
const fd = try os.openatWasi(self.fd, sub_path, 0x0, oflags, 0x0, base, 0x0);
return File{ .handle = fd };
}
@@ -1330,7 +1345,19 @@ pub const Dir = struct {
/// See also `Dir.realpathZ`, `Dir.realpathW`, and `Dir.realpathAlloc`.
pub fn realpath(self: Dir, pathname: []const u8, out_buffer: []u8) ![]u8 {
if (builtin.os.tag == .wasi) {
- @compileError("realpath is unsupported in WASI");
+ if (self.fd == os.wasi.AT.FDCWD or path.isAbsolute(pathname)) {
+ var buffer: [MAX_PATH_BYTES]u8 = undefined;
+ const out_path = try os.realpath(pathname, &buffer);
+ if (out_path.len > out_buffer.len) {
+ return error.NameTooLong;
+ }
+ mem.copy(u8, out_buffer, out_path);
+ return out_buffer[0..out_path.len];
+ } else {
+ // Unfortunately, we have no ability to look up the path for an fd_t
+ // on WASI, so we have to give up here.
+ return error.InvalidHandle;
+ }
}
if (builtin.os.tag == .windows) {
const pathname_w = try os.windows.sliceToPrefixedFileW(pathname);
@@ -1507,7 +1534,16 @@ pub const Dir = struct {
// TODO do we really need all the rights here?
const inheriting: w.rights_t = w.RIGHT.ALL ^ w.RIGHT.SOCK_SHUTDOWN;
- const result = os.openatWasi(self.fd, sub_path, symlink_flags, w.O.DIRECTORY, 0x0, base, inheriting);
+ const result = blk: {
+ if (self.fd == os.wasi.AT.FDCWD or path.isAbsolute(sub_path)) {
+ // Resolve absolute or CWD-relative paths to a path within a Preopen
+ var resolved_path_buf: [MAX_PATH_BYTES]u8 = undefined;
+ const resolved_path = try os.resolvePathWasi(sub_path, &resolved_path_buf);
+ break :blk os.openatWasi(resolved_path.dir_fd, resolved_path.relative_path, symlink_flags, w.O.DIRECTORY, 0x0, base, inheriting);
+ } else {
+ break :blk os.openatWasi(self.fd, sub_path, symlink_flags, w.O.DIRECTORY, 0x0, base, inheriting);
+ }
+ };
const fd = result catch |err| switch (err) {
error.FileTooBig => unreachable, // can't happen for directories
error.IsDir => unreachable, // we're providing O.DIRECTORY
@@ -1622,7 +1658,7 @@ pub const Dir = struct {
const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path);
return self.deleteFileW(sub_path_w.span());
} else if (builtin.os.tag == .wasi and !builtin.link_libc) {
- os.unlinkatWasi(self.fd, sub_path, 0) catch |err| switch (err) {
+ os.unlinkat(self.fd, sub_path, 0) catch |err| switch (err) {
error.DirNotEmpty => unreachable, // not passing AT.REMOVEDIR
else => |e| return e,
};
@@ -1761,7 +1797,7 @@ pub const Dir = struct {
sym_link_path: []const u8,
_: SymLinkFlags,
) !void {
- return os.symlinkatWasi(target_path, self.fd, sym_link_path);
+ return os.symlinkat(target_path, self.fd, sym_link_path);
}
/// Same as `symLink`, except the pathname parameters are null-terminated.
@@ -1807,7 +1843,7 @@ pub const Dir = struct {
/// WASI-only. Same as `readLink` except targeting WASI.
pub fn readLinkWasi(self: Dir, sub_path: []const u8, buffer: []u8) ![]u8 {
- return os.readlinkatWasi(self.fd, sub_path, buffer);
+ return os.readlinkat(self.fd, sub_path, buffer);
}
/// Same as `readLink`, except the `pathname` parameter is null-terminated.
@@ -1870,6 +1906,7 @@ pub const Dir = struct {
}
pub const DeleteTreeError = error{
+ InvalidHandle,
AccessDenied,
FileTooBig,
SymLinkLoop,
@@ -1935,6 +1972,7 @@ pub const Dir = struct {
continue :start_over;
},
+ error.InvalidHandle,
error.AccessDenied,
error.SymLinkLoop,
error.ProcessFdQuotaExceeded,
@@ -2002,6 +2040,7 @@ pub const Dir = struct {
continue :scan_dir;
},
+ error.InvalidHandle,
error.AccessDenied,
error.SymLinkLoop,
error.ProcessFdQuotaExceeded,
@@ -2272,8 +2311,6 @@ 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 and !builtin.link_libc) {
- @compileError("WASI doesn't have a concept of cwd(); use std.fs.wasi.PreopenList to get available Dir handles instead");
} else {
return Dir{ .fd = os.AT.FDCWD };
}
@@ -2285,26 +2322,17 @@ pub fn cwd() Dir {
///
/// Asserts that the path parameter has no null bytes.
pub fn openDirAbsolute(absolute_path: []const u8, flags: Dir.OpenDirOptions) File.OpenError!Dir {
- if (builtin.os.tag == .wasi) {
- @compileError("WASI doesn't have the concept of an absolute directory; use openDir instead for WASI.");
- }
assert(path.isAbsolute(absolute_path));
return cwd().openDir(absolute_path, flags);
}
/// Same as `openDirAbsolute` but the path parameter is null-terminated.
pub fn openDirAbsoluteZ(absolute_path_c: [*:0]const u8, flags: Dir.OpenDirOptions) File.OpenError!Dir {
- if (builtin.os.tag == .wasi) {
- @compileError("WASI doesn't have the concept of an absolute directory; use openDir instead for WASI.");
- }
assert(path.isAbsoluteZ(absolute_path_c));
return cwd().openDirZ(absolute_path_c, flags);
}
/// Same as `openDirAbsolute` but the path parameter is null-terminated.
pub fn openDirAbsoluteW(absolute_path_c: [*:0]const u16, flags: Dir.OpenDirOptions) File.OpenError!Dir {
- if (builtin.os.tag == .wasi) {
- @compileError("WASI doesn't have the concept of an absolute directory; use openDir instead for WASI.");
- }
assert(path.isAbsoluteWindowsW(absolute_path_c));
return cwd().openDirW(absolute_path_c, flags);
}
@@ -2339,25 +2367,16 @@ pub fn openFileAbsoluteW(absolute_path_w: []const u16, flags: File.OpenFlags) Fi
/// open it and handle the error for file not found.
/// See `accessAbsoluteZ` for a function that accepts a null-terminated path.
pub fn accessAbsolute(absolute_path: []const u8, flags: File.OpenFlags) Dir.AccessError!void {
- if (builtin.os.tag == .wasi) {
- @compileError("WASI doesn't have the concept of an absolute path; use access instead for WASI.");
- }
assert(path.isAbsolute(absolute_path));
try cwd().access(absolute_path, flags);
}
/// Same as `accessAbsolute` but the path parameter is null-terminated.
pub fn accessAbsoluteZ(absolute_path: [*:0]const u8, flags: File.OpenFlags) Dir.AccessError!void {
- if (builtin.os.tag == .wasi) {
- @compileError("WASI doesn't have the concept of an absolute path; use access instead for WASI.");
- }
assert(path.isAbsoluteZ(absolute_path));
try cwd().accessZ(absolute_path, flags);
}
/// Same as `accessAbsolute` but the path parameter is WTF-16 encoded.
pub fn accessAbsoluteW(absolute_path: [*:0]const 16, flags: File.OpenFlags) Dir.AccessError!void {
- if (builtin.os.tag == .wasi) {
- @compileError("WASI doesn't have the concept of an absolute path; use access instead for WASI.");
- }
assert(path.isAbsoluteWindowsW(absolute_path));
try cwd().accessW(absolute_path, flags);
}
@@ -2458,9 +2477,6 @@ pub const SymLinkFlags = struct {
/// If `sym_link_path` exists, it will not be overwritten.
/// See also `symLinkAbsoluteZ` and `symLinkAbsoluteW`.
pub fn symLinkAbsolute(target_path: []const u8, sym_link_path: []const u8, flags: SymLinkFlags) !void {
- if (builtin.os.tag == .wasi) {
- @compileError("symLinkAbsolute is not supported in WASI; use Dir.symLinkWasi instead");
- }
assert(path.isAbsolute(target_path));
assert(path.isAbsolute(sym_link_path));
if (builtin.os.tag == .windows) {
diff --git a/lib/std/fs/path.zig b/lib/std/fs/path.zig
index fbf5bd99ee..bdd1c53ed5 100644
--- a/lib/std/fs/path.zig
+++ b/lib/std/fs/path.zig
@@ -8,6 +8,7 @@ const fmt = std.fmt;
const Allocator = mem.Allocator;
const math = std.math;
const windows = std.os.windows;
+const os = std.os;
const fs = std.fs;
const process = std.process;
const native_os = builtin.target.os.tag;
@@ -733,7 +734,8 @@ pub fn resolvePosix(allocator: Allocator, paths: []const []const u8) ![]u8 {
}
test "resolve" {
- if (native_os == .wasi) return error.SkipZigTest;
+ if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest;
+ if (native_os == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, ".");
const cwd = try process.getCwdAlloc(testing.allocator);
defer testing.allocator.free(cwd);
@@ -753,7 +755,8 @@ test "resolveWindows" {
// TODO https://github.com/ziglang/zig/issues/3288
return error.SkipZigTest;
}
- if (native_os == .wasi) return error.SkipZigTest;
+ if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest;
+ if (native_os == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, ".");
if (native_os == .windows) {
const cwd = try process.getCwdAlloc(testing.allocator);
defer testing.allocator.free(cwd);
@@ -798,7 +801,8 @@ test "resolveWindows" {
}
test "resolvePosix" {
- if (native_os == .wasi) return error.SkipZigTest;
+ if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest;
+ if (native_os == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, ".");
try testResolvePosix(&[_][]const u8{ "/a/b", "c" }, "/a/b/c");
try testResolvePosix(&[_][]const u8{ "/a/b", "c", "//d", "e///" }, "/d/e");
@@ -1211,7 +1215,8 @@ test "relative" {
// TODO https://github.com/ziglang/zig/issues/3288
return error.SkipZigTest;
}
- if (native_os == .wasi) return error.SkipZigTest;
+ if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest;
+ if (native_os == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, ".");
try testRelativeWindows("c:/blah\\blah", "d:/games", "D:\\games");
try testRelativeWindows("c:/aaaa/bbbb", "c:/aaaa", "..");
diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig
index b00b0609b2..69fbe5449f 100644
--- a/lib/std/fs/test.zig
+++ b/lib/std/fs/test.zig
@@ -1,6 +1,7 @@
const std = @import("../std.zig");
const builtin = @import("builtin");
const testing = std.testing;
+const os = std.os;
const fs = std.fs;
const mem = std.mem;
const wasi = std.os.wasi;
@@ -45,7 +46,8 @@ fn testReadLink(dir: Dir, target_path: []const u8, symlink_path: []const u8) !vo
}
test "accessAbsolute" {
- if (builtin.os.tag == .wasi) return error.SkipZigTest;
+ if (builtin.os.tag == .wasi and builtin.link_libc) return error.SkipZigTest;
+ if (builtin.os.tag == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, ".");
var tmp = tmpDir(.{});
defer tmp.cleanup();
@@ -63,7 +65,8 @@ test "accessAbsolute" {
}
test "openDirAbsolute" {
- if (builtin.os.tag == .wasi) return error.SkipZigTest;
+ if (builtin.os.tag == .wasi and builtin.link_libc) return error.SkipZigTest;
+ if (builtin.os.tag == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, ".");
var tmp = tmpDir(.{});
defer tmp.cleanup();
@@ -99,7 +102,8 @@ test "openDir cwd parent .." {
}
test "readLinkAbsolute" {
- if (builtin.os.tag == .wasi) return error.SkipZigTest;
+ if (builtin.os.tag == .wasi and builtin.link_libc) return error.SkipZigTest;
+ if (builtin.os.tag == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, ".");
var tmp = tmpDir(.{});
defer tmp.cleanup();
@@ -507,7 +511,8 @@ test "rename" {
}
test "renameAbsolute" {
- if (builtin.os.tag == .wasi) return error.SkipZigTest;
+ if (builtin.os.tag == .wasi and builtin.link_libc) return error.SkipZigTest;
+ if (builtin.os.tag == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, ".");
var tmp_dir = tmpDir(.{});
defer tmp_dir.cleanup();
@@ -941,7 +946,8 @@ test "open file with exclusive nonblocking lock twice (absolute paths)" {
}
test "walker" {
- if (builtin.os.tag == .wasi) return error.SkipZigTest;
+ if (builtin.os.tag == .wasi and builtin.link_libc) return error.SkipZigTest;
+ if (builtin.os.tag == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, ".");
var tmp = tmpDir(.{ .iterate = true });
defer tmp.cleanup();
@@ -991,7 +997,8 @@ test "walker" {
}
test ". and .. in fs.Dir functions" {
- if (builtin.os.tag == .wasi) return error.SkipZigTest;
+ if (builtin.os.tag == .wasi and builtin.link_libc) return error.SkipZigTest;
+ if (builtin.os.tag == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, ".");
var tmp = tmpDir(.{});
defer tmp.cleanup();
@@ -1019,7 +1026,8 @@ test ". and .. in fs.Dir functions" {
}
test ". and .. in absolute functions" {
- if (builtin.os.tag == .wasi) return error.SkipZigTest;
+ if (builtin.os.tag == .wasi and builtin.link_libc) return error.SkipZigTest;
+ if (builtin.os.tag == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, ".");
var tmp = tmpDir(.{});
defer tmp.cleanup();
diff --git a/lib/std/fs/wasi.zig b/lib/std/fs/wasi.zig
index 1a033653d3..4754ded630 100644
--- a/lib/std/fs/wasi.zig
+++ b/lib/std/fs/wasi.zig
@@ -25,13 +25,29 @@ pub const PreopenType = union(PreopenTypeTag) {
const Self = @This();
pub fn eql(self: Self, other: PreopenType) bool {
- if (!mem.eql(u8, @tagName(self), @tagName(other))) return false;
+ if (std.meta.activeTag(self) != std.meta.activeTag(other)) return false;
switch (self) {
PreopenTypeTag.Dir => |this_path| return mem.eql(u8, this_path, other.Dir),
}
}
+ // Checks whether `other` refers to a subdirectory of `self` and, if so,
+ // returns the relative path to `other` from `self`
+ pub fn getRelativePath(self: Self, other: PreopenType) ?[]const u8 {
+ if (std.meta.activeTag(self) != std.meta.activeTag(other)) return null;
+
+ switch (self) {
+ PreopenTypeTag.Dir => |this_path| {
+ const other_path = other.Dir;
+ if (mem.indexOfDiff(u8, this_path, other_path)) |index| {
+ if (index < this_path.len) return null;
+ }
+ return other_path[this_path.len..];
+ },
+ }
+ }
+
pub fn format(self: Self, comptime fmt: []const u8, options: std.fmt.FormatOptions, out_stream: anytype) !void {
_ = fmt;
_ = options;
@@ -62,6 +78,15 @@ pub const Preopen = struct {
}
};
+/// WASI resource identifier struct. This is effectively a path within
+/// a WASI Preopen.
+pub const PreopenUri = struct {
+ /// WASI Preopen containing the resource.
+ base: Preopen,
+ /// Path to resource within `base`.
+ relative_path: []const u8,
+};
+
/// 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
@@ -137,12 +162,49 @@ pub const PreopenList = struct {
.SUCCESS => {},
else => |err| return os.unexpectedErrno(err),
}
+
const preopen = Preopen.new(fd, PreopenType{ .Dir = path_buf });
try self.buffer.append(preopen);
fd = try math.add(fd_t, fd, 1);
}
}
+ /// Find a preopen which includes access to `preopen_type`.
+ ///
+ /// If the preopen exists, `relative_path` is updated to point to the relative
+ /// portion of `preopen_type` and the matching Preopen is returned. If multiple
+ /// preopens match the provided resource, the most recent one is used.
+ pub fn findContaining(self: Self, preopen_type: PreopenType) ?PreopenUri {
+ // Search in reverse, so that most recently added preopens take precedence
+ var k: usize = self.buffer.items.len;
+ while (k > 0) {
+ k -= 1;
+
+ const preopen = self.buffer.items[k];
+ if (preopen.@"type".getRelativePath(preopen_type)) |rel_path_orig| {
+ var rel_path = rel_path_orig;
+ while (rel_path.len > 0 and rel_path[0] == '/') rel_path = rel_path[1..];
+
+ return PreopenUri{
+ .base = preopen,
+ .relative_path = if (rel_path.len == 0) "." else rel_path,
+ };
+ }
+ }
+ return null;
+ }
+
+ /// Find preopen by fd. If the preopen exists, return it.
+ /// Otherwise, return `null`.
+ pub fn findByFd(self: Self, fd: fd_t) ?Preopen {
+ for (self.buffer.items) |preopen| {
+ if (preopen.fd == fd) {
+ return preopen;
+ }
+ }
+ return null;
+ }
+
/// Find preopen by type. If the preopen exists, return it.
/// Otherwise, return `null`.
pub fn find(self: Self, preopen_type: PreopenType) ?*const Preopen {
@@ -173,8 +235,6 @@ test "extracting WASI preopens" {
try preopens.populate();
- try std.testing.expectEqual(@as(usize, 1), preopens.asSlice().len);
const preopen = preopens.find(PreopenType{ .Dir = "." }) orelse unreachable;
try std.testing.expect(preopen.@"type".eql(PreopenType{ .Dir = "." }));
- try std.testing.expectEqual(@as(i32, 3), preopen.fd);
}
diff --git a/lib/std/os.zig b/lib/std/os.zig
index 3e230b773d..241c38dedf 100644
--- a/lib/std/os.zig
+++ b/lib/std/os.zig
@@ -21,9 +21,13 @@ const assert = std.debug.assert;
const math = std.math;
const mem = std.mem;
const elf = std.elf;
+const fs = std.fs;
const dl = @import("dynamic_library.zig");
const MAX_PATH_BYTES = std.fs.MAX_PATH_BYTES;
const is_windows = builtin.os.tag == .windows;
+const Allocator = std.mem.Allocator;
+const Preopen = std.fs.wasi.Preopen;
+const PreopenList = std.fs.wasi.PreopenList;
pub const darwin = std.c;
pub const dragonfly = std.c;
@@ -93,7 +97,12 @@ pub const MAX_ADDR_LEN = system.MAX_ADDR_LEN;
pub const MMAP2_UNIT = system.MMAP2_UNIT;
pub const MSG = system.MSG;
pub const NAME_MAX = system.NAME_MAX;
-pub const O = system.O;
+pub const O = switch (builtin.os.tag) {
+ // We want to expose the POSIX-like OFLAGS, so we use std.c.wasi.O instead
+ // of std.os.wasi.O, which is for non-POSIX-like `wasi.path_open`, etc.
+ .wasi => std.c.O,
+ else => system.O,
+};
pub const PATH_MAX = system.PATH_MAX;
pub const POLL = system.POLL;
pub const POSIX_FADV = system.POSIX_FADV;
@@ -210,6 +219,17 @@ pub const LOG = struct {
pub const DEBUG = 7;
};
+/// An fd-relative file path
+///
+/// This is currently only used for WASI-specific functionality, but the concept
+/// is the same as the dirfd/pathname pairs in the `*at(...)` POSIX functions.
+pub const RelativePathWasi = struct {
+ /// Handle to directory
+ dir_fd: fd_t,
+ /// Path to resource within `dir_fd`.
+ relative_path: []const u8,
+};
+
pub const socket_t = if (builtin.os.tag == .windows) windows.ws2_32.SOCKET else fd_t;
/// See also `getenv`. Populated by startup code before main().
@@ -1239,6 +1259,9 @@ pub fn pwritev(fd: fd_t, iov: []const iovec_const, offset: u64) PWriteError!usiz
}
pub const OpenError = error{
+ /// In WASI, this error may occur when the provided file handle is invalid.
+ InvalidHandle,
+
/// In WASI, this error may occur when the file descriptor does
/// not hold the required rights to open a new resource relative to it.
AccessDenied,
@@ -1300,6 +1323,8 @@ pub fn open(file_path: []const u8, flags: u32, perm: mode_t) OpenError!fd_t {
if (builtin.os.tag == .windows) {
const file_path_w = try windows.sliceToPrefixedFileW(file_path);
return openW(file_path_w.span(), flags, perm);
+ } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
+ return openat(wasi.AT.FDCWD, file_path, flags, perm);
}
const file_path_c = try toPosixPath(file_path);
return openZ(&file_path_c, flags, perm);
@@ -1311,6 +1336,8 @@ pub fn openZ(file_path: [*:0]const u8, flags: u32, perm: mode_t) OpenError!fd_t
if (builtin.os.tag == .windows) {
const file_path_w = try windows.cStrToPrefixedFileW(file_path);
return openW(file_path_w.span(), flags, perm);
+ } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
+ return open(mem.sliceTo(file_path, 0), flags, perm);
}
const open_sym = if (builtin.os.tag == .linux and builtin.link_libc)
@@ -1347,7 +1374,7 @@ pub fn openZ(file_path: [*:0]const u8, flags: u32, perm: mode_t) OpenError!fd_t
}
}
-fn openOptionsFromFlags(flags: u32) windows.OpenFileOptions {
+fn openOptionsFromFlagsWindows(flags: u32) windows.OpenFileOptions {
const w = windows;
var access_mask: w.ULONG = w.READ_CONTROL | w.FILE_WRITE_ATTRIBUTES | w.SYNCHRONIZE;
@@ -1387,7 +1414,7 @@ fn openOptionsFromFlags(flags: u32) windows.OpenFileOptions {
/// or makes use of perm argument.
pub fn openW(file_path_w: []const u16, flags: u32, perm: mode_t) OpenError!fd_t {
_ = perm;
- var options = openOptionsFromFlags(flags);
+ var options = openOptionsFromFlagsWindows(flags);
options.dir = std.fs.cwd().fd;
return windows.OpenFile(file_path_w, options) catch |err| switch (err) {
error.WouldBlock => unreachable,
@@ -1396,21 +1423,204 @@ pub fn openW(file_path_w: []const u16, flags: u32, perm: mode_t) OpenError!fd_t
};
}
+var wasi_cwd = if (builtin.os.tag == .wasi and !builtin.link_libc) struct {
+ // List of available Preopens
+ preopens: ?PreopenList = null,
+ // Memory buffer for storing the relative portion of the CWD
+ path_buffer: [MAX_PATH_BYTES]u8 = undefined,
+ // Current Working Directory, stored as an fd_t and a relative path
+ cwd: ?RelativePathWasi = null,
+ // Preopen associated with `cwd`, if any
+ cwd_preopen: ?Preopen = null,
+}{} else undefined;
+
+/// Initialize the available Preopen list on WASI and set the CWD to `cwd_init`.
+/// Note that `cwd_init` corresponds to a Preopen directory, not necessarily
+/// a POSIX path. For example, "." matches a Preopen provided with `--dir=.`
+///
+/// This must be called before using any relative or absolute paths with `std.os`
+/// functions, if you are on WASI without linking libc.
+///
+/// `alloc` must not be a temporary or leak-detecting allocator, since `std.os`
+/// retains ownership of allocations internally and may never call free().
+pub fn initPreopensWasi(alloc: Allocator, cwd_init: ?[]const u8) !void {
+ if (builtin.os.tag == .wasi) {
+ if (!builtin.link_libc) {
+ if (wasi_cwd.preopens == null) {
+ var preopen_list = PreopenList.init(alloc);
+ try preopen_list.populate();
+ wasi_cwd.preopens = preopen_list;
+ }
+ if (cwd_init) |cwd| {
+ const preopen = wasi_cwd.preopens.?.findContaining(.{ .Dir = cwd });
+ if (preopen) |po| {
+ wasi_cwd.cwd_preopen = po.base;
+ wasi_cwd.cwd = RelativePathWasi{
+ .dir_fd = po.base.fd,
+ .relative_path = po.relative_path,
+ };
+ } else {
+ // No matching preopen found
+ return error.FileNotFound;
+ }
+ }
+ } else {
+ if (cwd_init) |cwd| try chdir(cwd);
+ }
+ }
+}
+
+/// Resolve a relative or absolute path to an handle (`fd_t`) and a relative subpath.
+///
+/// For absolute paths, this automatically searches among available Preopens to find
+/// a match. For relative paths, it uses the "emulated" CWD.
+pub fn resolvePathWasi(path: []const u8, out_buffer: *[MAX_PATH_BYTES]u8) !RelativePathWasi {
+ // Note: Due to WASI's "sandboxed" file handles, operations with this RelativePathWasi
+ // will fail if the relative path navigates outside of `dir_fd` using ".."
+ return resolvePathAndGetWasiPreopen(path, null, out_buffer);
+}
+
+fn resolvePathAndGetWasiPreopen(path: []const u8, preopen: ?*?Preopen, out_buffer: *[MAX_PATH_BYTES]u8) !RelativePathWasi {
+ var allocator = std.heap.FixedBufferAllocator.init(out_buffer);
+ var alloc = allocator.allocator();
+
+ if (fs.path.isAbsolute(path) or wasi_cwd.cwd == null) {
+ if (wasi_cwd.preopens == null) @panic("On WASI, `initPreopensWasi` must be called to initialize preopens " ++
+ "before using any CWD-relative or absolute paths.\n");
+
+ if (mem.startsWith(u8, path, "/preopens/fd/")) {
+ // "/preopens/fd/<N>" is a special prefix, which refers to a Preopen directly by fd
+ const fd_start = "/preopens/fd/".len;
+ const fd_end = mem.indexOfScalarPos(u8, path, fd_start, '/') orelse path.len;
+ const fd = std.fmt.parseUnsigned(fd_t, path[fd_start..fd_end], 10) catch unreachable;
+ const rel_path = if (path.len > fd_end + 1) path[fd_end + 1 ..] else ".";
+
+ if (preopen) |p| p.* = wasi_cwd.preopens.?.findByFd(fd);
+ return RelativePathWasi{
+ .dir_fd = fd,
+ .relative_path = alloc.dupe(u8, rel_path) catch return error.NameTooLong,
+ };
+ }
+
+ // For any other absolute path, we need to lookup a containing Preopen
+ const abs_path = fs.path.resolve(alloc, &.{ "/", path }) catch return error.NameTooLong;
+ const preopen_uri = wasi_cwd.preopens.?.findContaining(.{ .Dir = abs_path });
+
+ if (preopen_uri) |po| {
+ if (preopen) |p| p.* = po.base;
+ return RelativePathWasi{
+ .dir_fd = po.base.fd,
+ .relative_path = po.relative_path,
+ };
+ } else {
+ // No matching preopen found
+ return error.AccessDenied;
+ }
+ } else {
+ const cwd = wasi_cwd.cwd.?;
+
+ // If the path is empty or "." or "./", return CWD
+ if (std.mem.eql(u8, path, ".") or std.mem.eql(u8, path, "./")) {
+ return cwd;
+ }
+
+ // First resolve a combined path, where the "/" corresponds to `cwd.dir_fd`
+ // not the true filesystem root
+ const paths = &.{ "/", cwd.relative_path, path };
+ const resolved_path = fs.path.resolve(alloc, paths) catch return error.NameTooLong;
+
+ // Strip off the fake root to get the relative path w.r.t. `cwd.dir_fd`
+ const resolved_relative_path = resolved_path[1..];
+
+ if (preopen) |p| p.* = wasi_cwd.cwd_preopen;
+ return RelativePathWasi{
+ .dir_fd = cwd.dir_fd,
+ .relative_path = resolved_relative_path,
+ };
+ }
+}
+
/// Open and possibly create a file. Keeps trying if it gets interrupted.
/// `file_path` is relative to the open directory handle `dir_fd`.
/// See also `openatZ`.
pub fn openat(dir_fd: fd_t, file_path: []const u8, flags: u32, mode: mode_t) OpenError!fd_t {
- if (builtin.os.tag == .wasi and !builtin.link_libc) {
- @compileError("use openatWasi instead");
- }
if (builtin.os.tag == .windows) {
const file_path_w = try windows.sliceToPrefixedFileW(file_path);
return openatW(dir_fd, file_path_w.span(), flags, mode);
+ } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
+ // `mode` is ignored on WASI, which does not support unix-style file permissions
+ const fd = if (dir_fd == wasi.AT.FDCWD or fs.path.isAbsolute(file_path)) blk: {
+ // Resolve absolute or CWD-relative paths to a path within a Preopen
+ var path_buf: [MAX_PATH_BYTES]u8 = undefined;
+ const path = try resolvePathWasi(file_path, &path_buf);
+
+ const opts = try openOptionsFromFlagsWasi(path.dir_fd, flags);
+ break :blk try openatWasi(path.dir_fd, path.relative_path, opts.lookup_flags, opts.oflags, opts.fs_flags, opts.fs_rights_base, opts.fs_rights_inheriting);
+ } else blk: {
+ const opts = try openOptionsFromFlagsWasi(dir_fd, flags);
+ break :blk try openatWasi(dir_fd, file_path, opts.lookup_flags, opts.oflags, opts.fs_flags, opts.fs_rights_base, opts.fs_rights_inheriting);
+ };
+ errdefer close(fd);
+
+ const info = try fstat(fd);
+ if (flags & O.WRONLY != 0 and info.filetype == .DIRECTORY)
+ return error.IsDir;
+
+ return fd;
}
const file_path_c = try toPosixPath(file_path);
return openatZ(dir_fd, &file_path_c, flags, mode);
}
+/// A struct to contain all lookup/rights flags accepted by `wasi.path_open`
+const WasiOpenOptions = struct {
+ oflags: wasi.oflags_t,
+ lookup_flags: wasi.lookupflags_t,
+ fs_rights_base: wasi.rights_t,
+ fs_rights_inheriting: wasi.rights_t,
+ fs_flags: wasi.fdflags_t,
+};
+
+/// Compute rights + flags corresponding to the provided POSIX access mode.
+fn openOptionsFromFlagsWasi(fd: fd_t, oflag: u32) OpenError!WasiOpenOptions {
+ const w = std.os.wasi;
+
+ // First, discover the rights that we can derive from `fd`
+ var fsb_cur: wasi.fdstat_t = undefined;
+ _ = switch (w.fd_fdstat_get(fd, &fsb_cur)) {
+ .SUCCESS => .{},
+ .BADF => return error.InvalidHandle,
+ else => |err| return unexpectedErrno(err),
+ };
+
+ // Next, calculate the read/write rights to request, depending on the
+ // provided POSIX access mode
+ var rights: w.rights_t = 0;
+ if (oflag & O.RDONLY != 0) {
+ rights |= w.RIGHT.FD_READ | w.RIGHT.FD_READDIR;
+ }
+ if (oflag & O.WRONLY != 0) {
+ rights |= w.RIGHT.FD_DATASYNC | w.RIGHT.FD_WRITE |
+ w.RIGHT.FD_ALLOCATE | w.RIGHT.FD_FILESTAT_SET_SIZE;
+ }
+
+ // Request all other rights unconditionally
+ rights |= ~(w.RIGHT.FD_DATASYNC | w.RIGHT.FD_READ |
+ w.RIGHT.FD_WRITE | w.RIGHT.FD_ALLOCATE |
+ w.RIGHT.FD_READDIR | w.RIGHT.FD_FILESTAT_SET_SIZE);
+
+ // But only take rights that we can actually inherit
+ rights &= fsb_cur.fs_rights_inheriting;
+
+ return WasiOpenOptions{
+ .oflags = @truncate(w.oflags_t, (oflag >> 12)) & 0xfff,
+ .lookup_flags = if (oflag & O.NOFOLLOW == 0) w.LOOKUP_SYMLINK_FOLLOW else 0,
+ .fs_rights_base = rights,
+ .fs_rights_inheriting = fsb_cur.fs_rights_inheriting,
+ .fs_flags = @truncate(w.fdflags_t, oflag & 0xfff),
+ };
+}
+
/// Open and possibly create a file in WASI.
pub fn openatWasi(dir_fd: fd_t, file_path: []const u8, lookup_flags: lookupflags_t, oflags: oflags_t, fdflags: fdflags_t, base: rights_t, inheriting: rights_t) OpenError!fd_t {
while (true) {
@@ -1450,6 +1660,8 @@ pub fn openatZ(dir_fd: fd_t, file_path: [*:0]const u8, flags: u32, mode: mode_t)
if (builtin.os.tag == .windows) {
const file_path_w = try windows.cStrToPrefixedFileW(file_path);
return openatW(dir_fd, file_path_w.span(), flags, mode);
+ } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
+ return openat(dir_fd, mem.sliceTo(file_path, 0), flags, mode);
}
const openat_sym = if (builtin.os.tag == .linux and builtin.link_libc)
@@ -1496,7 +1708,7 @@ pub fn openatZ(dir_fd: fd_t, file_path: [*:0]const u8, flags: u32, mode: mode_t)
/// or makes use of perm argument.
pub fn openatW(dir_fd: fd_t, file_path_w: []const u16, flags: u32, mode: mode_t) OpenError!fd_t {
_ = mode;
- var options = openOptionsFromFlags(flags);
+ var options = openOptionsFromFlagsWindows(flags);
options.dir = dir_fd;
return windows.OpenFile(file_path_w, options) catch |err| switch (err) {
error.WouldBlock => unreachable,
@@ -1764,9 +1976,15 @@ pub const GetCwdError = error{
pub fn getcwd(out_buffer: []u8) GetCwdError![]u8 {
if (builtin.os.tag == .windows) {
return windows.GetCurrentDirectory(out_buffer);
- }
- if (builtin.os.tag == .wasi and !builtin.link_libc) {
- @compileError("WASI doesn't have a concept of cwd(); use std.fs.wasi.PreopenList to get available Dir handles instead");
+ } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
+ var buf: [MAX_PATH_BYTES]u8 = undefined;
+ const path = realpathWasi(".", &buf) catch |err| switch (err) {
+ error.NameTooLong => return error.NameTooLong,
+ error.InvalidHandle => return error.CurrentWorkingDirectoryUnlinked,
+ };
+ if (out_buffer.len < path.len) return error.NameTooLong;
+ std.mem.copy(u8, out_buffer, path);
+ return out_buffer[0..path.len];
}
const err = if (builtin.link_libc) blk: {
@@ -1809,11 +2027,10 @@ pub const SymLinkError = error{
/// If `sym_link_path` exists, it will not be overwritten.
/// See also `symlinkZ.
pub fn symlink(target_path: []const u8, sym_link_path: []const u8) SymLinkError!void {
- if (builtin.os.tag == .wasi and !builtin.link_libc) {
- @compileError("symlink is not supported in WASI; use symlinkat instead");
- }
if (builtin.os.tag == .windows) {
@compileError("symlink is not supported on Windows; use std.os.windows.CreateSymbolicLink instead");
+ } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
+ return symlinkat(target_path, wasi.AT.FDCWD, sym_link_path);
}
const target_path_c = try toPosixPath(target_path);
const sym_link_path_c = try toPosixPath(sym_link_path);
@@ -1825,6 +2042,8 @@ pub fn symlink(target_path: []const u8, sym_link_path: []const u8) SymLinkError!
pub fn symlinkZ(target_path: [*:0]const u8, sym_link_path: [*:0]const u8) SymLinkError!void {
if (builtin.os.tag == .windows) {
@compileError("symlink is not supported on Windows; use std.os.windows.CreateSymbolicLink instead");
+ } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
+ return symlink(mem.sliceTo(target_path, 0), mem.sliceTo(sym_link_path, 0));
}
switch (errno(system.symlink(target_path, sym_link_path))) {
.SUCCESS => return,
@@ -1853,11 +2072,16 @@ pub fn symlinkZ(target_path: [*:0]const u8, sym_link_path: [*:0]const u8) SymLin
/// If `sym_link_path` exists, it will not be overwritten.
/// See also `symlinkatWasi`, `symlinkatZ` and `symlinkatW`.
pub fn symlinkat(target_path: []const u8, newdirfd: fd_t, sym_link_path: []const u8) SymLinkError!void {
- if (builtin.os.tag == .wasi and !builtin.link_libc) {
- return symlinkatWasi(target_path, newdirfd, sym_link_path);
- }
if (builtin.os.tag == .windows) {
@compileError("symlinkat is not supported on Windows; use std.os.windows.CreateSymbolicLink instead");
+ } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
+ if (newdirfd == wasi.AT.FDCWD or fs.path.isAbsolute(target_path)) {
+ // Resolve absolute or CWD-relative paths to a path within a Preopen
+ var path_buf: [MAX_PATH_BYTES]u8 = undefined;
+ const path = try resolvePathWasi(sym_link_path, &path_buf);
+ return symlinkatWasi(target_path, path.dir_fd, path.relative_path);
+ }
+ return symlinkatWasi(target_path, newdirfd, sym_link_path);
}
const target_path_c = try toPosixPath(target_path);
const sym_link_path_c = try toPosixPath(sym_link_path);
@@ -1893,6 +2117,8 @@ pub fn symlinkatWasi(target_path: []const u8, newdirfd: fd_t, sym_link_path: []c
pub fn symlinkatZ(target_path: [*:0]const u8, newdirfd: fd_t, sym_link_path: [*:0]const u8) SymLinkError!void {
if (builtin.os.tag == .windows) {
@compileError("symlinkat is not supported on Windows; use std.os.windows.CreateSymbolicLink instead");
+ } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
+ return symlinkat(mem.sliceTo(target_path, 0), newdirfd, mem.sliceTo(sym_link_path, 0));
}
switch (errno(system.symlinkat(target_path, newdirfd, sym_link_path))) {
.SUCCESS => return,
@@ -1930,6 +2156,9 @@ pub const LinkError = UnexpectedError || error{
};
pub fn linkZ(oldpath: [*:0]const u8, newpath: [*:0]const u8, flags: i32) LinkError!void {
+ if (builtin.os.tag == .wasi and !builtin.link_libc) {
+ return link(mem.sliceTo(oldpath, 0), mem.sliceTo(newpath, 0), flags);
+ }
switch (errno(system.link(oldpath, newpath, flags))) {
.SUCCESS => return,
.ACCES => return error.AccessDenied,
@@ -1952,6 +2181,12 @@ pub fn linkZ(oldpath: [*:0]const u8, newpath: [*:0]const u8, flags: i32) LinkErr
}
pub fn link(oldpath: []const u8, newpath: []const u8, flags: i32) LinkError!void {
+ if (builtin.os.tag == .wasi and !builtin.link_libc) {
+ return linkat(wasi.AT.FDCWD, oldpath, wasi.AT.FDCWD, newpath, flags) catch |err| switch (err) {
+ error.NotDir => unreachable, // link() does not support directories
+ else => |e| return e,
+ };
+ }
const old = try toPosixPath(oldpath);
const new = try toPosixPath(newpath);
return try linkZ(&old, &new, flags);
@@ -1966,6 +2201,9 @@ pub fn linkatZ(
newpath: [*:0]const u8,
flags: i32,
) LinkatError!void {
+ if (builtin.os.tag == .wasi and !builtin.link_libc) {
+ return linkat(olddir, mem.sliceTo(oldpath, 0), newdir, mem.sliceTo(newpath, 0), flags);
+ }
switch (errno(system.linkat(olddir, oldpath, newdir, newpath, flags))) {
.SUCCESS => return,
.ACCES => return error.AccessDenied,
@@ -1995,11 +2233,62 @@ pub fn linkat(
newpath: []const u8,
flags: i32,
) LinkatError!void {
+ if (builtin.os.tag == .wasi and !builtin.link_libc) {
+ var resolve_olddir: bool = (olddir == wasi.AT.FDCWD or fs.path.isAbsolute(oldpath));
+ var resolve_newdir: bool = (newdir == wasi.AT.FDCWD or fs.path.isAbsolute(newpath));
+
+ var old: RelativePathWasi = .{ .dir_fd = olddir, .relative_path = oldpath };
+ var new: RelativePathWasi = .{ .dir_fd = newdir, .relative_path = newpath };
+
+ // Resolve absolute or CWD-relative paths to a path within a Preopen
+ if (resolve_olddir or resolve_newdir) {
+ var buf_old: [MAX_PATH_BYTES]u8 = undefined;
+ var buf_new: [MAX_PATH_BYTES]u8 = undefined;
+
+ if (resolve_olddir)
+ old = try resolvePathWasi(oldpath, &buf_old);
+
+ if (resolve_newdir)
+ new = try resolvePathWasi(newpath, &buf_new);
+
+ return linkatWasi(old, new, flags);
+ }
+ return linkatWasi(old, new, flags);
+ }
const old = try toPosixPath(oldpath);
const new = try toPosixPath(newpath);
return try linkatZ(olddir, &old, newdir, &new, flags);
}
+/// WASI-only. The same as `linkat` but targeting WASI.
+/// See also `linkat`.
+pub fn linkatWasi(old: RelativePathWasi, new: RelativePathWasi, flags: i32) LinkatError!void {
+ var old_flags: wasi.lookupflags_t = 0;
+ // TODO: Why is this not defined in wasi-libc?
+ if (flags & linux.AT.SYMLINK_FOLLOW != 0) old_flags |= wasi.LOOKUP_SYMLINK_FOLLOW;
+
+ switch (wasi.path_link(old.dir_fd, old_flags, old.relative_path.ptr, old.relative_path.len, new.dir_fd, new.relative_path.ptr, new.relative_path.len)) {
+ .SUCCESS => return,
+ .ACCES => return error.AccessDenied,
+ .DQUOT => return error.DiskQuota,
+ .EXIST => return error.PathAlreadyExists,
+ .FAULT => unreachable,
+ .IO => return error.FileSystem,
+ .LOOP => return error.SymLinkLoop,
+ .MLINK => return error.LinkQuotaExceeded,
+ .NAMETOOLONG => return error.NameTooLong,
+ .NOENT => return error.FileNotFound,
+ .NOMEM => return error.SystemResources,
+ .NOSPC => return error.NoSpaceLeft,
+ .NOTDIR => return error.NotDir,
+ .PERM => return error.AccessDenied,
+ .ROFS => return error.ReadOnlyFileSystem,
+ .XDEV => return error.NotSameFileSystem,
+ .INVAL => unreachable,
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
pub const UnlinkError = error{
FileNotFound,
@@ -2027,7 +2316,10 @@ pub const UnlinkError = error{
/// See also `unlinkZ`.
pub fn unlink(file_path: []const u8) UnlinkError!void {
if (builtin.os.tag == .wasi and !builtin.link_libc) {
- @compileError("unlink is not supported in WASI; use unlinkat instead");
+ return unlinkat(wasi.AT.FDCWD, file_path, 0) catch |err| switch (err) {
+ error.DirNotEmpty => unreachable, // only occurs when targeting directories
+ else => |e| return e,
+ };
} else if (builtin.os.tag == .windows) {
const file_path_w = try windows.sliceToPrefixedFileW(file_path);
return unlinkW(file_path_w.span());
@@ -2042,6 +2334,8 @@ pub fn unlinkZ(file_path: [*:0]const u8) UnlinkError!void {
if (builtin.os.tag == .windows) {
const file_path_w = try windows.cStrToPrefixedFileW(file_path);
return unlinkW(file_path_w.span());
+ } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
+ return unlink(mem.sliceTo(file_path, 0));
}
switch (errno(system.unlink(file_path))) {
.SUCCESS => return,
@@ -2079,6 +2373,12 @@ pub fn unlinkat(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatError!vo
const file_path_w = try windows.sliceToPrefixedFileW(file_path);
return unlinkatW(dirfd, file_path_w.span(), flags);
} else if (builtin.os.tag == .wasi and !builtin.link_libc) {
+ if (dirfd == wasi.AT.FDCWD or fs.path.isAbsolute(file_path)) {
+ // Resolve absolute or CWD-relative paths to a path within a Preopen
+ var path_buf: [MAX_PATH_BYTES]u8 = undefined;
+ const path = try resolvePathWasi(file_path, &path_buf);
+ return unlinkatWasi(path.dir_fd, path.relative_path, flags);
+ }
return unlinkatWasi(dirfd, file_path, flags);
} else {
const file_path_c = try toPosixPath(file_path);
@@ -2123,6 +2423,8 @@ pub fn unlinkatZ(dirfd: fd_t, file_path_c: [*:0]const u8, flags: u32) UnlinkatEr
if (builtin.os.tag == .windows) {
const file_path_w = try windows.cStrToPrefixedFileW(file_path_c);
return unlinkatW(dirfd, file_path_w.span(), flags);
+ } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
+ return unlinkat(dirfd, mem.sliceTo(file_path_c, 0), flags);
}
switch (errno(system.unlinkat(dirfd, file_path_c, flags))) {
.SUCCESS => return,
@@ -2181,7 +2483,7 @@ pub const RenameError = error{
/// Change the name or location of a file.
pub fn rename(old_path: []const u8, new_path: []const u8) RenameError!void {
if (builtin.os.tag == .wasi and !builtin.link_libc) {
- @compileError("rename is not supported in WASI; use renameat instead");
+ return renameat(wasi.AT.FDCWD, old_path, wasi.AT.FDCWD, new_path);
} else if (builtin.os.tag == .windows) {
const old_path_w = try windows.sliceToPrefixedFileW(old_path);
const new_path_w = try windows.sliceToPrefixedFileW(new_path);
@@ -2199,6 +2501,8 @@ pub fn renameZ(old_path: [*:0]const u8, new_path: [*:0]const u8) RenameError!voi
const old_path_w = try windows.cStrToPrefixedFileW(old_path);
const new_path_w = try windows.cStrToPrefixedFileW(new_path);
return renameW(old_path_w.span().ptr, new_path_w.span().ptr);
+ } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
+ return rename(mem.sliceTo(old_path, 0), mem.sliceTo(new_path, 0));
}
switch (errno(system.rename(old_path, new_path))) {
.SUCCESS => return,
@@ -2243,7 +2547,25 @@ pub fn renameat(
const new_path_w = try windows.sliceToPrefixedFileW(new_path);
return renameatW(old_dir_fd, old_path_w.span(), new_dir_fd, new_path_w.span(), windows.TRUE);
} else if (builtin.os.tag == .wasi and !builtin.link_libc) {
- return renameatWasi(old_dir_fd, old_path, new_dir_fd, new_path);
+ var resolve_old: bool = (old_dir_fd == wasi.AT.FDCWD or fs.path.isAbsolute(old_path));
+ var resolve_new: bool = (new_dir_fd == wasi.AT.FDCWD or fs.path.isAbsolute(new_path));
+
+ var old: RelativePathWasi = .{ .dir_fd = old_dir_fd, .relative_path = old_path };
+ var new: RelativePathWasi = .{ .dir_fd = new_dir_fd, .relative_path = new_path };
+
+ // Resolve absolute or CWD-relative paths to a path within a Preopen
+ if (resolve_old or resolve_new) {
+ var buf_old: [MAX_PATH_BYTES]u8 = undefined;
+ var buf_new: [MAX_PATH_BYTES]u8 = undefined;
+
+ if (resolve_old)
+ old = try resolvePathWasi(old_path, &buf_old);
+ if (resolve_new)
+ new = try resolvePathWasi(new_path, &buf_new);
+
+ return renameatWasi(old, new);
+ }
+ return renameatWasi(old, new);
} else {
const old_path_c = try toPosixPath(old_path);
const new_path_c = try toPosixPath(new_path);
@@ -2253,8 +2575,8 @@ pub fn renameat(
/// WASI-only. Same as `renameat` expect targeting WASI.
/// See also `renameat`.
-pub fn renameatWasi(old_dir_fd: fd_t, old_path: []const u8, new_dir_fd: fd_t, new_path: []const u8) RenameError!void {
- switch (wasi.path_rename(old_dir_fd, old_path.ptr, old_path.len, new_dir_fd, new_path.ptr, new_path.len)) {
+pub fn renameatWasi(old: RelativePathWasi, new: RelativePathWasi) RenameError!void {
+ switch (wasi.path_rename(old.dir_fd, old.relative_path.ptr, old.relative_path.len, new.dir_fd, new.relative_path.ptr, new.relative_path.len)) {
.SUCCESS => return,
.ACCES => return error.AccessDenied,
.PERM => return error.AccessDenied,
@@ -2290,6 +2612,8 @@ pub fn renameatZ(
const old_path_w = try windows.cStrToPrefixedFileW(old_path);
const new_path_w = try windows.cStrToPrefixedFileW(new_path);
return renameatW(old_dir_fd, old_path_w.span(), new_dir_fd, new_path_w.span(), windows.TRUE);
+ } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
+ return renameat(old_dir_fd, mem.sliceTo(old_path, 0), new_dir_fd, mem.sliceTo(new_path, 0));
}
switch (errno(system.renameat(old_dir_fd, old_path, new_dir_fd, new_path))) {
@@ -2380,6 +2704,12 @@ pub fn mkdirat(dir_fd: fd_t, sub_dir_path: []const u8, mode: u32) MakeDirError!v
const sub_dir_path_w = try windows.sliceToPrefixedFileW(sub_dir_path);
return mkdiratW(dir_fd, sub_dir_path_w.span(), mode);
} else if (builtin.os.tag == .wasi and !builtin.link_libc) {
+ if (dir_fd == wasi.AT.FDCWD or fs.path.isAbsolute(sub_dir_path)) {
+ // Resolve absolute or CWD-relative paths to a path within a Preopen
+ var path_buf: [MAX_PATH_BYTES]u8 = undefined;
+ const path = try resolvePathWasi(sub_dir_path, &path_buf);
+ return mkdiratWasi(path.dir_fd, path.relative_path, mode);
+ }
return mkdiratWasi(dir_fd, sub_dir_path, mode);
} else {
const sub_dir_path_c = try toPosixPath(sub_dir_path);
@@ -2414,6 +2744,8 @@ pub fn mkdiratZ(dir_fd: fd_t, sub_dir_path: [*:0]const u8, mode: u32) MakeDirErr
if (builtin.os.tag == .windows) {
const sub_dir_path_w = try windows.cStrToPrefixedFileW(sub_dir_path);
return mkdiratW(dir_fd, sub_dir_path_w.span().ptr, mode);
+ } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
+ return mkdirat(dir_fd, mem.sliceTo(sub_dir_path, 0), mode);
}
switch (errno(system.mkdirat(dir_fd, sub_dir_path, mode))) {
.SUCCESS => return,
@@ -2472,10 +2804,10 @@ pub const MakeDirError = error{
} || UnexpectedError;
/// Create a directory.
-/// `mode` is ignored on Windows.
+/// `mode` is ignored on Windows and WASI.
pub fn mkdir(dir_path: []const u8, mode: u32) MakeDirError!void {
if (builtin.os.tag == .wasi and !builtin.link_libc) {
- @compileError("mkdir is not supported in WASI; use mkdirat instead");
+ return mkdirat(wasi.AT.FDCWD, dir_path, mode);
} else if (builtin.os.tag == .windows) {
const dir_path_w = try windows.sliceToPrefixedFileW(dir_path);
return mkdirW(dir_path_w.span(), mode);
@@ -2490,6 +2822,8 @@ pub fn mkdirZ(dir_path: [*:0]const u8, mode: u32) MakeDirError!void {
if (builtin.os.tag == .windows) {
const dir_path_w = try windows.cStrToPrefixedFileW(dir_path);
return mkdirW(dir_path_w.span(), mode);
+ } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
+ return mkdir(mem.sliceTo(dir_path, 0), mode);
}
switch (errno(system.mkdir(dir_path, mode))) {
.SUCCESS => return,
@@ -2545,7 +2879,11 @@ pub const DeleteDirError = error{
/// Deletes an empty directory.
pub fn rmdir(dir_path: []const u8) DeleteDirError!void {
if (builtin.os.tag == .wasi and !builtin.link_libc) {
- @compileError("rmdir is not supported in WASI; use unlinkat instead");
+ return unlinkat(wasi.AT.FDCWD, dir_path, AT.REMOVEDIR) catch |err| switch (err) {
+ error.FileSystem => unreachable, // only occurs when targeting files
+ error.IsDir => unreachable, // only occurs when targeting files
+ else => |e| return e,
+ };
} else if (builtin.os.tag == .windows) {
const dir_path_w = try windows.sliceToPrefixedFileW(dir_path);
return rmdirW(dir_path_w.span());
@@ -2560,6 +2898,8 @@ pub fn rmdirZ(dir_path: [*:0]const u8) DeleteDirError!void {
if (builtin.os.tag == .windows) {
const dir_path_w = try windows.cStrToPrefixedFileW(dir_path);
return rmdirW(dir_path_w.span());
+ } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
+ return rmdir(mem.sliceTo(dir_path, 0));
}
switch (errno(system.rmdir(dir_path))) {
.SUCCESS => return,
@@ -2606,7 +2946,17 @@ pub const ChangeCurDirError = error{
/// `dir_path` is recommended to be a UTF-8 encoded string.
pub fn chdir(dir_path: []const u8) ChangeCurDirError!void {
if (builtin.os.tag == .wasi and !builtin.link_libc) {
- @compileError("chdir is not supported in WASI");
+ var preopen: ?Preopen = null;
+ const path = try resolvePathAndGetWasiPreopen(dir_path, &preopen, &wasi_cwd.path_buffer);
+
+ const dirinfo = try fstatat(path.dir_fd, path.relative_path, 0);
+ if (dirinfo.filetype != .DIRECTORY) {
+ return error.NotDir;
+ }
+
+ wasi_cwd.cwd_preopen = preopen;
+ wasi_cwd.cwd = path;
+ return;
} else if (builtin.os.tag == .windows) {
var utf16_dir_path: [windows.PATH_MAX_WIDE]u16 = undefined;
const len = try std.unicode.utf8ToUtf16Le(utf16_dir_path[0..], dir_path);
@@ -2625,6 +2975,8 @@ pub fn chdirZ(dir_path: [*:0]const u8) ChangeCurDirError!void {
const len = try std.unicode.utf8ToUtf16Le(utf16_dir_path[0..], dir_path);
if (len > utf16_dir_path.len) return error.NameTooLong;
return chdirW(utf16_dir_path[0..len]);
+ } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
+ return chdir(mem.sliceTo(dir_path, 0));
}
switch (errno(system.chdir(dir_path))) {
.SUCCESS => return,
@@ -2655,15 +3007,29 @@ pub const FchdirError = error{
} || UnexpectedError;
pub fn fchdir(dirfd: fd_t) FchdirError!void {
- while (true) {
- switch (errno(system.fchdir(dirfd))) {
- .SUCCESS => return,
- .ACCES => return error.AccessDenied,
- .BADF => unreachable,
- .NOTDIR => return error.NotDir,
- .INTR => continue,
- .IO => return error.FileSystem,
- else => |err| return unexpectedErrno(err),
+ if (builtin.os.tag == .wasi) {
+ // Check that this is a directory
+ const dirinfo = fstatat(dirfd, ".", 0) catch unreachable;
+ if (dirinfo.filetype != .DIRECTORY) {
+ return error.NotDir;
+ }
+
+ wasi_cwd.cwd = .{
+ .dir_fd = dirfd,
+ .relative_path = ".",
+ };
+ wasi_cwd.cwd_preopen = null;
+ } else {
+ while (true) {
+ switch (errno(system.fchdir(dirfd))) {
+ .SUCCESS => return,
+ .ACCES => return error.AccessDenied,
+ .BADF => unreachable,
+ .NOTDIR => return error.NotDir,
+ .INTR => continue,
+ .IO => return error.FileSystem,
+ else => |err| return unexpectedErrno(err),
+ }
}
}
}
@@ -2690,7 +3056,7 @@ pub const ReadLinkError = error{
/// The return value is a slice of `out_buffer` from index 0.
pub fn readlink(file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 {
if (builtin.os.tag == .wasi and !builtin.link_libc) {
- @compileError("readlink is not supported in WASI; use readlinkat instead");
+ return readlinkat(wasi.AT.FDCWD, file_path, out_buffer);
} else if (builtin.os.tag == .windows) {
const file_path_w = try windows.sliceToPrefixedFileW(file_path);
return readlinkW(file_path_w.span(), out_buffer);
@@ -2711,6 +3077,8 @@ pub fn readlinkZ(file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8
if (builtin.os.tag == .windows) {
const file_path_w = try windows.cStrToWin32PrefixedFileW(file_path);
return readlinkW(file_path_w.span(), out_buffer);
+ } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
+ return readlink(mem.sliceTo(file_path, 0), out_buffer);
}
const rc = system.readlink(file_path, out_buffer.ptr, out_buffer.len);
switch (errno(rc)) {
@@ -2733,6 +3101,12 @@ pub fn readlinkZ(file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8
/// See also `readlinkatWasi`, `realinkatZ` and `realinkatW`.
pub fn readlinkat(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 {
if (builtin.os.tag == .wasi and !builtin.link_libc) {
+ if (dirfd == wasi.AT.FDCWD or fs.path.isAbsolute(file_path)) {
+ // Resolve absolute or CWD-relative paths to a path within a Preopen
+ var path_buf: [MAX_PATH_BYTES]u8 = undefined;
+ var path = try resolvePathWasi(file_path, &path_buf);
+ return readlinkatWasi(path.dir_fd, path.relative_path, out_buffer);
+ }
return readlinkatWasi(dirfd, file_path, out_buffer);
}
if (builtin.os.tag == .windows) {
@@ -2775,6 +3149,8 @@ pub fn readlinkatZ(dirfd: fd_t, file_path: [*:0]const u8, out_buffer: []u8) Read
if (builtin.os.tag == .windows) {
const file_path_w = try windows.cStrToPrefixedFileW(file_path);
return readlinkatW(dirfd, file_path_w.span(), out_buffer);
+ } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
+ return readlinkat(dirfd, mem.sliceTo(file_path, 0), out_buffer);
}
const rc = system.readlinkat(dirfd, file_path, out_buffer.ptr, out_buffer.len);
switch (errno(rc)) {
@@ -3727,7 +4103,14 @@ pub const FStatAtError = FStatError || error{ NameTooLong, FileNotFound, SymLink
/// See also `fstatatZ` and `fstatatWasi`.
pub fn fstatat(dirfd: fd_t, pathname: []const u8, flags: u32) FStatAtError!Stat {
if (builtin.os.tag == .wasi and !builtin.link_libc) {
- return fstatatWasi(dirfd, pathname, flags);
+ const wasi_flags = if (flags & linux.AT.SYMLINK_NOFOLLOW == 0) wasi.LOOKUP_SYMLINK_FOLLOW else 0;
+ if (dirfd == wasi.AT.FDCWD or fs.path.isAbsolute(pathname)) {
+ // Resolve absolute or CWD-relative paths to a path within a Preopen
+ var path_buf: [MAX_PATH_BYTES]u8 = undefined;
+ const path = try resolvePathWasi(pathname, &path_buf);
+ return fstatatWasi(path.dir_fd, path.relative_path, wasi_flags);
+ }
+ return fstatatWasi(dirfd, pathname, wasi_flags);
} else if (builtin.os.tag == .windows) {
@compileError("fstatat is not yet implemented on Windows");
} else {
@@ -3758,6 +4141,10 @@ pub fn fstatatWasi(dirfd: fd_t, pathname: []const u8, flags: u32) FStatAtError!S
/// Same as `fstatat` but `pathname` is null-terminated.
/// See also `fstatat`.
pub fn fstatatZ(dirfd: fd_t, pathname: [*:0]const u8, flags: u32) FStatAtError!Stat {
+ if (builtin.os.tag == .wasi and !builtin.link_libc) {
+ return fstatatWasi(dirfd, mem.sliceTo(pathname), flags);
+ }
+
const fstatat_sym = if (builtin.os.tag == .linux and builtin.link_libc)
system.fstatat64
else
@@ -4056,6 +4443,8 @@ pub fn access(path: []const u8, mode: u32) AccessError!void {
const path_w = try windows.sliceToPrefixedFileW(path);
_ = try windows.GetFileAttributesW(path_w.span().ptr);
return;
+ } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
+ return faccessat(wasi.AT.FDCWD, path, mode, 0);
}
const path_c = try toPosixPath(path);
return accessZ(&path_c, mode);
@@ -4067,6 +4456,8 @@ pub fn accessZ(path: [*:0]const u8, mode: u32) AccessError!void {
const path_w = try windows.cStrToPrefixedFileW(path);
_ = try windows.GetFileAttributesW(path_w.span().ptr);
return;
+ } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
+ return access(mem.sliceTo(path, 0), mode);
}
switch (errno(system.access(path, mode))) {
.SUCCESS => return,
@@ -4108,6 +4499,45 @@ pub fn faccessat(dirfd: fd_t, path: []const u8, mode: u32, flags: u32) AccessErr
if (builtin.os.tag == .windows) {
const path_w = try windows.sliceToPrefixedFileW(path);
return faccessatW(dirfd, path_w.span().ptr, mode, flags);
+ } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
+ var resolved = RelativePathWasi{ .dir_fd = dirfd, .relative_path = path };
+
+ const file = blk: {
+ if (dirfd == wasi.AT.FDCWD or fs.path.isAbsolute(path)) {
+ // Resolve absolute or CWD-relative paths to a path within a Preopen
+ var path_buf: [MAX_PATH_BYTES]u8 = undefined;
+ resolved = resolvePathWasi(path, &path_buf) catch |err| break :blk @as(FStatAtError!Stat, err);
+ break :blk fstatat(resolved.dir_fd, resolved.relative_path, flags);
+ }
+ break :blk fstatat(dirfd, path, flags);
+ } catch |err| switch (err) {
+ error.AccessDenied => return error.PermissionDenied,
+ else => |e| return e,
+ };
+
+ if (mode != F_OK) {
+ var directory: wasi.fdstat_t = undefined;
+ if (wasi.fd_fdstat_get(resolved.dir_fd, &directory) != .SUCCESS) {
+ return error.PermissionDenied;
+ }
+
+ var rights: wasi.rights_t = 0;
+ if (mode & R_OK != 0) {
+ rights |= if (file.filetype == .DIRECTORY)
+ wasi.RIGHT.FD_READDIR
+ else
+ wasi.RIGHT.FD_READ;
+ }
+ if (mode & W_OK != 0) {
+ rights |= wasi.RIGHT.FD_WRITE;
+ }
+ // No validation for X_OK
+
+ if ((rights & directory.fs_rights_inheriting) != rights) {
+ return error.PermissionDenied;
+ }
+ }
+ return;
}
const path_c = try toPosixPath(path);
return faccessatZ(dirfd, &path_c, mode, flags);
@@ -4118,6 +4548,8 @@ pub fn faccessatZ(dirfd: fd_t, path: [*:0]const u8, mode: u32, flags: u32) Acces
if (builtin.os.tag == .windows) {
const path_w = try windows.cStrToPrefixedFileW(path);
return faccessatW(dirfd, path_w.span().ptr, mode, flags);
+ } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
+ return faccessat(dirfd, mem.sliceTo(path, 0), mode, flags);
}
switch (errno(system.faccessat(dirfd, path, mode, flags))) {
.SUCCESS => return,
@@ -4645,6 +5077,9 @@ pub const RealPathError = error{
SharingViolation,
PipeBusy,
+ /// On WASI, the current CWD may not be associated with an absolute path.
+ InvalidHandle,
+
/// On Windows, file paths must be valid Unicode.
InvalidUtf8,
@@ -4660,19 +5095,55 @@ pub fn realpath(pathname: []const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealPathE
if (builtin.os.tag == .windows) {
const pathname_w = try windows.sliceToPrefixedFileW(pathname);
return realpathW(pathname_w.span(), out_buffer);
- }
- if (builtin.os.tag == .wasi and !builtin.link_libc) {
- @compileError("Use std.fs.wasi.PreopenList to obtain valid Dir handles instead of using absolute paths");
+ } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
+ return realpathWasi(pathname, out_buffer);
}
const pathname_c = try toPosixPath(pathname);
return realpathZ(&pathname_c, out_buffer);
}
+/// Return an emulated canonicalized absolute pathname on WASI.
+///
+/// NOTE: This emulation is incomplete. Symbolic links are not
+/// currently expanded during path canonicalization.
+fn realpathWasi(pathname: []const u8, out_buffer: []u8) ![]u8 {
+ var alloc = std.heap.FixedBufferAllocator.init(out_buffer);
+ if (fs.path.isAbsolute(pathname))
+ return try fs.path.resolve(alloc.allocator(), &.{pathname}) catch error.NameTooLong;
+ if (wasi_cwd.cwd) |cwd| {
+ if (wasi_cwd.cwd_preopen) |po| {
+ var base_cwd_dir = switch (po.@"type") {
+ .Dir => |dir| dir,
+ };
+ const paths: [][]const u8 = if (fs.path.isAbsolute(base_cwd_dir)) blk: {
+ break :blk &.{ base_cwd_dir, cwd.relative_path, pathname };
+ } else blk: {
+ // No absolute path is associated with this preopen, so
+ // instead we use a special "/preopens/fd/<N>/" prefix
+ var buf: [16]u8 = undefined;
+ var fbs = std.io.fixedBufferStream(&buf);
+ std.fmt.formatInt(po.fd, 10, .lower, .{}, fbs.writer()) catch return error.NameTooLong;
+ break :blk &.{ "/preopens/fd/", fbs.getWritten(), cwd.relative_path, pathname };
+ };
+
+ return fs.path.resolve(alloc.allocator(), paths) catch error.NameTooLong;
+ } else {
+ // The CWD is not rooted to an existing Preopen,
+ // so we have no way to know its absolute path
+ return error.InvalidHandle;
+ }
+ } else {
+ return try fs.path.resolve(alloc.allocator(), &.{ "/", pathname }) catch error.NameTooLong;
+ }
+}
+
/// Same as `realpath` except `pathname` is null-terminated.
pub fn realpathZ(pathname: [*:0]const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {
if (builtin.os.tag == .windows) {
const pathname_w = try windows.cStrToPrefixedFileW(pathname);
return realpathW(pathname_w.span(), out_buffer);
+ } else if (builtin.os.tag == .wasi and !builtin.link_libc) {
+ return realpath(mem.sliceTo(pathname, 0), out_buffer);
}
if (!builtin.link_libc) {
const flags = if (builtin.os.tag == .linux) O.PATH | O.NONBLOCK | O.CLOEXEC else O.NONBLOCK | O.CLOEXEC;
@@ -4680,6 +5151,7 @@ pub fn realpathZ(pathname: [*:0]const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealP
error.FileLocksNotSupported => unreachable,
error.WouldBlock => unreachable,
error.FileBusy => unreachable, // not asking for write permissions
+ error.InvalidHandle => unreachable, // WASI-only
else => |e| return e,
};
defer close(fd);
diff --git a/lib/std/os/test.zig b/lib/std/os/test.zig
index cc62e6161c..348f5ffaa8 100644
--- a/lib/std/os/test.zig
+++ b/lib/std/os/test.zig
@@ -22,7 +22,7 @@ const Dir = std.fs.Dir;
const ArenaAllocator = std.heap.ArenaAllocator;
test "chdir smoke test" {
- if (native_os == .wasi) return error.SkipZigTest;
+ if (native_os == .wasi) return error.SkipZigTest; // WASI doesn't allow navigating outside of a preopen
// Get current working directory path
var old_cwd_buf: [fs.MAX_PATH_BYTES]u8 = undefined;
@@ -48,7 +48,8 @@ test "chdir smoke test" {
}
test "open smoke test" {
- if (native_os == .wasi) return error.SkipZigTest;
+ if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest;
+ if (native_os == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, ".");
// TODO verify file attributes using `fstat`
@@ -102,7 +103,8 @@ test "open smoke test" {
}
test "openat smoke test" {
- if (native_os == .wasi) return error.SkipZigTest;
+ if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest;
+ if (native_os == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, ".");
// TODO verify file attributes using `fstatat`
@@ -138,7 +140,8 @@ test "openat smoke test" {
}
test "symlink with relative paths" {
- if (native_os == .wasi) return error.SkipZigTest;
+ if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest;
+ if (native_os == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, ".");
const cwd = fs.cwd();
cwd.deleteFile("file.txt") catch {};
@@ -190,6 +193,13 @@ fn testReadlink(target_path: []const u8, symlink_path: []const u8) !void {
test "link with relative paths" {
switch (native_os) {
+ .wasi => {
+ if (builtin.link_libc) {
+ return error.SkipZigTest;
+ } else {
+ try os.initPreopensWasi(std.heap.page_allocator, ".");
+ }
+ },
.linux, .solaris => {},
else => return error.SkipZigTest,
}
@@ -212,14 +222,14 @@ test "link with relative paths" {
const nstat = try os.fstat(nfd.handle);
try testing.expectEqual(estat.ino, nstat.ino);
- try testing.expectEqual(@as(usize, 2), nstat.nlink);
+ try testing.expectEqual(@as(@TypeOf(nstat.nlink), 2), nstat.nlink);
}
try os.unlink("new.txt");
{
const estat = try os.fstat(efd.handle);
- try testing.expectEqual(@as(usize, 1), estat.nlink);
+ try testing.expectEqual(@as(@TypeOf(estat.nlink), 1), estat.nlink);
}
try cwd.deleteFile("example.txt");
@@ -227,6 +237,7 @@ test "link with relative paths" {
test "linkat with different directories" {
switch (native_os) {
+ .wasi => if (!builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, "."),
.linux, .solaris => {},
else => return error.SkipZigTest,
}
@@ -250,14 +261,14 @@ test "linkat with different directories" {
const nstat = try os.fstat(nfd.handle);
try testing.expectEqual(estat.ino, nstat.ino);
- try testing.expectEqual(@as(usize, 2), nstat.nlink);
+ try testing.expectEqual(@as(@TypeOf(nstat.nlink), 2), nstat.nlink);
}
try os.unlinkat(tmp.dir.fd, "new.txt", 0);
{
const estat = try os.fstat(efd.handle);
- try testing.expectEqual(@as(usize, 1), estat.nlink);
+ try testing.expectEqual(@as(@TypeOf(estat.nlink), 1), estat.nlink);
}
try cwd.deleteFile("example.txt");
@@ -388,8 +399,6 @@ test "getrandom" {
}
test "getcwd" {
- if (native_os == .wasi) return error.SkipZigTest;
-
// at least call it so it gets compiled
var buf: [std.fs.MAX_PATH_BYTES]u8 = undefined;
_ = os.getcwd(&buf) catch undefined;
@@ -878,3 +887,107 @@ test "POSIX file locking with fcntl" {
try expect(result.status == 0 * 256);
}
}
+
+test "rename smoke test" {
+ if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest;
+ if (native_os == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, ".");
+
+ var tmp = tmpDir(.{});
+ defer tmp.cleanup();
+
+ // Get base abs path
+ var arena = ArenaAllocator.init(testing.allocator);
+ defer arena.deinit();
+ const allocator = arena.allocator();
+
+ const base_path = blk: {
+ const relative_path = try fs.path.join(allocator, &[_][]const u8{ "zig-cache", "tmp", tmp.sub_path[0..] });
+ break :blk try fs.realpathAlloc(allocator, relative_path);
+ };
+
+ var file_path: []u8 = undefined;
+ var fd: os.fd_t = undefined;
+ const mode: os.mode_t = if (native_os == .windows) 0 else 0o666;
+
+ // Create some file using `open`.
+ file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_file" });
+ fd = try os.open(file_path, os.O.RDWR | os.O.CREAT | os.O.EXCL, mode);
+ os.close(fd);
+
+ // Rename the file
+ var new_file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_other_file" });
+ try os.rename(file_path, new_file_path);
+
+ // Try opening renamed file
+ file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_other_file" });
+ fd = try os.open(file_path, os.O.RDWR, mode);
+ os.close(fd);
+
+ // Try opening original file - should fail with error.FileNotFound
+ file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_file" });
+ try expectError(error.FileNotFound, os.open(file_path, os.O.RDWR, mode));
+
+ // Create some directory
+ file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_dir" });
+ try os.mkdir(file_path, mode);
+
+ // Rename the directory
+ new_file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_other_dir" });
+ try os.rename(file_path, new_file_path);
+
+ // Try opening renamed directory
+ file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_other_dir" });
+ fd = try os.open(file_path, os.O.RDONLY | os.O.DIRECTORY, mode);
+ os.close(fd);
+
+ // Try opening original directory - should fail with error.FileNotFound
+ file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_dir" });
+ try expectError(error.FileNotFound, os.open(file_path, os.O.RDONLY | os.O.DIRECTORY, mode));
+}
+
+test "access smoke test" {
+ if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest;
+ if (native_os == .wasi and !builtin.link_libc) try os.initPreopensWasi(std.heap.page_allocator, ".");
+
+ var tmp = tmpDir(.{});
+ defer tmp.cleanup();
+
+ // Get base abs path
+ var arena = ArenaAllocator.init(testing.allocator);
+ defer arena.deinit();
+ const allocator = arena.allocator();
+
+ const base_path = blk: {
+ const relative_path = try fs.path.join(allocator, &[_][]const u8{ "zig-cache", "tmp", tmp.sub_path[0..] });
+ break :blk try fs.realpathAlloc(allocator, relative_path);
+ };
+
+ var file_path: []u8 = undefined;
+ var fd: os.fd_t = undefined;
+ const mode: os.mode_t = if (native_os == .windows) 0 else 0o666;
+
+ // Create some file using `open`.
+ file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_file" });
+ fd = try os.open(file_path, os.O.RDWR | os.O.CREAT | os.O.EXCL, mode);
+ os.close(fd);
+
+ // Try to access() the file
+ file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_file" });
+ if (builtin.os.tag == .windows) {
+ try os.access(file_path, os.F_OK);
+ } else {
+ try os.access(file_path, os.F_OK | os.W_OK | os.R_OK);
+ }
+
+ // Try to access() a non-existent file - should fail with error.FileNotFound
+ file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_other_file" });
+ try expectError(error.FileNotFound, os.access(file_path, os.F_OK));
+
+ // Create some directory
+ file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_dir" });
+ try os.mkdir(file_path, mode);
+
+ // Try to access() the directory
+ file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_dir" });
+ try os.access(file_path, os.F_OK);
+}
diff --git a/lib/std/os/wasi.zig b/lib/std/os/wasi.zig
index 0b2538cb88..ec2c577de1 100644
--- a/lib/std/os/wasi.zig
+++ b/lib/std/os/wasi.zig
@@ -15,6 +15,11 @@ comptime {
// assert(@alignOf(u64) == 8);
}
+pub const F_OK = 0;
+pub const X_OK = 1;
+pub const W_OK = 2;
+pub const R_OK = 4;
+
pub const iovec_t = std.os.iovec;
pub const ciovec_t = std.os.iovec_const;
diff --git a/lib/std/zig/system/NativeTargetInfo.zig b/lib/std/zig/system/NativeTargetInfo.zig
index af41fc7905..36f6207677 100644
--- a/lib/std/zig/system/NativeTargetInfo.zig
+++ b/lib/std/zig/system/NativeTargetInfo.zig
@@ -369,6 +369,7 @@ fn detectAbiAndDynamicLinker(
error.IsDir,
error.NotDir,
+ error.InvalidHandle,
error.AccessDenied,
error.NoDevice,
error.FileNotFound,
@@ -670,6 +671,7 @@ pub fn abiAndDynamicLinkerFromFile(
error.FileNotFound,
error.NotDir,
+ error.InvalidHandle,
error.AccessDenied,
error.NoDevice,
=> continue,