authorgravatar for pat.github@tullmann.orgPat Tullmann <pat.github@tullmann.org> 2024-09-09 19:51:15-07:00
committergravatar for alex@alexrp.comAlex Rønne Petersen <alex@alexrp.com> 2025-02-09 09:08:11+01:00
log138d30bb4763a5831ace21d916bde669b4ff5efb
tree64b1e2d2da168992253274314fcf377861ae71b5
parent0af492a2725d5be526dd2418f6526bd23877fd3c

wasi: fix wasm-wasi-musl constants

Zig's copy of the `SYMLINK_{NO,}FOLLOW` constants from wasi-musl was wrong, as were the `IFIFO` and `IFSOCK` file type flags. Fix these up, and add comments pointing to exactly where they come from (as the wasi-musl source has lots of unused, different definitions of these constants). Add tests for the Zig convention that WASM preopen 3 is the current working directory. This is true for WASM with or without libc. Enable several fs and posix tests that are now passing (not necessarily because of this change) on wasm targets. Fixes #20890.

6 files changed, 84 insertions(+), 53 deletions(-)

lib/std/c.zig+41-9
......@@ -692,6 +692,7 @@ pub const F = switch (native_os) {
692692 .linux => linux.F,
693693 .emscripten => emscripten.F,
694694 .wasi => struct {
695 // Match `F_*` constants from lib/libc/include/wasm-wasi-musl/__header_fcntl.h
695696 pub const GETFD = 1;
696697 pub const SETFD = 2;
697698 pub const GETFL = 3;
......@@ -1723,17 +1724,43 @@ pub const S = switch (native_os) {
17231724 .linux => linux.S,
17241725 .emscripten => emscripten.S,
17251726 .wasi => struct {
1726 pub const IEXEC = @compileError("TODO audit this");
1727 // Match `S_*` constants from lib/libc/include/wasm-wasi-musl/__mode_t.h
17271728 pub const IFBLK = 0x6000;
17281729 pub const IFCHR = 0x2000;
17291730 pub const IFDIR = 0x4000;
1730 pub const IFIFO = 0xc000;
1731 pub const IFIFO = 0x1000;
17311732 pub const IFLNK = 0xa000;
17321733 pub const IFMT = IFBLK | IFCHR | IFDIR | IFIFO | IFLNK | IFREG | IFSOCK;
17331734 pub const IFREG = 0x8000;
1734 /// There's no concept of UNIX domain socket but we define this value here
1735 /// in order to line with other OSes.
1736 pub const IFSOCK = 0x1;
1735 pub const IFSOCK = 0xc000;
1736
1737 pub fn ISBLK(m: u32) bool {
1738 return m & IFMT == IFBLK;
1739 }
1740
1741 pub fn ISCHR(m: u32) bool {
1742 return m & IFMT == IFCHR;
1743 }
1744
1745 pub fn ISDIR(m: u32) bool {
1746 return m & IFMT == IFDIR;
1747 }
1748
1749 pub fn ISFIFO(m: u32) bool {
1750 return m & IFMT == IFIFO;
1751 }
1752
1753 pub fn ISLNK(m: u32) bool {
1754 return m & IFMT == IFLNK;
1755 }
1756
1757 pub fn ISREG(m: u32) bool {
1758 return m & IFMT == IFREG;
1759 }
1760
1761 pub fn ISSOCK(m: u32) bool {
1762 return m & IFMT == IFSOCK;
1763 }
17371764 },
17381765 .macos, .ios, .tvos, .watchos, .visionos => struct {
17391766 pub const IFMT = 0o170000;
......@@ -6802,6 +6829,7 @@ pub const Stat = switch (native_os) {
68026829 },
68036830 .emscripten => emscripten.Stat,
68046831 .wasi => extern struct {
6832 // Match wasi-libc's `struct stat` in lib/libc/include/wasm-wasi-musl/__struct_stat.h
68056833 dev: dev_t,
68066834 ino: ino_t,
68076835 nlink: nlink_t,
......@@ -7502,9 +7530,11 @@ pub const AT = switch (native_os) {
75027530 pub const RECURSIVE = 0x8000;
75037531 },
75047532 .wasi => struct {
7505 pub const SYMLINK_NOFOLLOW = 0x100;
7506 pub const SYMLINK_FOLLOW = 0x400;
7507 pub const REMOVEDIR: u32 = 0x4;
7533 // Match `AT_*` constants in lib/libc/include/wasm-wasi-musl/__header_fcntl.h
7534 pub const EACCESS = 0x0;
7535 pub const SYMLINK_NOFOLLOW = 0x1;
7536 pub const SYMLINK_FOLLOW = 0x2;
7537 pub const REMOVEDIR = 0x4;
75087538 /// When linking libc, we follow their convention and use -2 for current working directory.
75097539 /// However, without libc, Zig does a different convention: it assumes the
75107540 /// current working directory is the first preopen. This behavior can be
......@@ -7512,7 +7542,6 @@ pub const AT = switch (native_os) {
75127542 /// file.
75137543 pub const FDCWD: fd_t = if (builtin.link_libc) -2 else 3;
75147544 },
7515
75167545 else => void,
75177546};
75187547
......@@ -7541,6 +7570,7 @@ pub const O = switch (native_os) {
75417570 _: u9 = 0,
75427571 },
75437572 .wasi => packed struct(u32) {
7573 // Match `O_*` bits from lib/libc/include/wasm-wasi-musl/__header_fcntl.h
75447574 APPEND: bool = false,
75457575 DSYNC: bool = false,
75467576 NONBLOCK: bool = false,
......@@ -7557,6 +7587,8 @@ pub const O = switch (native_os) {
75577587 read: bool = false,
75587588 SEARCH: bool = false,
75597589 write: bool = false,
7590 // O_CLOEXEC, O_TTY_ININT, O_NOCTTY are 0 in wasi-musl, so they're silently
7591 // ignored in C code. Thus no mapping in Zig.
75607592 _: u3 = 0,
75617593 },
75627594 .solaris, .illumos => packed struct(u32) {
lib/std/fs/Dir.zig+4-11
......@@ -1295,17 +1295,10 @@ pub fn realpathZ(self: Dir, pathname: [*:0]const u8, out_buffer: []u8) RealPathE
12951295 return self.realpathW(pathname_w.span(), out_buffer);
12961296 }
12971297
1298 const flags: posix.O = switch (native_os) {
1299 .linux => .{
1300 .NONBLOCK = true,
1301 .CLOEXEC = true,
1302 .PATH = true,
1303 },
1304 else => .{
1305 .NONBLOCK = true,
1306 .CLOEXEC = true,
1307 },
1308 };
1298 var flags: posix.O = .{};
1299 if (@hasField(posix.O, "NONBLOCK")) flags.NONBLOCK = true;
1300 if (@hasField(posix.O, "CLOEXEC")) flags.CLOEXEC = true;
1301 if (@hasField(posix.O, "PATH")) flags.PATH = true;
13091302
13101303 const fd = posix.openatZ(self.fd, pathname, flags, 0) catch |err| switch (err) {
13111304 error.FileLocksNotSupported => return error.Unexpected,
lib/std/fs/test.zig+6-9
......@@ -361,9 +361,12 @@ test "openDirAbsolute" {
361361}
362362
363363test "openDir cwd parent '..'" {
364 if (native_os == .wasi) return error.SkipZigTest;
365
366 var dir = try fs.cwd().openDir("..", .{});
364 var dir = fs.cwd().openDir("..", .{}) catch |err| {
365 if (native_os == .wasi and err == error.AccessDenied) {
366 return; // This is okay. WASI disallows escaping from the fs sandbox
367 }
368 return err;
369 };
367370 defer dir.close();
368371}
369372
......@@ -1678,8 +1681,6 @@ test "read from locked file" {
16781681}
16791682
16801683test "walker" {
1681 if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest;
1682
16831684 var tmp = tmpDir(.{ .iterate = true });
16841685 defer tmp.cleanup();
16851686
......@@ -1731,8 +1732,6 @@ test "walker" {
17311732}
17321733
17331734test "walker without fully iterating" {
1734 if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest;
1735
17361735 var tmp = tmpDir(.{ .iterate = true });
17371736 defer tmp.cleanup();
17381737
......@@ -1754,8 +1753,6 @@ test "walker without fully iterating" {
17541753}
17551754
17561755test "'.' and '..' in fs.Dir functions" {
1757 if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest;
1758
17591756 if (native_os == .windows and builtin.cpu.arch == .aarch64) {
17601757 // https://github.com/ziglang/zig/issues/17134
17611758 return error.SkipZigTest;
lib/std/os/wasi.zig+1-1
......@@ -1,7 +1,7 @@
11//! wasi_snapshot_preview1 spec available (in witx format) here:
22//! * typenames -- https://github.com/WebAssembly/WASI/blob/main/legacy/preview1/witx/typenames.witx
33//! * module -- https://github.com/WebAssembly/WASI/blob/main/legacy/preview1/witx/wasi_snapshot_preview1.witx
4//! Note that libc API does *not* go in this file. wasi libc API goes into std/c/wasi.zig instead.
4//! Note that libc API does *not* go in this file. wasi libc API goes into std/c.zig instead.
55const builtin = @import("builtin");
66const std = @import("std");
77const assert = std.debug.assert;
lib/std/posix.zig+4-4
......@@ -5143,10 +5143,10 @@ pub fn sysctl(
51435143 newlen: usize,
51445144) SysCtlError!void {
51455145 if (native_os == .wasi) {
5146 @panic("unsupported"); // TODO should be compile error, not panic
5146 @compileError("sysctl not supported on WASI");
51475147 }
51485148 if (native_os == .haiku) {
5149 @panic("unsupported"); // TODO should be compile error, not panic
5149 @compileError("sysctl not supported on Haiku");
51505150 }
51515151
51525152 const name_len = cast(c_uint, name.len) orelse return error.NameTooLong;
......@@ -5168,10 +5168,10 @@ pub fn sysctlbynameZ(
51685168 newlen: usize,
51695169) SysCtlError!void {
51705170 if (native_os == .wasi) {
5171 @panic("unsupported"); // TODO should be compile error, not panic
5171 @compileError("sysctl not supported on WASI");
51725172 }
51735173 if (native_os == .haiku) {
5174 @panic("unsupported"); // TODO should be compile error, not panic
5174 @compileError("sysctl not supported on Haiku");
51755175 }
51765176
51775177 switch (errno(system.sysctlbyname(name, oldp, oldlenp, newp, newlen))) {
lib/std/posix/test.zig+28-19
......@@ -31,6 +31,19 @@ test "WTF-8 to WTF-16 conversion buffer overflows" {
3131 try expectError(error.NameTooLong, posix.chdirZ(input_wtf8));
3232}
3333
34test "check WASI CWD" {
35 if (native_os == .wasi) {
36 if (std.options.wasiCwd() != 3) {
37 @panic("WASI code that uses cwd (like this test) needs a preopen for cwd (add '--dir=.' to wasmtime)");
38 }
39
40 if (!builtin.link_libc) {
41 // WASI without-libc hardcodes fd 3 as the FDCWD token so it can be passed directly to WASI calls
42 try expectEqual(3, posix.AT.FDCWD);
43 }
44 }
45}
46
3447test "chdir smoke test" {
3548 if (native_os == .wasi) return error.SkipZigTest;
3649
......@@ -151,7 +164,6 @@ test "open smoke test" {
151164}
152165
153166test "openat smoke test" {
154 if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest;
155167 if (native_os == .windows) return error.SkipZigTest;
156168
157169 // TODO verify file attributes using `fstatat`
......@@ -200,10 +212,13 @@ test "openat smoke test" {
200212 }), mode);
201213 posix.close(fd);
202214
203 // Try opening as file which should fail.
204 try expectError(error.IsDir, posix.openat(tmp.dir.fd, "some_dir", CommonOpenFlags.lower(.{
205 .ACCMODE = .RDWR,
206 }), mode));
215 // Try opening as file which should fail (skip on wasi+libc due to
216 // https://github.com/bytecodealliance/wasmtime/issues/9054)
217 if (native_os != .wasi or !builtin.link_libc) {
218 try expectError(error.IsDir, posix.openat(tmp.dir.fd, "some_dir", CommonOpenFlags.lower(.{
219 .ACCMODE = .RDWR,
220 }), mode));
221 }
207222}
208223
209224test "symlink with relative paths" {
......@@ -366,8 +381,7 @@ test "fstatat" {
366381 defer file.close();
367382
368383 // now repeat but using `fstatat` instead
369 const flags = if (native_os == .wasi) 0x0 else posix.AT.SYMLINK_NOFOLLOW;
370 const statat = try posix.fstatat(tmp.dir.fd, "file.txt", flags);
384 const statat = try posix.fstatat(tmp.dir.fd, "file.txt", posix.AT.SYMLINK_NOFOLLOW);
371385
372386 // s390x-linux does not have nanosecond precision for fstat(), but it does for fstatat(). As a
373387 // result, comparing the two structures is doomed to fail.
......@@ -1308,22 +1322,17 @@ const CommonOpenFlags = packed struct {
13081322 NONBLOCK: bool = false,
13091323
13101324 pub fn lower(cof: CommonOpenFlags) posix.O {
1311 if (native_os == .wasi) return .{
1325 var result: posix.O = if (native_os == .wasi) .{
13121326 .read = cof.ACCMODE != .WRONLY,
13131327 .write = cof.ACCMODE != .RDONLY,
1314 .CREAT = cof.CREAT,
1315 .EXCL = cof.EXCL,
1316 .DIRECTORY = cof.DIRECTORY,
1317 .NONBLOCK = cof.NONBLOCK,
1318 };
1319 var result: posix.O = .{
1328 } else .{
13201329 .ACCMODE = cof.ACCMODE,
1321 .CREAT = cof.CREAT,
1322 .EXCL = cof.EXCL,
1323 .DIRECTORY = cof.DIRECTORY,
1324 .NONBLOCK = cof.NONBLOCK,
1325 .CLOEXEC = cof.CLOEXEC,
13261330 };
1331 result.CREAT = cof.CREAT;
1332 result.EXCL = cof.EXCL;
1333 result.DIRECTORY = cof.DIRECTORY;
1334 result.NONBLOCK = cof.NONBLOCK;
1335 if (@hasField(posix.O, "CLOEXEC")) result.CLOEXEC = cof.CLOEXEC;
13271336 if (@hasField(posix.O, "LARGEFILE")) result.LARGEFILE = cof.LARGEFILE;
13281337 return result;
13291338 }