From 5bb9689212d35f2c950fb2d542a9dca83b61937f Mon Sep 17 00:00:00 2001 From: Pat Tullmann Date: Sun, 28 Jul 2024 18:21:32 -0700 Subject: [PATCH 1/5] wasi: split with-libc and without-libc implementations Make a clearer separation between the Posix defines for WASI without a libc (the `system` defined in posix.zig) and the Posix defines for WASI with a libc (which should mirror the Musl-based wasi-libc implementation, and are defined in c.zig). Make WASI `posix.O` look more POSIX-y when compiling without libc. These structures and constants do not need to mirror the with-libc Wasi API (e.g., can use `.ACCMODE`). This removes some Wasi-specific code in Dir.zig and posix.zig by making the structure use names consistent with other platforms. Define `.S`, `.timespec`, and `.AT` structs for Wasi-without-libc in posix.zig. These are based on the wasi-with-libc structures, but cleaned up to remove cruft like padding or wholly unsupported fields. Fix `.S` and `.AT` constants on wasi-with-libc (defined in c.zig) to match wasi-libc headers. Define `mode_t` type as `void` for wasi targets because file permissions are not supported via the Posix APIS on these platforms. Use `mode_t` as the parameter type in Zig's mkdir\* and openat\* wrappers. Re-enable a lot of tests that have accumulated stale not-on-wasi guards, and simplify some tests (e.g., dropping unnecessary absolute paths) so they can run on wasi targets. --- lib/std/Build/Step/Run.zig | 2 +- lib/std/c.zig | 50 +++--- lib/std/debug.zig | 10 +- lib/std/fs/Dir.zig | 93 +++++------ lib/std/fs/File.zig | 12 +- lib/std/fs/test.zig | 79 ++++----- lib/std/os/linux.zig | 12 +- lib/std/os/wasi.zig | 2 +- lib/std/posix.zig | 320 +++++++++++++++++++++++++++++-------- lib/std/posix/test.zig | 103 ++++++------ 10 files changed, 439 insertions(+), 244 deletions(-) diff --git a/lib/std/Build/Step/Run.zig b/lib/std/Build/Step/Run.zig index 0c011e25eddd..84658c091f8b 100644 --- a/lib/std/Build/Step/Run.zig +++ b/lib/std/Build/Step/Run.zig @@ -1089,7 +1089,7 @@ fn runCommand( // the `--` before the module name. This appears to work for both old and // new Wasmtime versions. try interp_argv.append(bin_name); - try interp_argv.append("--dir=."); + try interp_argv.append("--dir=."); // Preopen '.' at file descriptor 3 for cwd try interp_argv.append("--"); try interp_argv.append(argv[0]); try interp_argv.appendSlice(argv[1..]); diff --git a/lib/std/c.zig b/lib/std/c.zig index 611f707e15e2..e736391dc060 100644 --- a/lib/std/c.zig +++ b/lib/std/c.zig @@ -133,9 +133,11 @@ pub const dev_t = switch (native_os) { pub const mode_t = switch (native_os) { .linux => linux.mode_t, .emscripten => emscripten.mode_t, - .openbsd, .haiku, .netbsd, .solaris, .illumos, .wasi => u32, + .openbsd, .haiku, .netbsd, .solaris, .illumos => u32, .freebsd, .macos, .ios, .tvos, .watchos, .visionos => u16, - else => u0, + .wasi => void, + .windows => u0, + else => u0, // TODO: should be void? }; pub const nlink_t = switch (native_os) { @@ -1702,17 +1704,15 @@ pub const S = switch (native_os) { .linux => linux.S, .emscripten => emscripten.S, .wasi => struct { - pub const IEXEC = @compileError("TODO audit this"); + // Match wasi-libc's libc-bottom-half/headers/public/__mode_t.h pub const IFBLK = 0x6000; pub const IFCHR = 0x2000; pub const IFDIR = 0x4000; - pub const IFIFO = 0xc000; + pub const IFIFO = 0x1000; pub const IFLNK = 0xa000; - pub const IFMT = IFBLK | IFCHR | IFDIR | IFIFO | IFLNK | IFREG | IFSOCK; pub const IFREG = 0x8000; - /// There's no concept of UNIX domain socket but we define this value here - /// in order to line with other OSes. - pub const IFSOCK = 0x1; + pub const IFSOCK = 0xc000; + pub const IFMT = IFBLK | IFCHR | IFDIR | IFIFO | IFLNK | IFREG | IFSOCK; }, .macos, .ios, .tvos, .watchos, .visionos => struct { pub const IFMT = 0o170000; @@ -6486,6 +6486,7 @@ pub const Stat = switch (native_os) { }, .emscripten => emscripten.Stat, .wasi => extern struct { + // Matches wasi-libc's libc-bottom-half/headers/public/__struct_stat.h dev: dev_t, ino: ino_t, nlink: nlink_t, @@ -7185,17 +7186,13 @@ pub const AT = switch (native_os) { pub const RECURSIVE = 0x8000; }, .wasi => struct { - pub const SYMLINK_NOFOLLOW = 0x100; - pub const SYMLINK_FOLLOW = 0x400; - pub const REMOVEDIR: u32 = 0x4; - /// When linking libc, we follow their convention and use -2 for current working directory. - /// However, without libc, Zig does a different convention: it assumes the - /// current working directory is the first preopen. This behavior can be - /// overridden with a public function called `wasi_cwd` in the root source - /// file. - pub const FDCWD: fd_t = if (builtin.link_libc) -2 else 3; + // Match AT_* constants in wasi-libc libc-bottom-half/headers/public/__header_fcntl.h + pub const FDCWD = -2; + pub const EACCESS = 0x0; + pub const SYMLINK_NOFOLLOW = 0x1; + pub const SYMLINK_FOLLOW = 0x2; + pub const REMOVEDIR = 0x4; }, - else => void, }; @@ -7224,6 +7221,7 @@ pub const O = switch (native_os) { _: u9 = 0, }, .wasi => packed struct(u32) { + // Match layout from wasi-libc libc-bottom-half/headers/public/__header_fcntl.h APPEND: bool = false, DSYNC: bool = false, NONBLOCK: bool = false, @@ -7236,10 +7234,17 @@ pub const O = switch (native_os) { TRUNC: bool = false, _16: u8 = 0, NOFOLLOW: bool = false, + + // Logical O_ACCMODE is the following 4 bits, note O_SEARCH is between RD and WR, + // so can't easily reuse std.posix.ACCMODE in this struct. EXEC: bool = false, - read: bool = false, + RDONLY: bool = true, // equivalent to "ACCMODE = .RDONLY" default SEARCH: bool = false, - write: bool = false, + WRONLY: bool = false, + + // CLOEXEC, TTY_ININT, NOCTTY are mapped 0, so they're silently + // ignored in C code. + _: u3 = 0, }, .solaris, .illumos => packed struct(u32) { @@ -9069,6 +9074,7 @@ pub const gettimeofday = switch (native_os) { pub const msync = switch (native_os) { .netbsd => private.__msync13, + .wasi => {}, else => private.msync, }; @@ -9211,8 +9217,8 @@ pub const fork = switch (native_os) { pub extern "c" fn access(path: [*:0]const u8, mode: c_uint) c_int; pub extern "c" fn faccessat(dirfd: fd_t, path: [*:0]const u8, mode: c_uint, flags: c_uint) c_int; pub extern "c" fn pipe(fds: *[2]fd_t) c_int; -pub extern "c" fn mkdir(path: [*:0]const u8, mode: c_uint) c_int; -pub extern "c" fn mkdirat(dirfd: fd_t, path: [*:0]const u8, mode: u32) c_int; +pub extern "c" fn mkdir(path: [*:0]const u8, mode: usize) c_int; +pub extern "c" fn mkdirat(dirfd: fd_t, path: [*:0]const u8, mode: usize) c_int; pub extern "c" fn symlink(existing: [*:0]const u8, new: [*:0]const u8) c_int; pub extern "c" fn symlinkat(oldpath: [*:0]const u8, newdirfd: fd_t, newpath: [*:0]const u8) c_int; pub extern "c" fn rename(old: [*:0]const u8, new: [*:0]const u8) c_int; diff --git a/lib/std/debug.zig b/lib/std/debug.zig index 826978abe42d..1a64ca0dee75 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -1034,7 +1034,7 @@ test printLineFromFileAnyOs { output.clearRetainingCapacity(); } { - const path = try fs.path.join(allocator, &.{ test_dir_path, "three_lines.zig" }); + const path = try join(allocator, &.{ test_dir_path, "three_lines.zig" }); defer allocator.free(path); try test_dir.dir.writeFile(.{ .sub_path = "three_lines.zig", @@ -1056,7 +1056,7 @@ test printLineFromFileAnyOs { { const file = try test_dir.dir.createFile("line_overlaps_page_boundary.zig", .{}); defer file.close(); - const path = try fs.path.join(allocator, &.{ test_dir_path, "line_overlaps_page_boundary.zig" }); + const path = try join(allocator, &.{ test_dir_path, "line_overlaps_page_boundary.zig" }); defer allocator.free(path); const overlap = 10; @@ -1072,7 +1072,7 @@ test printLineFromFileAnyOs { { const file = try test_dir.dir.createFile("file_ends_on_page_boundary.zig", .{}); defer file.close(); - const path = try fs.path.join(allocator, &.{ test_dir_path, "file_ends_on_page_boundary.zig" }); + const path = try join(allocator, &.{ test_dir_path, "file_ends_on_page_boundary.zig" }); defer allocator.free(path); var writer = file.writer(); @@ -1085,7 +1085,7 @@ test printLineFromFileAnyOs { { const file = try test_dir.dir.createFile("very_long_first_line_spanning_multiple_pages.zig", .{}); defer file.close(); - const path = try fs.path.join(allocator, &.{ test_dir_path, "very_long_first_line_spanning_multiple_pages.zig" }); + const path = try join(allocator, &.{ test_dir_path, "very_long_first_line_spanning_multiple_pages.zig" }); defer allocator.free(path); var writer = file.writer(); @@ -1110,7 +1110,7 @@ test printLineFromFileAnyOs { { const file = try test_dir.dir.createFile("file_of_newlines.zig", .{}); defer file.close(); - const path = try fs.path.join(allocator, &.{ test_dir_path, "file_of_newlines.zig" }); + const path = try join(allocator, &.{ test_dir_path, "file_of_newlines.zig" }); defer allocator.free(path); var writer = file.writer(); diff --git a/lib/std/fs/Dir.zig b/lib/std/fs/Dir.zig index d4ea8a010952..f0fecf67be84 100644 --- a/lib/std/fs/Dir.zig +++ b/lib/std/fs/Dir.zig @@ -2,7 +2,12 @@ fd: Handle, pub const Handle = posix.fd_t; -pub const default_mode = 0o755; +/// Default mode bits for a new directory. +pub const default_mode = switch (posix.mode_t) { + void => {}, // wasi-without-libc has no mode suppport + u0 => 0, // Zig's Posix layer doesn't support modes on Windows + else => 0o755, +}; pub const Entry = struct { name: []const u8, @@ -831,32 +836,19 @@ pub fn openFile(self: Dir, sub_path: []const u8, flags: File.OpenFlags) File.Ope /// Same as `openFile` but the path parameter is null-terminated. pub fn openFileZ(self: Dir, sub_path: [*:0]const u8, flags: File.OpenFlags) File.OpenError!File { - switch (native_os) { - .windows => { - const path_w = try windows.cStrToPrefixedFileW(self.fd, sub_path); - return self.openFileW(path_w.span(), flags); - }, - // Use the libc API when libc is linked because it implements things - // such as opening absolute file paths. - .wasi => if (!builtin.link_libc) { - return openFile(self, mem.sliceTo(sub_path, 0), flags); - }, - else => {}, + if (native_os == .windows) { + const path_w = try windows.cStrToPrefixedFileW(self.fd, sub_path); + return self.openFileW(path_w.span(), flags); + } + if (native_os == .wasi and !builtin.link_libc) { + return openFile(self, mem.sliceTo(sub_path, 0), flags); } - var os_flags: posix.O = switch (native_os) { - .wasi => .{ - .read = flags.mode != .write_only, - .write = flags.mode != .read_only, - }, - else => .{ - .ACCMODE = switch (flags.mode) { - .read_only => .RDONLY, - .write_only => .WRONLY, - .read_write => .RDWR, - }, - }, - }; + var os_flags: posix.O = std.posix.makeOFlags(switch (flags.mode) { + .read_only => .RDONLY, + .write_only => .WRONLY, + .read_write => .RDWR, + }, .{}); if (@hasField(posix.O, "CLOEXEC")) os_flags.CLOEXEC = true; if (@hasField(posix.O, "LARGEFILE")) os_flags.LARGEFILE = true; if (@hasField(posix.O, "NOCTTY")) os_flags.NOCTTY = !flags.allow_ctty; @@ -879,7 +871,8 @@ pub fn openFileZ(self: Dir, sub_path: [*:0]const u8, flags: File.OpenFlags) File }, } } - const fd = try posix.openatZ(self.fd, sub_path, os_flags, 0); + const mode = if (posix.mode_t == void) {} else 0; + const fd = try posix.openatZ(self.fd, sub_path, os_flags, mode); errdefer posix.close(fd); if (have_flock and !has_flock_open_flags and flags.lock != .none) { @@ -1002,12 +995,11 @@ pub fn createFileZ(self: Dir, sub_path_c: [*:0]const u8, flags: File.CreateFlags else => {}, } - var os_flags: posix.O = .{ - .ACCMODE = if (flags.read) .RDWR else .WRONLY, + var os_flags: posix.O = std.posix.makeOFlags(if (flags.read) .RDWR else .WRONLY, .{ .CREAT = true, .TRUNC = flags.truncate, .EXCL = flags.exclusive, - }; + }); if (@hasField(posix.O, "LARGEFILE")) os_flags.LARGEFILE = true; if (@hasField(posix.O, "CLOEXEC")) os_flags.CLOEXEC = true; @@ -1270,7 +1262,7 @@ pub const RealPathError = posix.RealPathError; /// See also `Dir.realpathZ`, `Dir.realpathW`, and `Dir.realpathAlloc`. pub fn realpath(self: Dir, pathname: []const u8, out_buffer: []u8) RealPathError![]u8 { if (native_os == .wasi) { - @compileError("realpath is not available on WASI"); + @compileError("WASI does not support absolute paths"); } if (native_os == .windows) { const pathname_w = try windows.sliceToPrefixedFileW(self.fd, pathname); @@ -1283,22 +1275,18 @@ pub fn realpath(self: Dir, pathname: []const u8, out_buffer: []u8) RealPathError /// Same as `Dir.realpath` except `pathname` is null-terminated. /// See also `Dir.realpath`, `realpathZ`. pub fn realpathZ(self: Dir, pathname: [*:0]const u8, out_buffer: []u8) RealPathError![]u8 { + if (native_os == .wasi) { + @compileError("WASI does not support absolute paths"); + } if (native_os == .windows) { const pathname_w = try windows.cStrToPrefixedFileW(self.fd, pathname); return self.realpathW(pathname_w.span(), out_buffer); } - const flags: posix.O = switch (native_os) { - .linux => .{ - .NONBLOCK = true, - .CLOEXEC = true, - .PATH = true, - }, - else => .{ - .NONBLOCK = true, - .CLOEXEC = true, - }, - }; + var flags: posix.O = .{}; + if (@hasField(posix.O, "NONBLOCK")) flags.NONBLOCK = true; + if (@hasField(posix.O, "CLOEXEC")) flags.CLOEXEC = true; + if (@hasField(posix.O, "PATH")) flags.PATH = true; const fd = posix.openatZ(self.fd, pathname, flags, 0) catch |err| switch (err) { error.FileLocksNotSupported => return error.Unexpected, @@ -1511,19 +1499,13 @@ pub fn openDirZ(self: Dir, sub_path_c: [*:0]const u8, args: OpenOptions) OpenErr else => {}, } - var symlink_flags: posix.O = switch (native_os) { - .wasi => .{ - .read = true, - .NOFOLLOW = args.no_follow, - .DIRECTORY = true, - }, - else => .{ - .ACCMODE = .RDONLY, - .NOFOLLOW = args.no_follow, - .DIRECTORY = true, - .CLOEXEC = true, - }, - }; + var symlink_flags: posix.O = std.posix.makeOFlags(.RDONLY, .{ + .NOFOLLOW = args.no_follow, + .DIRECTORY = true, + }); + + if (@hasField(posix.O, "CLOEXEC")) + symlink_flags.CLOEXEC = true; if (@hasField(posix.O, "PATH") and !args.iterate) symlink_flags.PATH = true; @@ -1556,7 +1538,8 @@ pub fn openDirW(self: Dir, sub_path_w: [*:0]const u16, args: OpenOptions) OpenEr /// Asserts `flags` has `DIRECTORY` set. fn openDirFlagsZ(self: Dir, sub_path_c: [*:0]const u8, flags: posix.O) OpenError!Dir { assert(flags.DIRECTORY); - const fd = posix.openatZ(self.fd, sub_path_c, flags, 0) catch |err| switch (err) { + const mode = if (posix.mode_t == void) {} else 0; + const fd = posix.openatZ(self.fd, sub_path_c, flags, mode) catch |err| switch (err) { error.FileTooBig => unreachable, // can't happen for directories error.IsDir => unreachable, // we're setting DIRECTORY error.NoSpaceLeft => unreachable, // not setting CREAT diff --git a/lib/std/fs/File.zig b/lib/std/fs/File.zig index 36e7999bf79d..10c474ab4e3c 100644 --- a/lib/std/fs/File.zig +++ b/lib/std/fs/File.zig @@ -27,9 +27,9 @@ pub const Kind = enum { /// the `touch` command, which would correspond to `0o644`. However, POSIX /// libc implementations use `0o666` inside `fopen` and then rely on the /// process-scoped "umask" setting to adjust this number for file creation. -pub const default_mode = switch (builtin.os.tag) { - .windows => 0, - .wasi => 0, +pub const default_mode = switch (posix.mode_t) { + void => {}, // WASI-without-libc has no mode support + u0 => 0, // Zig's Posix layer doesn't support modes for Windows else => 0o666, }; @@ -381,7 +381,7 @@ pub const Stat = struct { /// is unique to each filesystem. inode: INode, size: u64, - /// This is available on POSIX systems and is always 0 otherwise. + /// This is available on POSIX systems and is {} otherwise. mode: Mode, kind: Kind, @@ -399,7 +399,7 @@ pub const Stat = struct { return .{ .inode = st.ino, .size = @bitCast(st.size), - .mode = st.mode, + .mode = if (Mode == void) {} else st.mode, .kind = k: { const m = st.mode & posix.S.IFMT; switch (m) { @@ -455,7 +455,7 @@ pub const Stat = struct { return .{ .inode = st.ino, .size = @bitCast(st.size), - .mode = 0, + .mode = {}, .kind = switch (st.filetype) { .BLOCK_DEVICE => .block_device, .CHARACTER_DEVICE => .character_device, diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index b0810f81a548..4a20e58c2722 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -14,6 +14,16 @@ const File = std.fs.File; const tmpDir = testing.tmpDir; const SymLinkFlags = std.fs.Dir.SymLinkFlags; +// Filter to skip tests on platforms that don't support absolute paths +const supports_absolute_paths = std.os.isGetFdPathSupportedOnTarget(builtin.os); + +// Filter to skip tests on platforms that don't support file locking +const supports_file_locking = (builtin.os.tag != .wasi); + +// Filter to skip tests on platforms that don't support file modes +// WASI does not currently support chmod (but it should in the future) +const supports_chmod = (builtin.os.tag != .wasi and builtin.os.tag != .windows); + const PathType = enum { relative, absolute, @@ -279,12 +289,11 @@ test "File.stat on a File that is a symlink returns Kind.sym_link" { const sub_path_c = try posix.toPosixPath("symlink"); // the O_NOFOLLOW | O_PATH combination can obtain a fd to a symlink // note that if O_DIRECTORY is set, then this will error with ENOTDIR - const flags: posix.O = .{ + const flags: posix.O = std.posix.makeOFlags(.RDONLY, .{ .NOFOLLOW = true, .PATH = true, - .ACCMODE = .RDONLY, .CLOEXEC = true, - }; + }); const fd = try posix.openatZ(ctx.dir.fd, &sub_path_c, flags, 0); break :linux_symlink Dir{ .fd = fd }; }, @@ -315,7 +324,7 @@ test "openDir" { } test "accessAbsolute" { - if (native_os == .wasi) return error.SkipZigTest; + if (!supports_absolute_paths) return error.SkipZigTest; var tmp = tmpDir(.{}); defer tmp.cleanup(); @@ -333,7 +342,7 @@ test "accessAbsolute" { } test "openDirAbsolute" { - if (native_os == .wasi) return error.SkipZigTest; + if (!supports_absolute_paths) return error.SkipZigTest; var tmp = tmpDir(.{}); defer tmp.cleanup(); @@ -369,7 +378,7 @@ test "openDir cwd parent '..'" { test "openDir non-cwd parent '..'" { switch (native_os) { - .wasi, .netbsd, .openbsd => return error.SkipZigTest, + .netbsd, .openbsd => return error.SkipZigTest, else => {}, } @@ -382,17 +391,23 @@ test "openDir non-cwd parent '..'" { var dir = try subdir.openDir("..", .{}); defer dir.close(); - const expected_path = try tmp.dir.realpathAlloc(testing.allocator, "."); - defer testing.allocator.free(expected_path); + if (supports_absolute_paths) { + const expected_path = try tmp.dir.realpathAlloc(testing.allocator, "."); + defer testing.allocator.free(expected_path); - const actual_path = try dir.realpathAlloc(testing.allocator, "."); - defer testing.allocator.free(actual_path); + const actual_path = try dir.realpathAlloc(testing.allocator, "."); + defer testing.allocator.free(actual_path); - try testing.expectEqualStrings(expected_path, actual_path); + try testing.expectEqualStrings(expected_path, actual_path); + } + + const tmpStat = try tmp.dir.stat(); + const dirStat = try dir.stat(); + try testing.expectEqual(tmpStat.inode, dirStat.inode); } test "readLinkAbsolute" { - if (native_os == .wasi) return error.SkipZigTest; + if (!supports_absolute_paths) return error.SkipZigTest; var tmp = tmpDir(.{}); defer tmp.cleanup(); @@ -604,7 +619,7 @@ fn contains(entries: *const std.ArrayList(Dir.Entry), el: Dir.Entry) bool { } test "Dir.realpath smoke test" { - if (!comptime std.os.isGetFdPathSupportedOnTarget(builtin.os)) return error.SkipZigTest; + if (!supports_absolute_paths) return error.SkipZigTest; try testWithAllSupportedPathTypes(struct { fn impl(ctx: *TestContext) !void { @@ -728,7 +743,7 @@ test "directory operations on files" { try testing.expectError(error.NotDir, ctx.dir.openDir(test_file_name, .{})); try testing.expectError(error.NotDir, ctx.dir.deleteDir(test_file_name)); - if (ctx.path_type == .absolute and comptime PathType.absolute.isSupported(builtin.os)) { + if (ctx.path_type == .absolute and supports_absolute_paths) { try testing.expectError(error.PathAlreadyExists, fs.makeDirAbsolute(test_file_name)); try testing.expectError(error.NotDir, fs.deleteDirAbsolute(test_file_name)); } @@ -769,7 +784,7 @@ test "file operations on directories" { // TODO: Add a read-only test as well, see https://github.com/ziglang/zig/issues/5732 try testing.expectError(error.IsDir, ctx.dir.openFile(test_dir_name, .{ .mode = .read_write })); - if (ctx.path_type == .absolute and comptime PathType.absolute.isSupported(builtin.os)) { + if (ctx.path_type == .absolute and supports_absolute_paths) { try testing.expectError(error.IsDir, fs.createFileAbsolute(test_dir_name, .{})); try testing.expectError(error.IsDir, fs.deleteFileAbsolute(test_dir_name)); } @@ -980,7 +995,7 @@ test "rename" { } test "renameAbsolute" { - if (native_os == .wasi) return error.SkipZigTest; + if (!supports_absolute_paths) return error.SkipZigTest; var tmp_dir = tmpDir(.{}); defer tmp_dir.cleanup(); @@ -1524,7 +1539,7 @@ test "AtomicFile" { } test "open file with exclusive nonblocking lock twice" { - if (native_os == .wasi) return error.SkipZigTest; + if (!supports_file_locking) return error.SkipZigTest; try testWithAllSupportedPathTypes(struct { fn impl(ctx: *TestContext) !void { @@ -1540,7 +1555,7 @@ test "open file with exclusive nonblocking lock twice" { } test "open file with shared and exclusive nonblocking lock" { - if (native_os == .wasi) return error.SkipZigTest; + if (!supports_file_locking) return error.SkipZigTest; try testWithAllSupportedPathTypes(struct { fn impl(ctx: *TestContext) !void { @@ -1556,7 +1571,7 @@ test "open file with shared and exclusive nonblocking lock" { } test "open file with exclusive and shared nonblocking lock" { - if (native_os == .wasi) return error.SkipZigTest; + if (!supports_file_locking) return error.SkipZigTest; try testWithAllSupportedPathTypes(struct { fn impl(ctx: *TestContext) !void { @@ -1615,7 +1630,7 @@ test "open file with exclusive lock twice, make sure second lock waits" { } test "open file with exclusive nonblocking lock twice (absolute paths)" { - if (native_os == .wasi) return error.SkipZigTest; + if (!supports_file_locking or !supports_absolute_paths) return error.SkipZigTest; var random_bytes: [12]u8 = undefined; std.crypto.random.bytes(&random_bytes); @@ -1648,8 +1663,6 @@ test "open file with exclusive nonblocking lock twice (absolute paths)" { } test "walker" { - if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest; - var tmp = tmpDir(.{ .iterate = true }); defer tmp.cleanup(); @@ -1701,8 +1714,6 @@ test "walker" { } test "walker without fully iterating" { - if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest; - var tmp = tmpDir(.{ .iterate = true }); defer tmp.cleanup(); @@ -1724,8 +1735,6 @@ test "walker without fully iterating" { } test "'.' and '..' in fs.Dir functions" { - if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest; - if (native_os == .windows and builtin.cpu.arch == .aarch64) { // https://github.com/ziglang/zig/issues/17134 return error.SkipZigTest; @@ -1764,7 +1773,7 @@ test "'.' and '..' in fs.Dir functions" { } test "'.' and '..' in absolute functions" { - if (native_os == .wasi) return error.SkipZigTest; + if (!supports_absolute_paths) return error.SkipZigTest; var tmp = tmpDir(.{}); defer tmp.cleanup(); @@ -1808,8 +1817,7 @@ test "'.' and '..' in absolute functions" { } test "chmod" { - if (native_os == .windows or native_os == .wasi) - return error.SkipZigTest; + if (!supports_chmod) return error.SkipZigTest; var tmp = tmpDir(.{}); defer tmp.cleanup(); @@ -1830,8 +1838,7 @@ test "chmod" { } test "chown" { - if (native_os == .windows or native_os == .wasi) - return error.SkipZigTest; + if (!supports_chmod) return error.SkipZigTest; var tmp = tmpDir(.{}); defer tmp.cleanup(); @@ -1863,8 +1870,7 @@ test "File.Metadata" { } test "File.Permissions" { - if (native_os == .wasi) - return error.SkipZigTest; + if (!supports_chmod) return error.SkipZigTest; var tmp = tmpDir(.{}); defer tmp.cleanup(); @@ -1889,8 +1895,7 @@ test "File.Permissions" { } test "File.PermissionsUnix" { - if (native_os == .windows or native_os == .wasi) - return error.SkipZigTest; + if (!supports_chmod) return error.SkipZigTest; var tmp = tmpDir(.{}); defer tmp.cleanup(); @@ -2033,7 +2038,7 @@ test "invalid UTF-8/WTF-8 paths" { try testing.expectError(expected_err, ctx.dir.statFile(invalid_path)); - if (native_os != .wasi) { + if (supports_absolute_paths) { try testing.expectError(expected_err, ctx.dir.realpath(invalid_path, &[_]u8{})); try testing.expectError(expected_err, ctx.dir.realpathZ(invalid_path, &[_]u8{})); try testing.expectError(expected_err, ctx.dir.realpathAlloc(testing.allocator, invalid_path)); @@ -2042,7 +2047,7 @@ test "invalid UTF-8/WTF-8 paths" { try testing.expectError(expected_err, fs.rename(ctx.dir, invalid_path, ctx.dir, invalid_path)); try testing.expectError(expected_err, fs.renameZ(ctx.dir, invalid_path, ctx.dir, invalid_path)); - if (native_os != .wasi and ctx.path_type != .relative) { + if (supports_absolute_paths and ctx.path_type != .relative) { try testing.expectError(expected_err, fs.updateFileAbsolute(invalid_path, invalid_path, .{})); try testing.expectError(expected_err, fs.copyFileAbsolute(invalid_path, invalid_path, .{})); try testing.expectError(expected_err, fs.makeDirAbsolute(invalid_path)); diff --git a/lib/std/os/linux.zig b/lib/std/os/linux.zig index a29b381c401c..7703b17371d7 100644 --- a/lib/std/os/linux.zig +++ b/lib/std/os/linux.zig @@ -802,7 +802,7 @@ pub fn readlinkat(dirfd: i32, noalias path: [*:0]const u8, noalias buf_ptr: [*]u return syscall4(.readlinkat, @as(usize, @bitCast(@as(isize, dirfd))), @intFromPtr(path), @intFromPtr(buf_ptr), buf_len); } -pub fn mkdir(path: [*:0]const u8, mode: u32) usize { +pub fn mkdir(path: [*:0]const u8, mode: mode_t) usize { if (@hasField(SYS, "mkdir")) { return syscall2(.mkdir, @intFromPtr(path), mode); } else { @@ -810,11 +810,11 @@ pub fn mkdir(path: [*:0]const u8, mode: u32) usize { } } -pub fn mkdirat(dirfd: i32, path: [*:0]const u8, mode: u32) usize { +pub fn mkdirat(dirfd: i32, path: [*:0]const u8, mode: mode_t) usize { return syscall3(.mkdirat, @as(usize, @bitCast(@as(isize, dirfd))), @intFromPtr(path), mode); } -pub fn mknod(path: [*:0]const u8, mode: u32, dev: u32) usize { +pub fn mknod(path: [*:0]const u8, mode: mode_t, dev: u32) usize { if (@hasField(SYS, "mknod")) { return syscall3(.mknod, @intFromPtr(path), mode, dev); } else { @@ -822,7 +822,7 @@ pub fn mknod(path: [*:0]const u8, mode: u32, dev: u32) usize { } } -pub fn mknodat(dirfd: i32, path: [*:0]const u8, mode: u32, dev: u32) usize { +pub fn mknodat(dirfd: i32, path: [*:0]const u8, mode: mode_t, dev: u32) usize { return syscall4(.mknodat, @as(usize, @bitCast(@as(isize, dirfd))), @intFromPtr(path), mode, dev); } @@ -1041,7 +1041,7 @@ pub fn pread(fd: i32, buf: [*]u8, count: usize, offset: i64) usize { } } -pub fn access(path: [*:0]const u8, mode: u32) usize { +pub fn access(path: [*:0]const u8, mode: mode_t) usize { if (@hasField(SYS, "access")) { return syscall2(.access, @intFromPtr(path), mode); } else { @@ -1049,7 +1049,7 @@ pub fn access(path: [*:0]const u8, mode: u32) usize { } } -pub fn faccessat(dirfd: i32, path: [*:0]const u8, mode: u32, flags: u32) usize { +pub fn faccessat(dirfd: i32, path: [*:0]const u8, mode: mode_t, flags: u32) usize { return syscall4(.faccessat, @as(usize, @bitCast(@as(isize, dirfd))), @intFromPtr(path), mode, flags); } diff --git a/lib/std/os/wasi.zig b/lib/std/os/wasi.zig index 2c7d0d327211..0d393b60d30e 100644 --- a/lib/std/os/wasi.zig +++ b/lib/std/os/wasi.zig @@ -1,7 +1,7 @@ //! wasi_snapshot_preview1 spec available (in witx format) here: //! * typenames -- https://github.com/WebAssembly/WASI/blob/main/legacy/preview1/witx/typenames.witx //! * module -- https://github.com/WebAssembly/WASI/blob/main/legacy/preview1/witx/wasi_snapshot_preview1.witx -//! Note that libc API does *not* go in this file. wasi libc API goes into std/c/wasi.zig instead. +//! Note that libc API does *not* go in this file. wasi libc API goes into std/c.zig instead. const builtin = @import("builtin"); const std = @import("std"); const assert = std.debug.assert; diff --git a/lib/std/posix.zig b/lib/std/posix.zig index 665ea17788ac..5ab3440143eb 100644 --- a/lib/std/posix.zig +++ b/lib/std/posix.zig @@ -29,22 +29,194 @@ test { _ = @import("posix/test.zig"); } -/// Whether to use libc for the POSIX API layer. -const use_libc = builtin.link_libc or switch (native_os) { - .windows, .wasi => true, - else => false, -}; - const linux = std.os.linux; const windows = std.os.windows; const wasi = std.os.wasi; /// A libc-compatible API layer. -pub const system = if (use_libc) +pub const system = if (builtin.link_libc or native_os == .windows) std.c else switch (native_os) { .linux => linux, .plan9 => std.os.plan9, + .wasi => struct { + // This is the non-libc Wasi "system" implementation. See std.c + // for the with-libc variation. WASI is somewhat limited in a + // couple key ways that reduce the POSIX compatibilty: + // + // - no notion of "absolute paths". So calls to `realpath` + // or to look up the path for a file descriptor will + // @compileError. + // + // - no notion of file modes/permissions. The POSIX `mode_t` is + // void. + // + // - Immutable "current working directory". So APIs to + // change the working directory will @compileError. + // + // - No signals, mmap, fcntl, pipe, gethostname, fork, or pthread + // support. + + pub const PATH_MAX = 4096; + pub const STDIN_FILENO = 0; + pub const STDOUT_FILENO = 1; + pub const STDERR_FILENO = 2; + pub const E = wasi.errno_t; + pub const fd_t = wasi.fd_t; + pub const ino_t = wasi.inode_t; + pub const dev_t = wasi.device_t; + pub const nlink_t = wasi.linkcount_t; + pub const off_t = i64; + pub const time_t = i64; + pub const msync = {}; + pub const pid_t = void; + pub const pollfd = void; + pub const rlimit_resource = void; + pub const ucontext_t = void; + pub const AT = struct { + pub const FDCWD = 3; // Needs no translation. In Zig, fd 3 is a preopened cwd fd. + pub const SYMLINK_FOLLOW = 0x100; // for {sym,}linkat() + pub const SYMLINK_NOFOLLOW = 0x200; // for {f,}chmodat() + pub const REMOVEDIR = 0x400; + }; + + pub const Stat = struct { + // POSIX-y set of the fields supported by wasi.filestat_t. + dev: system.dev_t, + ino: system.ino_t, + nlink: system.nlink_t, + mode: u32, // only the file-type bits (S.IFMT) no permission bits + size: system.off_t, + atim: system.timespec, + mtim: system.timespec, + ctim: system.timespec, + + pub fn atime(self: @This()) system.timespec { + return self.atim; + } + + pub fn mtime(self: @This()) system.timespec { + return self.mtim; + } + + pub fn ctime(self: @This()) system.timespec { + return self.ctim; + } + + pub fn fromFilestat(st: wasi.filestat_t) @This() { + return .{ + .dev = st.dev, + .ino = st.ino, + .mode = switch (st.filetype) { + .UNKNOWN => 0, + .BLOCK_DEVICE => system.S.IFBLK, + .CHARACTER_DEVICE => system.S.IFCHR, + .DIRECTORY => system.S.IFDIR, + .REGULAR_FILE => system.S.IFREG, + .SOCKET_DGRAM => system.S.IFSOCK, + .SOCKET_STREAM => system.S.IFIFO, + .SYMBOLIC_LINK => system.S.IFLNK, + _ => 0, + }, + .nlink = st.nlink, + .size = @intCast(st.size), + .atim = system.timespec.fromTimestamp(st.atim), + .mtim = system.timespec.fromTimestamp(st.mtim), + .ctim = system.timespec.fromTimestamp(st.ctim), + }; + } + }; + + pub const S = struct { + // These constants here are not tied to any existing C headers. + pub const IFBLK = 0x1000; + pub const IFCHR = 0x2000; + pub const IFDIR = 0x3000; + pub const IFLNK = 0x4000; + pub const IFREG = 0x5000; + pub const IFSOCK = 0x6000; + pub const IFIFO = 0x7000; + pub const IFMT = IFBLK | IFCHR | IFDIR | IFIFO | IFLNK | IFREG | IFSOCK; + + pub fn ISBLK(m: u32) bool { + return m & IFMT == IFBLK; + } + + pub fn ISCHR(m: u32) bool { + return m & IFMT == IFCHR; + } + + pub fn ISDIR(m: u32) bool { + return m & IFMT == IFDIR; + } + + pub fn ISFIFO(m: u32) bool { + return m & IFMT == IFIFO; + } + + pub fn ISLNK(m: u32) bool { + return m & IFMT == IFLNK; + } + + pub fn ISREG(m: u32) bool { + return m & IFMT == IFREG; + } + + pub fn ISSOCK(m: u32) bool { + return m & IFMT == IFSOCK; + } + }; + + pub const timespec = struct { + sec: system.time_t, + nsec: isize, + + pub fn fromTimestamp(tm: wasi.timestamp_t) @This() { + const sec: wasi.timestamp_t = tm / 1_000_000_000; + const nsec = tm - sec * 1_000_000_000; + return .{ + .sec = @as(system.time_t, @intCast(sec)), + .nsec = @as(isize, @intCast(nsec)), + }; + } + + pub fn toTimestamp(ts: @This()) wasi.timestamp_t { + return @as(wasi.timestamp_t, @intCast(ts.sec * 1_000_000_000)) + + @as(wasi.timestamp_t, @intCast(ts.nsec)); + } + }; + + pub const mode_t = void; // Wasi does not (yet) support file mode/permission bits + + pub const O = packed struct(u32) { + // Map POSIX-y open flags into an amalgam of Wasi oflags_t, lookupflags_t, fdflags_t + // and ACCMODE. Doesn't need to match any existing C structure. + ACCMODE: ACCMODE = .RDONLY, + + // oflags_t: + CREAT: bool = false, + DIRECTORY: bool = false, + EXCL: bool = false, + TRUNC: bool = false, + + // fdflags_t: + APPEND: bool = false, + DSYNC: bool = false, + NONBLOCK: bool = false, + RSYNC: bool = false, + SYNC: bool = false, + + // lookupflags_t: + NOFOLLOW: bool = false, // POSIX reverses the polarity of Wasi SYMLINK_FOLLOW + + _: u20 = 0, + }; + + pub const F_OK = 0x000; + pub const X_OK = 0x001; + pub const W_OK = 0x002; + pub const R_OK = 0x004; + }, else => struct { pub const ucontext_t = void; pub const pid_t = void; @@ -52,6 +224,8 @@ else switch (native_os) { pub const fd_t = void; pub const uid_t = void; pub const gid_t = void; + pub const msync = {}; + pub const rlimit_resource = void; }, }; @@ -194,6 +368,19 @@ pub const ACCMODE = enum(u2) { RDWR = 2, }; +// Build O flags set from the access mode and given base flags. This is only necessary +// because of the Wasi-libc O flags do not put RDONLY and WRONLY in adjacent bits. +pub fn makeOFlags(acc: std.posix.ACCMODE, baseFlags: std.posix.O) std.posix.O { + var flags = baseFlags; + if (comptime (native_os == .wasi and builtin.link_libc)) { + flags.RDONLY = (acc == .RDONLY or acc == .RDWR); + flags.WRONLY = (acc == .WRONLY or acc == .RDWR); + } else { + flags.ACCMODE = acc; + } + return flags; +} + pub const TCSA = enum(c_uint) { NOW, DRAIN, @@ -243,7 +430,7 @@ pub const socket_t = if (native_os == .windows) windows.ws2_32.SOCKET else fd_t; /// function only returns a well-defined value when it is called directly after /// the system function call whose errno value is intended to be observed. pub fn errno(rc: anytype) E { - if (use_libc) { + if (builtin.link_libc) { return if (rc == -1) @enumFromInt(std.c._errno().*) else .SUCCESS; } const signed: isize = @bitCast(rc); @@ -1643,20 +1830,19 @@ pub fn openat(dir_fd: fd_t, file_path: []const u8, flags: O, mode: mode_t) OpenE if (native_os == .windows) { @compileError("Windows does not support POSIX; use Windows-specific API or cross-platform std.fs API"); } else if (native_os == .wasi and !builtin.link_libc) { - // `mode` is ignored on WASI, which does not support unix-style file permissions const opts = try openOptionsFromFlagsWasi(flags); const fd = try openatWasi( dir_fd, file_path, opts.lookup_flags, opts.oflags, - opts.fs_flags, + opts.fd_flags, opts.fs_rights_base, opts.fs_rights_inheriting, ); errdefer close(fd); - if (flags.write) { + if (flags.ACCMODE == .WRONLY or flags.ACCMODE == .RDWR) { const info = try std.os.fstat_wasi(fd); if (info.filetype == .DIRECTORY) return error.IsDir; @@ -1683,7 +1869,6 @@ pub fn openatWasi( switch (wasi.path_open(dir_fd, lookup_flags, file_path.ptr, file_path.len, oflags, base, inheriting, fdflags, &fd)) { .SUCCESS => return fd, .INTR => continue, - .FAULT => unreachable, // Provides INVAL with a linux host on a bad path name, but NOENT on Windows .INVAL => return error.BadPathName, @@ -1717,42 +1902,45 @@ const WasiOpenOptions = struct { lookup_flags: wasi.lookupflags_t, fs_rights_base: wasi.rights_t, fs_rights_inheriting: wasi.rights_t, - fs_flags: wasi.fdflags_t, + fd_flags: wasi.fdflags_t, }; /// Compute rights + flags corresponding to the provided POSIX access mode. -fn openOptionsFromFlagsWasi(oflag: O) OpenError!WasiOpenOptions { - const w = std.os.wasi; - - // Next, calculate the read/write rights to request, depending on the +fn openOptionsFromFlagsWasi(posixFlags: O) OpenError!WasiOpenOptions { + // Calculate the read/write rights to request, depending on the // provided POSIX access mode - var rights: w.rights_t = .{}; - if (oflag.read) { + var rights: std.os.wasi.rights_t = .{}; + const accmode = posixFlags.ACCMODE; + if (accmode == .RDONLY or accmode == .RDWR) { rights.FD_READ = true; rights.FD_READDIR = true; } - if (oflag.write) { + if (accmode == .WRONLY or accmode == .RDWR) { rights.FD_DATASYNC = true; rights.FD_WRITE = true; rights.FD_ALLOCATE = true; rights.FD_FILESTAT_SET_SIZE = true; } - // https://github.com/ziglang/zig/issues/18882 - const flag_bits: u32 = @bitCast(oflag); - const oflags_int: u16 = @as(u12, @truncate(flag_bits >> 12)); - const fs_flags_int: u16 = @as(u12, @truncate(flag_bits)); - return .{ - // https://github.com/ziglang/zig/issues/18882 - .oflags = @bitCast(oflags_int), + .oflags = .{ + .CREAT = posixFlags.CREAT, + .DIRECTORY = posixFlags.DIRECTORY, + .EXCL = posixFlags.EXCL, + .TRUNC = posixFlags.TRUNC, + }, .lookup_flags = .{ - .SYMLINK_FOLLOW = !oflag.NOFOLLOW, + .SYMLINK_FOLLOW = !posixFlags.NOFOLLOW, }, .fs_rights_base = rights, .fs_rights_inheriting = rights, - // https://github.com/ziglang/zig/issues/18882 - .fs_flags = @bitCast(fs_flags_int), + .fd_flags = .{ + .APPEND = posixFlags.APPEND, + .NONBLOCK = posixFlags.NONBLOCK, + .DSYNC = posixFlags.DSYNC, + .RSYNC = posixFlags.RSYNC, + .SYNC = posixFlags.SYNC, + }, }; } @@ -1771,7 +1959,8 @@ pub fn openatZ(dir_fd: fd_t, file_path: [*:0]const u8, flags: O, mode: mode_t) O const openat_sym = if (lfs64_abi) system.openat64 else system.openat; while (true) { - const rc = openat_sym(dir_fd, file_path, flags, mode); + const syscall_mode = if (mode_t == void) @as(u32, 0) else mode; + const rc = openat_sym(dir_fd, file_path, flags, syscall_mode); switch (errno(rc)) { .SUCCESS => return @intCast(rc), .INTR => continue, @@ -2070,7 +2259,7 @@ pub fn symlink(target_path: []const u8, sym_link_path: []const u8) SymLinkError! if (native_os == .windows) { @compileError("symlink is not supported on Windows; use std.os.windows.CreateSymbolicLink instead"); } else if (native_os == .wasi and !builtin.link_libc) { - return symlinkat(target_path, wasi.AT.FDCWD, sym_link_path); + return symlinkat(target_path, AT.FDCWD, sym_link_path); } const target_path_c = try toPosixPath(target_path); const sym_link_path_c = try toPosixPath(sym_link_path); @@ -2240,7 +2429,7 @@ pub fn linkZ(oldpath: [*:0]const u8, newpath: [*:0]const u8) LinkError!void { /// On other platforms, both paths are an opaque sequence of bytes with no particular encoding. pub fn link(oldpath: []const u8, newpath: []const u8) LinkError!void { if (native_os == .wasi and !builtin.link_libc) { - return linkat(wasi.AT.FDCWD, oldpath, wasi.AT.FDCWD, newpath, 0) catch |err| switch (err) { + return linkat(AT.FDCWD, oldpath, AT.FDCWD, newpath, 0) catch |err| switch (err) { error.NotDir => unreachable, // link() does not support directories else => |e| return e, }; @@ -2377,7 +2566,7 @@ pub const UnlinkError = error{ /// See also `unlinkZ`. pub fn unlink(file_path: []const u8) UnlinkError!void { if (native_os == .wasi and !builtin.link_libc) { - return unlinkat(wasi.AT.FDCWD, file_path, 0) catch |err| switch (err) { + return unlinkat(AT.FDCWD, file_path, 0) catch |err| switch (err) { error.DirNotEmpty => unreachable, // only occurs when targeting directories else => |e| return e, }; @@ -2571,7 +2760,7 @@ pub const RenameError = error{ /// On other platforms, both paths are an opaque sequence of bytes with no particular encoding. pub fn rename(old_path: []const u8, new_path: []const u8) RenameError!void { if (native_os == .wasi and !builtin.link_libc) { - return renameat(wasi.AT.FDCWD, old_path, wasi.AT.FDCWD, new_path); + return renameat(AT.FDCWD, old_path, AT.FDCWD, new_path); } else if (native_os == .windows) { const old_path_w = try windows.sliceToPrefixedFileW(null, old_path); const new_path_w = try windows.sliceToPrefixedFileW(null, new_path); @@ -2845,7 +3034,7 @@ pub fn renameatW( /// On Windows, `sub_dir_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). /// On WASI, `sub_dir_path` should be encoded as valid UTF-8. /// On other platforms, `sub_dir_path` is an opaque sequence of bytes with no particular encoding. -pub fn mkdirat(dir_fd: fd_t, sub_dir_path: []const u8, mode: u32) MakeDirError!void { +pub fn mkdirat(dir_fd: fd_t, sub_dir_path: []const u8, mode: mode_t) MakeDirError!void { if (native_os == .windows) { const sub_dir_path_w = try windows.sliceToPrefixedFileW(dir_fd, sub_dir_path); return mkdiratW(dir_fd, sub_dir_path_w.span(), mode); @@ -2857,7 +3046,7 @@ pub fn mkdirat(dir_fd: fd_t, sub_dir_path: []const u8, mode: u32) MakeDirError!v } } -pub fn mkdiratWasi(dir_fd: fd_t, sub_dir_path: []const u8, mode: u32) MakeDirError!void { +pub fn mkdiratWasi(dir_fd: fd_t, sub_dir_path: []const u8, mode: mode_t) MakeDirError!void { _ = mode; switch (wasi.path_create_directory(dir_fd, sub_dir_path.ptr, sub_dir_path.len)) { .SUCCESS => return, @@ -2882,14 +3071,15 @@ pub fn mkdiratWasi(dir_fd: fd_t, sub_dir_path: []const u8, mode: u32) MakeDirErr } /// Same as `mkdirat` except the parameters are null-terminated. -pub fn mkdiratZ(dir_fd: fd_t, sub_dir_path: [*:0]const u8, mode: u32) MakeDirError!void { +pub fn mkdiratZ(dir_fd: fd_t, sub_dir_path: [*:0]const u8, mode: mode_t) MakeDirError!void { if (native_os == .windows) { const sub_dir_path_w = try windows.cStrToPrefixedFileW(dir_fd, sub_dir_path); return mkdiratW(dir_fd, sub_dir_path_w.span(), mode); } else if (native_os == .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))) { + const syscall_mode = if (mode_t == void) @as(u32, 0) else mode; + switch (errno(system.mkdirat(dir_fd, sub_dir_path, syscall_mode))) { .SUCCESS => return, .ACCES => return error.AccessDenied, .BADF => unreachable, @@ -2916,7 +3106,7 @@ pub fn mkdiratZ(dir_fd: fd_t, sub_dir_path: [*:0]const u8, mode: u32) MakeDirErr } /// Windows-only. Same as `mkdirat` except the parameter WTF16 LE encoded. -pub fn mkdiratW(dir_fd: fd_t, sub_path_w: []const u16, mode: u32) MakeDirError!void { +pub fn mkdiratW(dir_fd: fd_t, sub_path_w: []const u16, mode: mode_t) MakeDirError!void { _ = mode; const sub_dir_handle = windows.OpenFile(sub_path_w, .{ .dir = dir_fd, @@ -2963,9 +3153,9 @@ pub const MakeDirError = error{ /// On Windows, `dir_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). /// On WASI, `dir_path` should be encoded as valid UTF-8. /// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding. -pub fn mkdir(dir_path: []const u8, mode: u32) MakeDirError!void { +pub fn mkdir(dir_path: []const u8, mode: mode_t) MakeDirError!void { if (native_os == .wasi and !builtin.link_libc) { - return mkdirat(wasi.AT.FDCWD, dir_path, mode); + return mkdirat(AT.FDCWD, dir_path, mode); } else if (native_os == .windows) { const dir_path_w = try windows.sliceToPrefixedFileW(null, dir_path); return mkdirW(dir_path_w.span(), mode); @@ -2979,7 +3169,7 @@ pub fn mkdir(dir_path: []const u8, mode: u32) MakeDirError!void { /// On Windows, `dir_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). /// On WASI, `dir_path` should be encoded as valid UTF-8. /// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding. -pub fn mkdirZ(dir_path: [*:0]const u8, mode: u32) MakeDirError!void { +pub fn mkdirZ(dir_path: [*:0]const u8, mode: mode_t) MakeDirError!void { if (native_os == .windows) { const dir_path_w = try windows.cStrToPrefixedFileW(null, dir_path); return mkdirW(dir_path_w.span(), mode); @@ -3010,7 +3200,7 @@ pub fn mkdirZ(dir_path: [*:0]const u8, mode: u32) MakeDirError!void { } /// Windows-only. Same as `mkdir` but the parameters is WTF16LE encoded. -pub fn mkdirW(dir_path_w: []const u16, mode: u32) MakeDirError!void { +pub fn mkdirW(dir_path_w: []const u16, mode: mode_t) MakeDirError!void { _ = mode; const sub_dir_handle = windows.OpenFile(dir_path_w, .{ .dir = fs.cwd().fd, @@ -3053,7 +3243,7 @@ pub const DeleteDirError = error{ /// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding. pub fn rmdir(dir_path: []const u8) DeleteDirError!void { if (native_os == .wasi and !builtin.link_libc) { - return unlinkat(wasi.AT.FDCWD, dir_path, AT.REMOVEDIR) catch |err| switch (err) { + return unlinkat(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, @@ -3242,7 +3432,7 @@ pub const ReadLinkError = error{ /// On other platforms, the result is an opaque sequence of bytes with no particular encoding. pub fn readlink(file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 { if (native_os == .wasi and !builtin.link_libc) { - return readlinkat(wasi.AT.FDCWD, file_path, out_buffer); + return readlinkat(AT.FDCWD, file_path, out_buffer); } else if (native_os == .windows) { const file_path_w = try windows.sliceToPrefixedFileW(null, file_path); return readlinkW(file_path_w.span(), out_buffer); @@ -4381,10 +4571,9 @@ pub fn fstatat(dirfd: fd_t, pathname: []const u8, flags: u32) FStatAtError!Stat /// See also `fstatat`. pub fn fstatatZ(dirfd: fd_t, pathname: [*:0]const u8, flags: u32) FStatAtError!Stat { if (native_os == .wasi and !builtin.link_libc) { - const filestat = try std.os.fstatat_wasi(dirfd, mem.sliceTo(pathname, 0), .{ - .SYMLINK_FOLLOW = (flags & AT.SYMLINK_NOFOLLOW) == 0, - }); - return Stat.fromFilestat(filestat); + return fstatat(dirfd, mem.sliceTo(pathname, 0), flags); + } else if (native_os == .windows) { + @compileError("fstatatZ is not yet implemented on Windows"); } const fstatat_sym = if (lfs64_abi) system.fstatat64 else system.fstatat; @@ -4793,7 +4982,7 @@ pub fn access(path: []const u8, mode: u32) AccessError!void { _ = try windows.GetFileAttributesW(path_w.span().ptr); return; } else if (native_os == .wasi and !builtin.link_libc) { - return faccessat(wasi.AT.FDCWD, path, mode, 0); + return faccessat(AT.FDCWD, path, mode, 0); } const path_c = try toPosixPath(path); return accessZ(&path_c, mode); @@ -4842,16 +5031,17 @@ pub fn accessZ(path: [*:0]const u8, mode: u32) AccessError!void { /// Windows. See `fs` for the cross-platform file system API. pub fn faccessat(dirfd: fd_t, path: []const u8, mode: u32, flags: u32) AccessError!void { if (native_os == .windows) { - const path_w = try windows.sliceToPrefixedFileW(dirfd, path); + const path_w = try windows.sliceToPrefixedFileW(dirfd, path) catch |err| switch (err) { + error.AccessDenied => return error.PermissionDenied, + else => |e| return e, + }; return faccessatW(dirfd, path_w.span().ptr); } else if (native_os == .wasi and !builtin.link_libc) { const resolved: RelativePathWasi = .{ .dir_fd = dirfd, .relative_path = path }; - const st = blk: { - break :blk std.os.fstatat_wasi(dirfd, path, .{ - .SYMLINK_FOLLOW = (flags & AT.SYMLINK_NOFOLLOW) == 0, - }); - } catch |err| switch (err) { + const st = std.os.fstatat_wasi(resolved.dir_fd, path, .{ + .SYMLINK_FOLLOW = (flags & AT.SYMLINK_NOFOLLOW) == 0, + }) catch |err| switch (err) { error.AccessDenied => return error.PermissionDenied, else => |e| return e, }; @@ -5043,7 +5233,7 @@ pub fn sysctl( newlen: usize, ) SysCtlError!void { if (native_os == .wasi) { - @panic("unsupported"); // TODO should be compile error, not panic + @compileError("sysctl is not supported on WASI"); } if (native_os == .haiku) { @panic("unsupported"); // TODO should be compile error, not panic @@ -5068,7 +5258,7 @@ pub fn sysctlbynameZ( newlen: usize, ) SysCtlError!void { if (native_os == .wasi) { - @panic("unsupported"); // TODO should be compile error, not panic + @compileError("sysctlbynameZ is not supported on WASI"); } if (native_os == .haiku) { @panic("unsupported"); // TODO should be compile error, not panic @@ -5085,6 +5275,10 @@ pub fn sysctlbynameZ( } pub fn gettimeofday(tv: ?*timeval, tz: ?*timezone) void { + if (native_os == .wasi and !builtin.link_libc) { + @compileError("gettimeofday is not implemented on no-libc WASI yet"); + } + switch (errno(system.gettimeofday(tv, tz))) { .SUCCESS => return, .INVAL => unreachable, @@ -5391,8 +5585,8 @@ pub fn realpath(pathname: []const u8, out_buffer: *[max_path_bytes]u8) RealPathE if (native_os == .windows) { const pathname_w = try windows.sliceToPrefixedFileW(null, pathname); return realpathW(pathname_w.span(), out_buffer); - } else if (native_os == .wasi and !builtin.link_libc) { - @compileError("WASI does not support os.realpath"); + } else if (native_os == .wasi) { + @compileError("WASI has no support for absolute paths"); } const pathname_c = try toPosixPath(pathname); return realpathZ(&pathname_c, out_buffer); @@ -5405,8 +5599,8 @@ pub fn realpathZ(pathname: [*:0]const u8, out_buffer: *[max_path_bytes]u8) RealP if (native_os == .windows) { const pathname_w = try windows.cStrToPrefixedFileW(null, pathname); return realpathW(pathname_w.span(), out_buffer); - } else if (native_os == .wasi and !builtin.link_libc) { - return realpath(mem.sliceTo(pathname, 0), out_buffer); + } else if (native_os == .wasi) { + @compileError("WASI has no support for absolute paths"); } if (!builtin.link_libc) { const flags: O = switch (native_os) { diff --git a/lib/std/posix/test.zig b/lib/std/posix/test.zig index 7ba9a7701eba..c6957d5296b7 100644 --- a/lib/std/posix/test.zig +++ b/lib/std/posix/test.zig @@ -22,6 +22,32 @@ const tmpDir = std.testing.tmpDir; const Dir = std.fs.Dir; const ArenaAllocator = std.heap.ArenaAllocator; +// Filter to skip tests on platforms that don't support chdir. +const supports_chdir = (native_os != .wasi); + +// Filter to skip tests on platforms that don't support absolute paths +const supports_absolute_paths = (native_os != .wasi); + +// Tests that create/delete files in the current working directory are not safe to run +// in CI. So this is off by default. But handy when testing. Do not submit true. +// Test should probably be fixed to use unique names to avoid races. +// +// https://github.com/ziglang/zig/issues/14968 +const enable_sketchy_cwd_tests = true; + +test "check WASI CWD" { + if (native_os == .wasi) { + if (std.options.wasiCwd() != 3) { + @panic("WASI code that uses cwd (like this test) needs a preopen for cwd (add '--dir=.' to wasmtime)"); + } + + if (!builtin.link_libc) { + // WASI without-libc hardcodes fd 3 as the FDCWD token so it can be passed directly to WASI calls + try expectEqual(3, posix.AT.FDCWD); + } + } +} + // https://github.com/ziglang/zig/issues/20288 test "WTF-8 to WTF-16 conversion buffer overflows" { if (native_os != .windows) return error.SkipZigTest; @@ -32,12 +58,8 @@ test "WTF-8 to WTF-16 conversion buffer overflows" { } test "chdir smoke test" { - if (native_os == .wasi) return error.SkipZigTest; - - if (true) { - // https://github.com/ziglang/zig/issues/14968 - return error.SkipZigTest; - } + if (!supports_chdir) return error.SkipZigTest; + if (!enable_sketchy_cwd_tests) return error.SkipZigTest; // Get current working directory path var old_cwd_buf: [fs.max_path_bytes]u8 = undefined; @@ -95,8 +117,14 @@ test "chdir smoke test" { } } +const default_mode = switch (posix.mode_t) { + void => {}, + u0 => 0, + else => 0o666, +}; + test "open smoke test" { - if (native_os == .wasi) return error.SkipZigTest; + if (!supports_absolute_paths) return error.SkipZigTest; if (native_os == .windows) return error.SkipZigTest; // TODO verify file attributes using `fstat` @@ -116,7 +144,7 @@ test "open smoke test" { var file_path: []u8 = undefined; var fd: posix.fd_t = undefined; - const mode: posix.mode_t = if (native_os == .windows) 0 else 0o666; + const mode: posix.mode_t = default_mode; // Create some file using `open`. file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_file" }); @@ -151,7 +179,6 @@ test "open smoke test" { } test "openat smoke test" { - if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest; if (native_os == .windows) return error.SkipZigTest; // TODO verify file attributes using `fstatat` @@ -160,7 +187,7 @@ test "openat smoke test" { defer tmp.cleanup(); var fd: posix.fd_t = undefined; - const mode: posix.mode_t = if (native_os == .windows) 0 else 0o666; + const mode: posix.mode_t = default_mode; // Create some file using `openat`. fd = try posix.openat(tmp.dir.fd, "some_file", CommonOpenFlags.lower(.{ @@ -207,12 +234,8 @@ test "openat smoke test" { } test "symlink with relative paths" { - if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest; + if (!enable_sketchy_cwd_tests) return error.SkipZigTest; - if (true) { - // https://github.com/ziglang/zig/issues/14968 - return error.SkipZigTest; - } const cwd = fs.cwd(); cwd.deleteFile("file.txt") catch {}; cwd.deleteFile("symlinked") catch {}; @@ -262,16 +285,12 @@ fn testReadlink(target_path: []const u8, symlink_path: []const u8) !void { } test "link with relative paths" { - if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest; - switch (native_os) { .wasi, .linux, .solaris, .illumos => {}, else => return error.SkipZigTest, } - if (true) { - // https://github.com/ziglang/zig/issues/14968 - return error.SkipZigTest; - } + if (!enable_sketchy_cwd_tests) return error.SkipZigTest; + var cwd = fs.cwd(); cwd.deleteFile("example.txt") catch {}; @@ -305,16 +324,12 @@ test "link with relative paths" { } test "linkat with different directories" { - if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest; - switch (native_os) { .wasi, .linux, .solaris, .illumos => {}, else => return error.SkipZigTest, } - if (true) { - // https://github.com/ziglang/zig/issues/14968 - return error.SkipZigTest; - } + if (!enable_sketchy_cwd_tests) return error.SkipZigTest; + var cwd = fs.cwd(); var tmp = tmpDir(.{}); @@ -365,7 +380,7 @@ test "fstatat" { defer file.close(); // now repeat but using `fstatat` instead - const flags = if (native_os == .wasi) 0x0 else posix.AT.SYMLINK_NOFOLLOW; + const flags = posix.AT.SYMLINK_NOFOLLOW; const statat = try posix.fstatat(tmp.dir.fd, "file.txt", flags); try expectEqual(stat, statat); } @@ -1014,7 +1029,7 @@ test "POSIX file locking with fcntl" { } test "rename smoke test" { - if (native_os == .wasi) return error.SkipZigTest; + if (!supports_absolute_paths) return error.SkipZigTest; // Or fix test to use relative paths if (native_os == .windows) return error.SkipZigTest; var tmp = tmpDir(.{}); @@ -1032,7 +1047,7 @@ test "rename smoke test" { var file_path: []u8 = undefined; var fd: posix.fd_t = undefined; - const mode: posix.mode_t = if (native_os == .windows) 0 else 0o666; + const mode: posix.mode_t = default_mode; // Create some file using `open`. file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_file" }); @@ -1071,7 +1086,7 @@ test "rename smoke test" { } test "access smoke test" { - if (native_os == .wasi) return error.SkipZigTest; + if (!supports_absolute_paths) return error.SkipZigTest; // Or fix test to use relative paths if (native_os == .windows) return error.SkipZigTest; var tmp = tmpDir(.{}); @@ -1089,7 +1104,7 @@ test "access smoke test" { var file_path: []u8 = undefined; var fd: posix.fd_t = undefined; - const mode: posix.mode_t = if (native_os == .windows) 0 else 0o666; + const mode: posix.mode_t = default_mode; // Create some file using `open`. file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_file" }); @@ -1146,7 +1161,7 @@ test "isatty" { } test "read with empty buffer" { - if (native_os == .wasi) return error.SkipZigTest; + if (!supports_absolute_paths) return error.SkipZigTest; // Or fix test to use relative paths var tmp = tmpDir(.{}); defer tmp.cleanup(); @@ -1171,7 +1186,7 @@ test "read with empty buffer" { } test "pread with empty buffer" { - if (native_os == .wasi) return error.SkipZigTest; + if (!supports_absolute_paths) return error.SkipZigTest; // Or fix test to use relative paths var tmp = tmpDir(.{}); defer tmp.cleanup(); @@ -1196,7 +1211,7 @@ test "pread with empty buffer" { } test "write with empty buffer" { - if (native_os == .wasi) return error.SkipZigTest; + if (!supports_absolute_paths) return error.SkipZigTest; // Or fix test to use relative paths var tmp = tmpDir(.{}); defer tmp.cleanup(); @@ -1221,7 +1236,7 @@ test "write with empty buffer" { } test "pwrite with empty buffer" { - if (native_os == .wasi) return error.SkipZigTest; + if (!supports_absolute_paths) return error.SkipZigTest; // Or fix test to use relative paths var tmp = tmpDir(.{}); defer tmp.cleanup(); @@ -1299,22 +1314,14 @@ const CommonOpenFlags = packed struct { NONBLOCK: bool = false, pub fn lower(cof: CommonOpenFlags) posix.O { - if (native_os == .wasi) return .{ - .read = cof.ACCMODE != .WRONLY, - .write = cof.ACCMODE != .RDONLY, - .CREAT = cof.CREAT, - .EXCL = cof.EXCL, - .DIRECTORY = cof.DIRECTORY, - .NONBLOCK = cof.NONBLOCK, - }; - var result: posix.O = .{ - .ACCMODE = cof.ACCMODE, + var result: posix.O = std.posix.makeOFlags(cof.ACCMODE, .{ .CREAT = cof.CREAT, .EXCL = cof.EXCL, .DIRECTORY = cof.DIRECTORY, .NONBLOCK = cof.NONBLOCK, - .CLOEXEC = cof.CLOEXEC, - }; + }); + + if (@hasField(posix.O, "CLOEXEC")) result.CLOEXEC = cof.CLOEXEC; if (@hasField(posix.O, "LARGEFILE")) result.LARGEFILE = cof.LARGEFILE; return result; } From d5d7e2821b2b228ffd82d60a49627a89964d244c Mon Sep 17 00:00:00 2001 From: Pat Tullmann Date: Sun, 4 Aug 2024 09:56:21 -0700 Subject: [PATCH 2/5] windows: Posix mode_t is void, not u0 now To be consistent with the Wasi mode_t, use 'void'. --- lib/std/c.zig | 5 ++--- lib/std/fs/Dir.zig | 3 +-- lib/std/fs/File.zig | 5 ++--- lib/std/posix/test.zig | 1 - src/link.zig | 2 +- src/link/Wasm.zig | 3 +-- 6 files changed, 7 insertions(+), 12 deletions(-) diff --git a/lib/std/c.zig b/lib/std/c.zig index e736391dc060..17cfb7fd77aa 100644 --- a/lib/std/c.zig +++ b/lib/std/c.zig @@ -135,9 +135,8 @@ pub const mode_t = switch (native_os) { .emscripten => emscripten.mode_t, .openbsd, .haiku, .netbsd, .solaris, .illumos => u32, .freebsd, .macos, .ios, .tvos, .watchos, .visionos => u16, - .wasi => void, - .windows => u0, - else => u0, // TODO: should be void? + .wasi, .windows => void, + else => void, }; pub const nlink_t = switch (native_os) { diff --git a/lib/std/fs/Dir.zig b/lib/std/fs/Dir.zig index f0fecf67be84..99d7401183ba 100644 --- a/lib/std/fs/Dir.zig +++ b/lib/std/fs/Dir.zig @@ -4,8 +4,7 @@ pub const Handle = posix.fd_t; /// Default mode bits for a new directory. pub const default_mode = switch (posix.mode_t) { - void => {}, // wasi-without-libc has no mode suppport - u0 => 0, // Zig's Posix layer doesn't support modes on Windows + void => {}, else => 0o755, }; diff --git a/lib/std/fs/File.zig b/lib/std/fs/File.zig index 10c474ab4e3c..5c4399b45ebf 100644 --- a/lib/std/fs/File.zig +++ b/lib/std/fs/File.zig @@ -28,8 +28,7 @@ pub const Kind = enum { /// libc implementations use `0o666` inside `fopen` and then rely on the /// process-scoped "umask" setting to adjust this number for file creation. pub const default_mode = switch (posix.mode_t) { - void => {}, // WASI-without-libc has no mode support - u0 => 0, // Zig's Posix layer doesn't support modes for Windows + void => {}, else => 0o666, }; @@ -495,7 +494,7 @@ pub fn stat(self: File) StatError!Stat { return .{ .inode = info.InternalInformation.IndexNumber, .size = @as(u64, @bitCast(info.StandardInformation.EndOfFile)), - .mode = 0, + .mode = {}, .kind = if (info.BasicInformation.FileAttributes & windows.FILE_ATTRIBUTE_REPARSE_POINT != 0) reparse_point: { var tag_info: windows.FILE_ATTRIBUTE_TAG_INFO = undefined; const tag_rc = windows.ntdll.NtQueryInformationFile(self.handle, &io_status_block, &tag_info, @sizeOf(windows.FILE_ATTRIBUTE_TAG_INFO), .FileAttributeTagInformation); diff --git a/lib/std/posix/test.zig b/lib/std/posix/test.zig index c6957d5296b7..bf73cea84e67 100644 --- a/lib/std/posix/test.zig +++ b/lib/std/posix/test.zig @@ -119,7 +119,6 @@ test "chdir smoke test" { const default_mode = switch (posix.mode_t) { void => {}, - u0 => 0, else => 0o666, }; diff --git a/src/link.zig b/src/link.zig index 894074cddaad..44f264ad24d0 100644 --- a/src/link.zig +++ b/src/link.zig @@ -985,7 +985,7 @@ pub const File = struct { // with 0o755 permissions, but it works appropriately if the system is configured // more leniently. As another data point, C's fopen seems to open files with the // 666 mode. - const executable_mode = if (builtin.target.os.tag == .windows) 0 else 0o777; + const executable_mode = if (fs.File.Mode == void) {} else 0o777; switch (effectiveOutputMode(use_lld, output_mode)) { .Lib => return switch (link_mode) { .dynamic => executable_mode, diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index 2c5fc3fe7a28..d73dd8179a0a 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -439,8 +439,7 @@ pub fn createEmpty( fs.File.default_mode | 0b001_000_000 else fs.File.default_mode - else - 0, + else {}, }); wasm.name = sub_path; From ddc776d6f83cc3394f13d7e075a447dc6e720fd3 Mon Sep 17 00:00:00 2001 From: Pat Tullmann Date: Mon, 5 Aug 2024 20:24:52 -0700 Subject: [PATCH 3/5] {fs,io,posix,debug} tests: clean up and Wasi improvements Add `unique_fname` for creating unique-name files when testing in the default CWD (though tests should still prefer tmpDir()). Enable the tests disabled because this is racy (#14968). Add some WASI-specific behavior handling. Other cleanups of the tests: * use `realpathAlloc()`to get fullpath into tmpDir() instances * use `testing.allocator` where that simplifies things (vs. manual ArenaAllocator for 1 or 2 allocs) * Trust `TmpDir.cleanup()` to clean up sub-trees * Remove some unnecessary absolute paths (to enable WASI tests) * Drop some no-longer necessary `[_][]const u8` explicit types * Put file cleanups into `defer` Fixes #14968 --- lib/std/debug.zig | 9 +- lib/std/fs/test.zig | 121 ++++++---- lib/std/io/test.zig | 16 +- lib/std/posix/test.zig | 525 ++++++++++++++++++++--------------------- 4 files changed, 341 insertions(+), 330 deletions(-) diff --git a/lib/std/debug.zig b/lib/std/debug.zig index 1a64ca0dee75..1a097c363d26 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -1017,12 +1017,12 @@ test printLineFromFileAnyOs { var test_dir = std.testing.tmpDir(.{}); defer test_dir.cleanup(); - // Relies on testing.tmpDir internals which is not ideal, but SourceLocation requires paths. + // Relies on testing.tmpDir internals which is not ideal, but SourceLocation requires paths relative to cwd. const test_dir_path = try join(allocator, &.{ ".zig-cache", "tmp", test_dir.sub_path[0..] }); defer allocator.free(test_dir_path); - // Cases { + // no newlines in file const path = try join(allocator, &.{ test_dir_path, "one_line.zig" }); defer allocator.free(path); try test_dir.dir.writeFile(.{ .sub_path = "one_line.zig", .data = "no new lines in this file, but one is printed anyway" }); @@ -1034,6 +1034,7 @@ test printLineFromFileAnyOs { output.clearRetainingCapacity(); } { + // print 1 & 3 of 3-line file const path = try join(allocator, &.{ test_dir_path, "three_lines.zig" }); defer allocator.free(path); try test_dir.dir.writeFile(.{ @@ -1054,6 +1055,7 @@ test printLineFromFileAnyOs { output.clearRetainingCapacity(); } { + // mem.page_size boundary crossing line const file = try test_dir.dir.createFile("line_overlaps_page_boundary.zig", .{}); defer file.close(); const path = try join(allocator, &.{ test_dir_path, "line_overlaps_page_boundary.zig" }); @@ -1070,6 +1072,7 @@ test printLineFromFileAnyOs { output.clearRetainingCapacity(); } { + // ends on mem.page_size boundary const file = try test_dir.dir.createFile("file_ends_on_page_boundary.zig", .{}); defer file.close(); const path = try join(allocator, &.{ test_dir_path, "file_ends_on_page_boundary.zig" }); @@ -1083,6 +1086,7 @@ test printLineFromFileAnyOs { output.clearRetainingCapacity(); } { + // multi-mem.page_size "line" const file = try test_dir.dir.createFile("very_long_first_line_spanning_multiple_pages.zig", .{}); defer file.close(); const path = try join(allocator, &.{ test_dir_path, "very_long_first_line_spanning_multiple_pages.zig" }); @@ -1108,6 +1112,7 @@ test printLineFromFileAnyOs { output.clearRetainingCapacity(); } { + // mem.page_size pages of newlines const file = try test_dir.dir.createFile("file_of_newlines.zig", .{}); defer file.close(); const path = try join(allocator, &.{ test_dir_path, "file_of_newlines.zig" }); diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index 4a20e58c2722..da9405f3e510 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -329,14 +329,8 @@ test "accessAbsolute" { var tmp = tmpDir(.{}); defer tmp.cleanup(); - 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, &.{ ".zig-cache", "tmp", tmp.sub_path[0..] }); - break :blk try fs.realpathAlloc(allocator, relative_path); - }; + const base_path = try tmp.dir.realpathAlloc(testing.allocator, "."); + defer testing.allocator.free(base_path); try fs.accessAbsolute(base_path, .{}); } @@ -347,32 +341,62 @@ test "openDirAbsolute" { var tmp = tmpDir(.{}); defer tmp.cleanup(); + const tmp_ino = (try tmp.dir.stat()).inode; + try tmp.dir.makeDir("subdir"); - var arena = ArenaAllocator.init(testing.allocator); - defer arena.deinit(); - const allocator = arena.allocator(); + const sub_path = try tmp.dir.realpathAlloc(testing.allocator, "subdir"); + defer testing.allocator.free(sub_path); - const base_path = blk: { - const relative_path = try fs.path.join(allocator, &.{ ".zig-cache", "tmp", tmp.sub_path[0..], "subdir" }); - break :blk try fs.realpathAlloc(allocator, relative_path); - }; + // Can open subdir + var tmp_sub = try fs.openDirAbsolute(sub_path, .{}); + defer tmp_sub.close(); + + const sub_ino = (try tmp_sub.stat()).inode; { - var dir = try fs.openDirAbsolute(base_path, .{}); + // Can open parent + const dir_path = try fs.path.join(testing.allocator, &.{ sub_path, ".." }); + defer testing.allocator.free(dir_path); + + var dir = try fs.openDirAbsolute(dir_path, .{}); defer dir.close(); + + const ino = (try dir.stat()).inode; + try testing.expectEqual(tmp_ino, ino); } - for ([_][]const u8{ ".", ".." }) |sub_path| { - const dir_path = try fs.path.join(allocator, &.{ base_path, sub_path }); + { + // Can open subdir + "." + const dir_path = try fs.path.join(testing.allocator, &.{ sub_path, "." }); + defer testing.allocator.free(dir_path); + var dir = try fs.openDirAbsolute(dir_path, .{}); defer dir.close(); + + const ino = (try dir.stat()).inode; + try testing.expectEqual(sub_ino, ino); + } + + { + // Can open subdir + "..", with extra "." + const dir_path = try fs.path.join(testing.allocator, &.{ sub_path, ".", "..", "." }); + defer testing.allocator.free(dir_path); + + var dir = try fs.openDirAbsolute(dir_path, .{}); + defer dir.close(); + + const ino = (try dir.stat()).inode; + try testing.expectEqual(tmp_ino, ino); } } test "openDir cwd parent '..'" { - if (native_os == .wasi) return error.SkipZigTest; - - var dir = try fs.cwd().openDir("..", .{}); + var dir = fs.cwd().openDir("..", .{}) catch |err| { + if (native_os == .wasi and err == error.AccessDenied) { + return; // This is okay. WASI disallows escaping from the fs sandbox + } + return err; + }; defer dir.close(); } @@ -388,7 +412,15 @@ test "openDir non-cwd parent '..'" { var subdir = try tmp.dir.makeOpenPath("subdir", .{}); defer subdir.close(); - var dir = try subdir.openDir("..", .{}); + var dir = subdir.openDir("..", .{}) catch |err| { + if (native_os == .wasi and err == error.AccessDenied) { + // This is odd (we're asking for the parent of a subdirectory, + // which should be safely inside the sandbox), but this is the + // current wasmtime behavior (with and without libc). + return error.SkipZigTest; + } + return err; + }; defer dir.close(); if (supports_absolute_paths) { @@ -421,10 +453,7 @@ test "readLinkAbsolute" { defer arena.deinit(); const allocator = arena.allocator(); - const base_path = blk: { - const relative_path = try fs.path.join(allocator, &.{ ".zig-cache", "tmp", tmp.sub_path[0..] }); - break :blk try fs.realpathAlloc(allocator, relative_path); - }; + const base_path = try tmp.dir.realpathAlloc(allocator, "."); { const target_path = try fs.path.join(allocator, &.{ base_path, "file.txt" }); @@ -760,7 +789,6 @@ test "directory operations on files" { test "file operations on directories" { // TODO: fix this test on FreeBSD. https://github.com/ziglang/zig/issues/1759 if (native_os == .freebsd) return error.SkipZigTest; - if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/20747 try testWithAllSupportedPathTypes(struct { fn impl(ctx: *TestContext) !void { @@ -771,18 +799,29 @@ test "file operations on directories" { try testing.expectError(error.IsDir, ctx.dir.createFile(test_dir_name, .{})); try testing.expectError(error.IsDir, ctx.dir.deleteFile(test_dir_name)); switch (native_os) { - // no error when reading a directory. - .dragonfly, .netbsd => {}, - // Currently, WASI will return error.Unexpected (via ENOTCAPABLE) when attempting fd_read on a directory handle. - // TODO: Re-enable on WASI once https://github.com/bytecodealliance/wasmtime/issues/1935 is resolved. - .wasi => {}, + .dragonfly, .netbsd => { + // no error when reading a directory. See https://github.com/ziglang/zig/issues/5732 + const buf = try ctx.dir.readFileAlloc(testing.allocator, test_dir_name, std.math.maxInt(usize)); + testing.allocator.free(buf); + }, + .wasi => { + // WASI return EBADF, which gets mapped to NotOpenForReading. + // See https://github.com/bytecodealliance/wasmtime/issues/1935 + try testing.expectError(error.NotOpenForReading, ctx.dir.readFileAlloc(testing.allocator, test_dir_name, std.math.maxInt(usize))); + }, else => { try testing.expectError(error.IsDir, ctx.dir.readFileAlloc(testing.allocator, test_dir_name, std.math.maxInt(usize))); }, } - // Note: The `.mode = .read_write` is necessary to ensure the error occurs on all platforms. - // TODO: Add a read-only test as well, see https://github.com/ziglang/zig/issues/5732 - try testing.expectError(error.IsDir, ctx.dir.openFile(test_dir_name, .{ .mode = .read_write })); + + if (native_os == .wasi and builtin.link_libc) { + // Older wasmtime errors here, newer ones do not, see https://github.com/ziglang/zig/issues/20747 + _ = ctx.dir.openFile(test_dir_name, .{ .mode = .read_write }) catch {}; + } else { + // Note: The `.mode = .read_write` is necessary to ensure the error occurs on all platforms. + // TODO: Add a read-only test as well, see https://github.com/ziglang/zig/issues/5732 + try testing.expectError(error.IsDir, ctx.dir.openFile(test_dir_name, .{ .mode = .read_write })); + } if (ctx.path_type == .absolute and supports_absolute_paths) { try testing.expectError(error.IsDir, fs.createFileAbsolute(test_dir_name, .{})); @@ -1005,10 +1044,7 @@ test "renameAbsolute" { defer arena.deinit(); const allocator = arena.allocator(); - const base_path = blk: { - const relative_path = try fs.path.join(allocator, &.{ ".zig-cache", "tmp", tmp_dir.sub_path[0..] }); - break :blk try fs.realpathAlloc(allocator, relative_path); - }; + const base_path = try tmp_dir.dir.realpathAlloc(allocator, "."); try testing.expectError(error.FileNotFound, fs.renameAbsolute( try fs.path.join(allocator, &.{ base_path, "missing_file_name" }), @@ -1398,7 +1434,6 @@ test "sendfile" { defer tmp.cleanup(); try tmp.dir.makePath("os_test_tmp"); - defer tmp.dir.deleteTree("os_test_tmp") catch {}; var dir = try tmp.dir.openDir("os_test_tmp", .{}); defer dir.close(); @@ -1463,7 +1498,6 @@ test "copyRangeAll" { defer tmp.cleanup(); try tmp.dir.makePath("os_test_tmp"); - defer tmp.dir.deleteTree("os_test_tmp") catch {}; var dir = try tmp.dir.openDir("os_test_tmp", .{}); defer dir.close(); @@ -1782,10 +1816,7 @@ test "'.' and '..' in absolute functions" { defer arena.deinit(); const allocator = arena.allocator(); - const base_path = blk: { - const relative_path = try fs.path.join(allocator, &.{ ".zig-cache", "tmp", tmp.sub_path[0..] }); - break :blk try fs.realpathAlloc(allocator, relative_path); - }; + const base_path = try tmp.dir.realpathAlloc(allocator, "."); const subdir_path = try fs.path.join(allocator, &.{ base_path, "./subdir" }); try fs.makeDirAbsolute(subdir_path); diff --git a/lib/std/io/test.zig b/lib/std/io/test.zig index 5ac4bb65d2b5..0c2e480e47be 100644 --- a/lib/std/io/test.zig +++ b/lib/std/io/test.zig @@ -108,10 +108,7 @@ test "File seek ops" { const tmp_file_name = "temp_test_file.txt"; var file = try tmp.dir.createFile(tmp_file_name, .{}); - defer { - file.close(); - tmp.dir.deleteFile(tmp_file_name) catch {}; - } + defer file.close(); try file.writeAll(&([_]u8{0x55} ** 8192)); @@ -135,10 +132,7 @@ test "setEndPos" { const tmp_file_name = "temp_test_file.txt"; var file = try tmp.dir.createFile(tmp_file_name, .{}); - defer { - file.close(); - tmp.dir.deleteFile(tmp_file_name) catch {}; - } + defer file.close(); // Verify that the file size changes and the file offset is not moved try std.testing.expect((try file.getEndPos()) == 0); @@ -161,10 +155,8 @@ test "updateTimes" { const tmp_file_name = "just_a_temporary_file.txt"; var file = try tmp.dir.createFile(tmp_file_name, .{ .read = true }); - defer { - file.close(); - tmp.dir.deleteFile(tmp_file_name) catch {}; - } + defer file.close(); + const stat_old = try file.stat(); // Set atime and mtime to 5s before try file.updateTimes( diff --git a/lib/std/posix/test.zig b/lib/std/posix/test.zig index bf73cea84e67..11282ba4dfcb 100644 --- a/lib/std/posix/test.zig +++ b/lib/std/posix/test.zig @@ -8,7 +8,6 @@ const io = std.io; const fs = std.fs; const mem = std.mem; const elf = std.elf; -const File = std.fs.File; const Thread = std.Thread; const linux = std.os.linux; @@ -19,8 +18,6 @@ const AtomicRmwOp = std.builtin.AtomicRmwOp; const AtomicOrder = std.builtin.AtomicOrder; const native_os = builtin.target.os.tag; const tmpDir = std.testing.tmpDir; -const Dir = std.fs.Dir; -const ArenaAllocator = std.heap.ArenaAllocator; // Filter to skip tests on platforms that don't support chdir. const supports_chdir = (native_os != .wasi); @@ -28,13 +25,6 @@ const supports_chdir = (native_os != .wasi); // Filter to skip tests on platforms that don't support absolute paths const supports_absolute_paths = (native_os != .wasi); -// Tests that create/delete files in the current working directory are not safe to run -// in CI. So this is off by default. But handy when testing. Do not submit true. -// Test should probably be fixed to use unique names to avoid races. -// -// https://github.com/ziglang/zig/issues/14968 -const enable_sketchy_cwd_tests = true; - test "check WASI CWD" { if (native_os == .wasi) { if (std.options.wasiCwd() != 3) { @@ -57,9 +47,25 @@ test "WTF-8 to WTF-16 conversion buffer overflows" { try expectError(error.NameTooLong, posix.chdirZ(input_wtf8)); } -test "chdir smoke test" { +// For tests that create entries in the current working directory, use +// this to ensure a unique name. Otherwise parallel test invocations +// (e.g., running on multiple architectures on CI) might collide in the +// working directory. Tests should prefer tmpDir() over this. +fn unique_fname(allocator: std.mem.Allocator, basename: []const u8) ![]u8 { + const byte_count = 12; + const char_count = comptime std.fs.base64_encoder.calcSize(byte_count); + + var random_bytes: [byte_count]u8 = undefined; + std.crypto.random.bytes(&random_bytes); + + var prefix: [char_count]u8 = undefined; + _ = std.fs.base64_encoder.encode(&prefix, &random_bytes); + + return try std.fmt.allocPrint(allocator, "{s}-{s}", .{ prefix, basename }); +} + +test "chdir absolute parent" { if (!supports_chdir) return error.SkipZigTest; - if (!enable_sketchy_cwd_tests) return error.SkipZigTest; // Get current working directory path var old_cwd_buf: [fs.max_path_bytes]u8 = undefined; @@ -74,47 +80,45 @@ test "chdir smoke test" { } // Next, change current working directory to one level above - if (native_os != .wasi) { // WASI does not support navigating outside of Preopens - const parent = fs.path.dirname(old_cwd) orelse unreachable; // old_cwd should be absolute - try posix.chdir(parent); + const parent = fs.path.dirname(old_cwd) orelse unreachable; // old_cwd should be absolute + try posix.chdir(parent); + defer posix.chdir(old_cwd) catch unreachable; - // Restore cwd because process may have other tests that do not tolerate chdir. - defer posix.chdir(old_cwd) catch unreachable; + var new_cwd_buf: [fs.max_path_bytes]u8 = undefined; + const new_cwd = try posix.getcwd(new_cwd_buf[0..]); + try expect(mem.eql(u8, parent, new_cwd)); +} - var new_cwd_buf: [fs.max_path_bytes]u8 = undefined; - const new_cwd = try posix.getcwd(new_cwd_buf[0..]); - try expect(mem.eql(u8, parent, new_cwd)); - } +test "chdir relative subdir" { + if (!supports_chdir) return error.SkipZigTest; - // Next, change current working directory to a temp directory one level below - { - // Create a tmp directory - var tmp_dir_buf: [fs.max_path_bytes]u8 = undefined; - const tmp_dir_path = path: { - var allocator = std.heap.FixedBufferAllocator.init(&tmp_dir_buf); - break :path try fs.path.resolve(allocator.allocator(), &[_][]const u8{ old_cwd, "zig-test-tmp" }); - }; - var tmp_dir = try fs.cwd().makeOpenPath("zig-test-tmp", .{}); + // Get current working directory path + var old_cwd_buf: [fs.max_path_bytes]u8 = undefined; + const old_cwd = try posix.getcwd(old_cwd_buf[0..]); - // Change current working directory to tmp directory - try posix.chdir("zig-test-tmp"); + const dir_name = try unique_fname(a, "chdir-target"); + defer a.free(dir_name); - var new_cwd_buf: [fs.max_path_bytes]u8 = undefined; - const new_cwd = try posix.getcwd(new_cwd_buf[0..]); + // Create a new (sub)directory + const expected_tmp_dir_path = try fs.path.resolve(a, &.{ old_cwd, dir_name }); + defer a.free(expected_tmp_dir_path); - // On Windows, fs.path.resolve returns an uppercase drive letter, but the drive letter returned by getcwd may be lowercase - var resolved_cwd_buf: [fs.max_path_bytes]u8 = undefined; - const resolved_cwd = path: { - var allocator = std.heap.FixedBufferAllocator.init(&resolved_cwd_buf); - break :path try fs.path.resolve(allocator.allocator(), &[_][]const u8{new_cwd}); - }; - try expect(mem.eql(u8, tmp_dir_path, resolved_cwd)); + var tmp_dir = try fs.cwd().makeOpenPath(dir_name, .{}); + defer tmp_dir.close(); + defer fs.cwd().deleteDir(dir_name) catch {}; - // Restore cwd because process may have other tests that do not tolerate chdir. - tmp_dir.close(); - posix.chdir(old_cwd) catch unreachable; - try fs.cwd().deleteDir("zig-test-tmp"); - } + // Change current working directory to new directory + try posix.chdir(dir_name); + defer posix.chdir(old_cwd) catch unreachable; + + var new_cwd_buf: [fs.max_path_bytes]u8 = undefined; + const new_cwd = try posix.getcwd(new_cwd_buf[0..]); + + // On Windows, fs.path.resolve returns an uppercase drive letter, but the drive letter returned by getcwd may be lowercase + const resolved_cwd = try fs.path.resolve(a, &.{new_cwd}); + defer a.free(resolved_cwd); + + try expect(mem.eql(u8, expected_tmp_dir_path, resolved_cwd)); } const default_mode = switch (posix.mode_t) { @@ -131,50 +135,62 @@ test "open smoke test" { 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 = try tmp.dir.realpathAlloc(a, "."); + defer a.free(base_path); - 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: posix.fd_t = undefined; const mode: posix.mode_t = default_mode; - // Create some file using `open`. - file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_file" }); - fd = try posix.open(file_path, .{ .ACCMODE = .RDWR, .CREAT = true, .EXCL = true }, mode); - posix.close(fd); + { + // Create some file using `open`. + const file_path = try fs.path.join(a, &.{ base_path, "some_file" }); + defer a.free(file_path); + const fd = try posix.open(file_path, .{ .ACCMODE = .RDWR, .CREAT = true, .EXCL = true }, mode); + posix.close(fd); + } - // Try this again with the same flags. This op should fail with error.PathAlreadyExists. - file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_file" }); - try expectError(error.PathAlreadyExists, posix.open(file_path, .{ .ACCMODE = .RDWR, .CREAT = true, .EXCL = true }, mode)); + { + // Try this again with the same flags. This op should fail with error.PathAlreadyExists. + const file_path = try fs.path.join(a, &.{ base_path, "some_file" }); + defer a.free(file_path); + try expectError(error.PathAlreadyExists, posix.open(file_path, .{ .ACCMODE = .RDWR, .CREAT = true, .EXCL = true }, mode)); + } - // Try opening without `EXCL` flag. - file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_file" }); - fd = try posix.open(file_path, .{ .ACCMODE = .RDWR, .CREAT = true }, mode); - posix.close(fd); + { + // Try opening without `EXCL` flag. + const file_path = try fs.path.join(a, &.{ base_path, "some_file" }); + defer a.free(file_path); + const fd = try posix.open(file_path, .{ .ACCMODE = .RDWR, .CREAT = true }, mode); + posix.close(fd); + } - // Try opening as a directory which should fail. - file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_file" }); - try expectError(error.NotDir, posix.open(file_path, .{ .ACCMODE = .RDWR, .DIRECTORY = true }, mode)); + { + // Try opening as a directory which should fail. + const file_path = try fs.path.join(a, &.{ base_path, "some_file" }); + defer a.free(file_path); + try expectError(error.NotDir, posix.open(file_path, .{ .ACCMODE = .RDWR, .DIRECTORY = true }, mode)); + } - // Create some directory - file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_dir" }); - try posix.mkdir(file_path, mode); + { + // Create some directory + const file_path = try fs.path.join(a, &.{ base_path, "some_dir" }); + defer a.free(file_path); + try posix.mkdir(file_path, mode); + } - // Open dir using `open` - file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_dir" }); - fd = try posix.open(file_path, .{ .ACCMODE = .RDONLY, .DIRECTORY = true }, mode); - posix.close(fd); + { + // Open dir using `open` + const file_path = try fs.path.join(a, &.{ base_path, "some_dir" }); + defer a.free(file_path); + const fd = try posix.open(file_path, .{ .ACCMODE = .RDONLY, .DIRECTORY = true }, mode); + posix.close(fd); + } - // Try opening as file which should fail. - file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_dir" }); - try expectError(error.IsDir, posix.open(file_path, .{ .ACCMODE = .RDWR }, mode)); + { + // Try opening as file which should fail. + const file_path = try fs.path.join(a, &.{ base_path, "some_dir" }); + defer a.free(file_path); + try expectError(error.IsDir, posix.open(file_path, .{ .ACCMODE = .RDWR }, mode)); + } } test "openat smoke test" { @@ -227,46 +243,45 @@ test "openat smoke test" { posix.close(fd); // Try opening as file which should fail. + // Note on wasmtime v23.0.2 (at least) this returns a file descriptor (not the IsDir error). try expectError(error.IsDir, posix.openat(tmp.dir.fd, "some_dir", CommonOpenFlags.lower(.{ .ACCMODE = .RDWR, }), mode)); } test "symlink with relative paths" { - if (!enable_sketchy_cwd_tests) return error.SkipZigTest; + const targetName = try unique_fname(a, "symlink-target"); + defer a.free(targetName); + + const symlinkName = try unique_fname(a, "symlinked"); + defer a.free(symlinkName); const cwd = fs.cwd(); - cwd.deleteFile("file.txt") catch {}; - cwd.deleteFile("symlinked") catch {}; - // First, try relative paths in cwd - try cwd.writeFile(.{ .sub_path = "file.txt", .data = "nonsense" }); + defer cwd.deleteFile(targetName) catch {}; + defer cwd.deleteFile(symlinkName) catch {}; + + // Create the target file + try cwd.writeFile(.{ .sub_path = targetName, .data = "nonsense" }); if (native_os == .windows) { - std.os.windows.CreateSymbolicLink( - cwd.fd, - &[_]u16{ 's', 'y', 'm', 'l', 'i', 'n', 'k', 'e', 'd' }, - &[_:0]u16{ 'f', 'i', 'l', 'e', '.', 't', 'x', 't' }, - false, - ) catch |err| switch (err) { + const wtargetName = try std.unicode.wtf8ToWtf16LeAllocZ(a, targetName); + const wsymlinkName = try std.unicode.wtf8ToWtf16LeAllocZ(a, symlinkName); + defer a.free(wtargetName); + defer a.free(wsymlinkName); + + std.os.windows.CreateSymbolicLink(cwd.fd, wsymlinkName, wtargetName, false) catch |err| switch (err) { // Symlink requires admin privileges on windows, so this test can legitimately fail. - error.AccessDenied => { - try cwd.deleteFile("file.txt"); - try cwd.deleteFile("symlinked"); - return error.SkipZigTest; - }, + error.AccessDenied => return error.SkipZigTest, else => return err, }; } else { - try posix.symlink("file.txt", "symlinked"); + try posix.symlink(targetName, symlinkName); } var buffer: [fs.max_path_bytes]u8 = undefined; - const given = try posix.readlink("symlinked", buffer[0..]); - try expect(mem.eql(u8, "file.txt", given)); - - try cwd.deleteFile("file.txt"); - try cwd.deleteFile("symlinked"); + const given = try posix.readlink(symlinkName, buffer[0..]); + try expect(mem.eql(u8, targetName, given)); } test "readlink on Windows" { @@ -288,20 +303,25 @@ test "link with relative paths" { .wasi, .linux, .solaris, .illumos => {}, else => return error.SkipZigTest, } - if (!enable_sketchy_cwd_tests) return error.SkipZigTest; + + const targetName = try unique_fname(a, "link-target"); + defer a.free(targetName); + + const linkName = try unique_fname(a, "newlink"); + defer a.free(linkName); var cwd = fs.cwd(); - cwd.deleteFile("example.txt") catch {}; - cwd.deleteFile("new.txt") catch {}; + defer cwd.deleteFile(targetName) catch {}; + defer cwd.deleteFile(linkName) catch {}; - try cwd.writeFile(.{ .sub_path = "example.txt", .data = "example" }); - try posix.link("example.txt", "new.txt"); + try cwd.writeFile(.{ .sub_path = targetName, .data = "example" }); + try posix.link(targetName, linkName); - const efd = try cwd.openFile("example.txt", .{}); + const efd = try cwd.openFile(targetName, .{}); defer efd.close(); - const nfd = try cwd.openFile("new.txt", .{}); + const nfd = try cwd.openFile(linkName, .{}); defer nfd.close(); { @@ -312,14 +332,12 @@ test "link with relative paths" { try testing.expectEqual(@as(@TypeOf(nstat.nlink), 2), nstat.nlink); } - try posix.unlink("new.txt"); + try posix.unlink(linkName); { const estat = try posix.fstat(efd.handle); try testing.expectEqual(@as(@TypeOf(estat.nlink), 1), estat.nlink); } - - try cwd.deleteFile("example.txt"); } test "linkat with different directories" { @@ -327,21 +345,25 @@ test "linkat with different directories" { .wasi, .linux, .solaris, .illumos => {}, else => return error.SkipZigTest, } - if (!enable_sketchy_cwd_tests) return error.SkipZigTest; + + const targetName = try unique_fname(a, "link-target"); + defer a.free(targetName); + + const linkName = try unique_fname(a, "newlink"); + defer a.free(linkName); var cwd = fs.cwd(); var tmp = tmpDir(.{}); - cwd.deleteFile("example.txt") catch {}; - tmp.dir.deleteFile("new.txt") catch {}; + defer cwd.deleteFile(targetName) catch {}; - try cwd.writeFile(.{ .sub_path = "example.txt", .data = "example" }); - try posix.linkat(cwd.fd, "example.txt", tmp.dir.fd, "new.txt", 0); + try cwd.writeFile(.{ .sub_path = targetName, .data = "example" }); + try posix.linkat(cwd.fd, targetName, tmp.dir.fd, linkName, 0); - const efd = try cwd.openFile("example.txt", .{}); + const efd = try cwd.openFile(targetName, .{}); defer efd.close(); - const nfd = try tmp.dir.openFile("new.txt", .{}); + const nfd = try tmp.dir.openFile(linkName, .{}); { defer nfd.close(); @@ -352,14 +374,12 @@ test "linkat with different directories" { try testing.expectEqual(@as(@TypeOf(nstat.nlink), 2), nstat.nlink); } - try posix.unlinkat(tmp.dir.fd, "new.txt", 0); + try posix.unlinkat(tmp.dir.fd, linkName, 0); { const estat = try posix.fstat(efd.handle); try testing.expectEqual(@as(@TypeOf(estat.nlink), 1), estat.nlink); } - - try cwd.deleteFile("example.txt"); } test "fstatat" { @@ -698,8 +718,6 @@ test "mmap" { try testing.expectEqual(i, try stream.readInt(u32, .little)); } } - - try tmp.dir.deleteFile(test_out_file); } test "getenv" { @@ -725,10 +743,7 @@ test "fcntl" { const test_out_file = "os_tmp_test"; const file = try tmp.dir.createFile(test_out_file, .{}); - defer { - file.close(); - tmp.dir.deleteFile(test_out_file) catch {}; - } + defer file.close(); // Note: The test assumes createFile opens the file with CLOEXEC { @@ -764,10 +779,7 @@ test "sync" { const test_out_file = "os_tmp_test"; const file = try tmp.dir.createFile(test_out_file, .{}); - defer { - file.close(); - tmp.dir.deleteFile(test_out_file) catch {}; - } + defer file.close(); posix.sync(); try posix.syncfs(file.handle); @@ -784,10 +796,7 @@ test "fsync" { const test_out_file = "os_tmp_test"; const file = try tmp.dir.createFile(test_out_file, .{}); - defer { - file.close(); - tmp.dir.deleteFile(test_out_file) catch {}; - } + defer file.close(); try posix.fsync(file.handle); try posix.fdatasync(file.handle); @@ -1034,54 +1043,65 @@ test "rename smoke test" { 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); - }; + const base_path = try tmp.dir.realpathAlloc(a, "."); + defer a.free(base_path); - var file_path: []u8 = undefined; - var fd: posix.fd_t = undefined; const mode: posix.mode_t = default_mode; - // Create some file using `open`. - file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_file" }); - fd = try posix.open(file_path, .{ .ACCMODE = .RDWR, .CREAT = true, .EXCL = true }, mode); - posix.close(fd); - - // Rename the file - var new_file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_other_file" }); - try posix.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 posix.open(file_path, .{ .ACCMODE = .RDWR }, mode); - posix.close(fd); + { + // Create some file using `open`. + const file_path = try fs.path.join(a, &.{ base_path, "some_file" }); + defer a.free(file_path); + const fd = try posix.open(file_path, .{ .ACCMODE = .RDWR, .CREAT = true, .EXCL = true }, mode); + posix.close(fd); + + // Rename the file + const new_file_path = try fs.path.join(a, &.{ base_path, "some_other_file" }); + defer a.free(new_file_path); + try posix.rename(file_path, new_file_path); + } - // 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, posix.open(file_path, .{ .ACCMODE = .RDWR }, mode)); + { + // Try opening renamed file + const file_path = try fs.path.join(a, &.{ base_path, "some_other_file" }); + defer a.free(file_path); + const fd = try posix.open(file_path, .{ .ACCMODE = .RDWR }, mode); + posix.close(fd); + } - // Create some directory - file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_dir" }); - try posix.mkdir(file_path, mode); + { + // Try opening original file - should fail with error.FileNotFound + const file_path = try fs.path.join(a, &.{ base_path, "some_file" }); + defer a.free(file_path); + try expectError(error.FileNotFound, posix.open(file_path, .{ .ACCMODE = .RDWR }, mode)); + } - // Rename the directory - new_file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_other_dir" }); - try posix.rename(file_path, new_file_path); + { + // Create some directory + const file_path = try fs.path.join(a, &.{ base_path, "some_dir" }); + defer a.free(file_path); + try posix.mkdir(file_path, mode); + + // Rename the directory + const new_file_path = try fs.path.join(a, &.{ base_path, "some_other_dir" }); + defer a.free(new_file_path); + try posix.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 posix.open(file_path, .{ .ACCMODE = .RDONLY, .DIRECTORY = true }, mode); - posix.close(fd); + { + // Try opening renamed directory + const file_path = try fs.path.join(a, &.{ base_path, "some_other_dir" }); + defer a.free(file_path); + const fd = try posix.open(file_path, .{ .ACCMODE = .RDONLY, .DIRECTORY = true }, mode); + posix.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, posix.open(file_path, .{ .ACCMODE = .RDONLY, .DIRECTORY = true }, mode)); + { + // Try opening original directory - should fail with error.FileNotFound + const file_path = try fs.path.join(a, &.{ base_path, "some_dir" }); + defer a.free(file_path); + try expectError(error.FileNotFound, posix.open(file_path, .{ .ACCMODE = .RDONLY, .DIRECTORY = true }, mode)); + } } test "access smoke test" { @@ -1091,44 +1111,51 @@ test "access smoke test" { 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 = try tmp.dir.realpathAlloc(a, "."); + defer a.free(base_path); - 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: posix.fd_t = undefined; const mode: posix.mode_t = default_mode; - // Create some file using `open`. - file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_file" }); - fd = try posix.open(file_path, .{ .ACCMODE = .RDWR, .CREAT = true, .EXCL = true }, mode); - posix.close(fd); + { + // Create some file using `open`. + const file_path = try fs.path.join(a, &.{ base_path, "some_file" }); + defer a.free(file_path); + const fd = try posix.open(file_path, .{ .ACCMODE = .RDWR, .CREAT = true, .EXCL = true }, mode); + posix.close(fd); + } - // Try to access() the file - file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_file" }); - if (native_os == .windows) { - try posix.access(file_path, posix.F_OK); - } else { - try posix.access(file_path, posix.F_OK | posix.W_OK | posix.R_OK); + { + // Try to access() the file + const file_path = try fs.path.join(a, &.{ base_path, "some_file" }); + defer a.free(file_path); + if (native_os == .windows) { + try posix.access(file_path, posix.F_OK); + } else { + try posix.access(file_path, posix.F_OK | posix.W_OK | posix.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, posix.access(file_path, posix.F_OK)); + { + // Try to access() a non-existent file - should fail with error.FileNotFound + const file_path = try fs.path.join(a, &.{ base_path, "some_other_file" }); + defer a.free(file_path); + try expectError(error.FileNotFound, posix.access(file_path, posix.F_OK)); + } - // Create some directory - file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_dir" }); - try posix.mkdir(file_path, mode); + { + // Create some directory + const file_path = try fs.path.join(a, &.{ base_path, "some_dir" }); + defer a.free(file_path); + try posix.mkdir(file_path, mode); + } + + { + // Try to access() the directory + const file_path = try fs.path.join(a, &.{ base_path, "some_dir" }); + defer a.free(file_path); - // Try to access() the directory - file_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_dir" }); - try posix.access(file_path, posix.F_OK); + try posix.access(file_path, posix.F_OK); + } } test "timerfd" { @@ -1160,103 +1187,59 @@ test "isatty" { } test "read with empty buffer" { - if (!supports_absolute_paths) return error.SkipZigTest; // Or fix test to use relative paths - var tmp = tmpDir(.{}); defer tmp.cleanup(); - var arena = ArenaAllocator.init(testing.allocator); - defer arena.deinit(); - const allocator = arena.allocator(); - - // Get base abs path - 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); - }; - - const file_path: []u8 = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_file" }); - var file = try fs.cwd().createFile(file_path, .{ .read = true }); + var file = try tmp.dir.createFile("read_empty", .{ .read = true }); defer file.close(); - const bytes = try allocator.alloc(u8, 0); + const bytes = try a.alloc(u8, 0); + defer a.free(bytes); - _ = try posix.read(file.handle, bytes); + const rc = try posix.read(file.handle, bytes); + try expectEqual(rc, 0); } test "pread with empty buffer" { - if (!supports_absolute_paths) return error.SkipZigTest; // Or fix test to use relative paths - var tmp = tmpDir(.{}); defer tmp.cleanup(); - var arena = ArenaAllocator.init(testing.allocator); - defer arena.deinit(); - const allocator = arena.allocator(); - - // Get base abs path - 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); - }; - - const file_path: []u8 = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_file" }); - var file = try fs.cwd().createFile(file_path, .{ .read = true }); + var file = try tmp.dir.createFile("pread_empty", .{ .read = true }); defer file.close(); - const bytes = try allocator.alloc(u8, 0); + const bytes = try a.alloc(u8, 0); + defer a.free(bytes); - _ = try posix.pread(file.handle, bytes, 0); + const rc = try posix.pread(file.handle, bytes, 0); + try expectEqual(rc, 0); } test "write with empty buffer" { - if (!supports_absolute_paths) return error.SkipZigTest; // Or fix test to use relative paths - var tmp = tmpDir(.{}); defer tmp.cleanup(); - var arena = ArenaAllocator.init(testing.allocator); - defer arena.deinit(); - const allocator = arena.allocator(); - - // Get base abs path - 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); - }; - - const file_path: []u8 = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_file" }); - var file = try fs.cwd().createFile(file_path, .{}); + var file = try tmp.dir.createFile("write_empty", .{}); defer file.close(); - const bytes = try allocator.alloc(u8, 0); + const bytes = try a.alloc(u8, 0); + defer a.free(bytes); - _ = try posix.write(file.handle, bytes); + const rc = try posix.write(file.handle, bytes); + try expectEqual(rc, 0); } test "pwrite with empty buffer" { - if (!supports_absolute_paths) return error.SkipZigTest; // Or fix test to use relative paths - var tmp = tmpDir(.{}); defer tmp.cleanup(); - var arena = ArenaAllocator.init(testing.allocator); - defer arena.deinit(); - const allocator = arena.allocator(); - - // Get base abs path - 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); - }; - - const file_path: []u8 = try fs.path.join(allocator, &[_][]const u8{ base_path, "some_file" }); - var file = try fs.cwd().createFile(file_path, .{}); + var file = try tmp.dir.createFile("pwrite_empty", .{}); defer file.close(); - const bytes = try allocator.alloc(u8, 0); + const bytes = try a.alloc(u8, 0); + defer a.free(bytes); - _ = try posix.pwrite(file.handle, bytes, 0); + const rc = try posix.pwrite(file.handle, bytes, 0); + try expectEqual(rc, 0); } fn expectMode(dir: posix.fd_t, file: []const u8, mode: posix.mode_t) !void { From 46e72c16413fa75581d9d9bf30726f8441c01331 Mon Sep 17 00:00:00 2001 From: Pat Tullmann Date: Mon, 5 Aug 2024 23:51:20 -0700 Subject: [PATCH 4/5] wasi: fstat{at,} support Add a test for fstat{,at} with symlinks (for everyone, not just wasi) Fix the wasi-libc Stat.mode field, and add the expected accessors to the S struct. Fixes #20890 --- lib/std/c.zig | 30 +++++++++++++++++- lib/std/posix/test.zig | 71 ++++++++++++++++++++++++++++++++++++++---- 2 files changed, 94 insertions(+), 7 deletions(-) diff --git a/lib/std/c.zig b/lib/std/c.zig index 17cfb7fd77aa..2829069dce84 100644 --- a/lib/std/c.zig +++ b/lib/std/c.zig @@ -1712,6 +1712,34 @@ pub const S = switch (native_os) { pub const IFREG = 0x8000; pub const IFSOCK = 0xc000; pub const IFMT = IFBLK | IFCHR | IFDIR | IFIFO | IFLNK | IFREG | IFSOCK; + + pub fn ISBLK(m: u32) bool { + return m & IFMT == IFBLK; + } + + pub fn ISCHR(m: u32) bool { + return m & IFMT == IFCHR; + } + + pub fn ISDIR(m: u32) bool { + return m & IFMT == IFDIR; + } + + pub fn ISFIFO(m: u32) bool { + return m & IFMT == IFIFO; + } + + pub fn ISLNK(m: u32) bool { + return m & IFMT == IFLNK; + } + + pub fn ISREG(m: u32) bool { + return m & IFMT == IFREG; + } + + pub fn ISSOCK(m: u32) bool { + return m & IFMT == IFSOCK; + } }, .macos, .ios, .tvos, .watchos, .visionos => struct { pub const IFMT = 0o170000; @@ -6489,7 +6517,7 @@ pub const Stat = switch (native_os) { dev: dev_t, ino: ino_t, nlink: nlink_t, - mode: mode_t, + mode: c_uint, // only the file-type bits (S.IFMT), no permission bits uid: uid_t, gid: gid_t, __pad0: c_uint = 0, diff --git a/lib/std/posix/test.zig b/lib/std/posix/test.zig index 11282ba4dfcb..7c0203b9c7aa 100644 --- a/lib/std/posix/test.zig +++ b/lib/std/posix/test.zig @@ -25,6 +25,9 @@ const supports_chdir = (native_os != .wasi); // Filter to skip tests on platforms that don't support absolute paths const supports_absolute_paths = (native_os != .wasi); +// Filter to skip tests on platforms that don't (yet) suppport fstat/fstatat +const supports_fstat = (native_os != .windows); + test "check WASI CWD" { if (native_os == .wasi) { if (std.options.wasiCwd() != 3) { @@ -299,6 +302,8 @@ fn testReadlink(target_path: []const u8, symlink_path: []const u8) !void { } test "link with relative paths" { + if (!supports_fstat) return error.SkipZigTest; + switch (native_os) { .wasi, .linux, .solaris, .illumos => {}, else => return error.SkipZigTest, @@ -341,6 +346,8 @@ test "link with relative paths" { } test "linkat with different directories" { + if (!supports_fstat) return error.SkipZigTest; + switch (native_os) { .wasi, .linux, .solaris, .illumos => {}, else => return error.SkipZigTest, @@ -382,9 +389,8 @@ test "linkat with different directories" { } } -test "fstatat" { - // enable when `fstat` and `fstatat` are implemented on Windows - if (native_os == .windows) return error.SkipZigTest; +test "fstat(at) file" { + if (!supports_fstat) return error.SkipZigTest; var tmp = tmpDir(.{}); defer tmp.cleanup(); @@ -396,14 +402,66 @@ test "fstatat" { // fetch file's info on the opened fd directly const file = try tmp.dir.openFile("file.txt", .{}); const stat = try posix.fstat(file.handle); - defer file.close(); + file.close(); // now repeat but using `fstatat` instead - const flags = posix.AT.SYMLINK_NOFOLLOW; - const statat = try posix.fstatat(tmp.dir.fd, "file.txt", flags); + const statat = try posix.fstatat(tmp.dir.fd, "file.txt", 0); try expectEqual(stat, statat); } +test "fstat(at) symlink" { + if (!supports_fstat) return error.SkipZigTest; + + var tmp = testing.tmpDir(.{}); + defer tmp.cleanup(); + + try tmp.dir.writeFile(.{ .sub_path = "target.txt", .data = "irrelevant" }); + + const target = try tmp.dir.openFile("target.txt", .{}); + const statTarget = try posix.fstat(target.handle); + target.close(); + + // Set up symlink + try tmp.dir.symLink("target.txt", "sym.lnk", .{}); + + // Openat (+follow) + fstat() the symlink + const linkFollowFd = try posix.openat(tmp.dir.fd, "sym.lnk", .{}, default_mode); + defer posix.close(linkFollowFd); + const statLinkFollow = try posix.fstat(linkFollowFd); + + // fstatat (with and without follow) the symlink + const statatLinkFollow = try posix.fstatat(tmp.dir.fd, "sym.lnk", 0); + const statatLinkNoFollow = try posix.fstatat(tmp.dir.fd, "sym.lnk", posix.AT.SYMLINK_NOFOLLOW); + + if (@hasField(posix.O, "PATH")) { + // Can only openat() a symlink with NOFOLLOW if O.PATH is + // supported. Result should exactly match result from the + // no-follow fstatat() call. + + const linkNoFollowFd = try posix.openat(tmp.dir.fd, "sym.lnk", .{ .NOFOLLOW = true, .PATH = true }, default_mode); + defer posix.close(linkNoFollowFd); + + const statLinkNoFollow = try posix.fstat(linkNoFollowFd); + try testing.expectEqual(statLinkNoFollow, statatLinkNoFollow); + } + + // Link following should have followed the link + try testing.expectEqual(statTarget, statLinkFollow); + try testing.expectEqual(statTarget, statatLinkFollow); + + // symlink and target are different: + try testing.expect(statTarget.ino != statatLinkNoFollow.ino); + try testing.expect(statTarget.mode != statatLinkNoFollow.mode); + + // target is a regular, non-link file: + try testing.expect(posix.S.ISREG(statTarget.mode)); + try testing.expect(!posix.S.ISLNK(statTarget.mode)); + + // symlink is a non-regular, link file: + try testing.expect(!posix.S.ISREG(statatLinkNoFollow.mode)); + try testing.expect(posix.S.ISLNK(statatLinkNoFollow.mode)); +} + test "readlinkat" { var tmp = tmpDir(.{}); defer tmp.cleanup(); @@ -1249,6 +1307,7 @@ fn expectMode(dir: posix.fd_t, file: []const u8, mode: posix.mode_t) !void { test "fchmodat smoke test" { if (!std.fs.has_executable_bit) return error.SkipZigTest; + if (!supports_fstat) return error.SkipZigTest; // for expectMode() var tmp = tmpDir(.{}); defer tmp.cleanup(); From 3be1ed05101f73d65971c3dacae677a1edf3b983 Mon Sep 17 00:00:00 2001 From: Pat Tullmann Date: Thu, 15 Aug 2024 23:28:09 -0700 Subject: [PATCH 5/5] wasi c.zig: Update comments around wasi-libc constants Point to the correct header files where the wasi-libc constants are defined that the Zig lib/std/c.zig is replicating. Zig's current copy of wasi-libc has a bug in the file-mode constants. Both S_IFIFO and S_IFSOCK are 0xc000, but S_IFIFO should be 0x1000. See https://github.com/WebAssembly/wasi-libc/pull/463. --- lib/std/c.zig | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/lib/std/c.zig b/lib/std/c.zig index 2829069dce84..6a0a57ae04e4 100644 --- a/lib/std/c.zig +++ b/lib/std/c.zig @@ -672,6 +672,7 @@ pub const F = switch (native_os) { .linux => linux.F, .emscripten => emscripten.F, .wasi => struct { + // Match F_* constants from lib/libc/include/wasm-wasi-musl/__header_fcntl.h pub const GETFD = 1; pub const SETFD = 2; pub const GETFL = 3; @@ -1703,14 +1704,20 @@ pub const S = switch (native_os) { .linux => linux.S, .emscripten => emscripten.S, .wasi => struct { - // Match wasi-libc's libc-bottom-half/headers/public/__mode_t.h + // Match S_* constants from lib/libc/include/wasm-wasi-musl/__mode_t.h + // + // Note, a bug in wasi-libc means both IFIFO and IFSOCK have the same value (0xc000). + // IFIFO should be 0x1000 (see https://github.com/WebAssembly/wasi-libc/pull/463), and + // 0x1000 is used by the wasi-libc bottom-half. So we use 0x1000 here. But the actual bit + // values we get back from a wasi-libc may get masked with the wrong values, or may get + // mistranslated. So the FIFO and Socket file-type bits are not trustworthy. pub const IFBLK = 0x6000; pub const IFCHR = 0x2000; pub const IFDIR = 0x4000; - pub const IFIFO = 0x1000; + pub const IFIFO = 0x1000; // buggy pub const IFLNK = 0xa000; pub const IFREG = 0x8000; - pub const IFSOCK = 0xc000; + pub const IFSOCK = 0xc000; // buggy pub const IFMT = IFBLK | IFCHR | IFDIR | IFIFO | IFLNK | IFREG | IFSOCK; pub fn ISBLK(m: u32) bool { @@ -6513,7 +6520,7 @@ pub const Stat = switch (native_os) { }, .emscripten => emscripten.Stat, .wasi => extern struct { - // Matches wasi-libc's libc-bottom-half/headers/public/__struct_stat.h + // Matches wasi-libc's struct stat in lib/libc/include/wasm-wasi-musl/__struct_stat.h dev: dev_t, ino: ino_t, nlink: nlink_t, @@ -7213,7 +7220,7 @@ pub const AT = switch (native_os) { pub const RECURSIVE = 0x8000; }, .wasi => struct { - // Match AT_* constants in wasi-libc libc-bottom-half/headers/public/__header_fcntl.h + // Match AT_* constants in lib/libc/include/wasm-wasi-musl/__header_fcntl.h pub const FDCWD = -2; pub const EACCESS = 0x0; pub const SYMLINK_NOFOLLOW = 0x1; @@ -7248,7 +7255,7 @@ pub const O = switch (native_os) { _: u9 = 0, }, .wasi => packed struct(u32) { - // Match layout from wasi-libc libc-bottom-half/headers/public/__header_fcntl.h + // Match O_* bits from lib/libc/include/wasm-wasi-musl/__header_fcntl.h APPEND: bool = false, DSYNC: bool = false, NONBLOCK: bool = false,