Merge pull request #11021 from topolarity/wasi-cwd

stdlib: Add emulated CWD to std.os for WASI targets
This commit is contained in:
Jakub Konka
2022-03-05 20:31:44 +01:00
committed by GitHub
10 changed files with 778 additions and 96 deletions

View File

@@ -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);

View File

@@ -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,
}

View File

@@ -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) {

View File

@@ -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", "..");

View File

@@ -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();

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -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,