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) {...@@ -692,6 +692,7 @@ pub const F = switch (native_os) {
692 .linux => linux.F,692 .linux => linux.F,
693 .emscripten => emscripten.F,693 .emscripten => emscripten.F,
694 .wasi => struct {694 .wasi => struct {
695 // Match `F_*` constants from lib/libc/include/wasm-wasi-musl/__header_fcntl.h
695 pub const GETFD = 1;696 pub const GETFD = 1;
696 pub const SETFD = 2;697 pub const SETFD = 2;
697 pub const GETFL = 3;698 pub const GETFL = 3;
...@@ -1723,17 +1724,43 @@ pub const S = switch (native_os) {...@@ -1723,17 +1724,43 @@ pub const S = switch (native_os) {
1723 .linux => linux.S,1724 .linux => linux.S,
1724 .emscripten => emscripten.S,1725 .emscripten => emscripten.S,
1725 .wasi => struct {1726 .wasi => struct {
1726 pub const IEXEC = @compileError("TODO audit this");1727 // Match `S_*` constants from lib/libc/include/wasm-wasi-musl/__mode_t.h
1727 pub const IFBLK = 0x6000;1728 pub const IFBLK = 0x6000;
1728 pub const IFCHR = 0x2000;1729 pub const IFCHR = 0x2000;
1729 pub const IFDIR = 0x4000;1730 pub const IFDIR = 0x4000;
1730 pub const IFIFO = 0xc000;1731 pub const IFIFO = 0x1000;
1731 pub const IFLNK = 0xa000;1732 pub const IFLNK = 0xa000;
1732 pub const IFMT = IFBLK | IFCHR | IFDIR | IFIFO | IFLNK | IFREG | IFSOCK;1733 pub const IFMT = IFBLK | IFCHR | IFDIR | IFIFO | IFLNK | IFREG | IFSOCK;
1733 pub const IFREG = 0x8000;1734 pub const IFREG = 0x8000;
1734 /// There's no concept of UNIX domain socket but we define this value here1735 pub const IFSOCK = 0xc000;
1735 /// in order to line with other OSes.1736
1736 pub const IFSOCK = 0x1;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 }
1737 },1764 },
1738 .macos, .ios, .tvos, .watchos, .visionos => struct {1765 .macos, .ios, .tvos, .watchos, .visionos => struct {
1739 pub const IFMT = 0o170000;1766 pub const IFMT = 0o170000;
...@@ -6802,6 +6829,7 @@ pub const Stat = switch (native_os) {...@@ -6802,6 +6829,7 @@ pub const Stat = switch (native_os) {
6802 },6829 },
6803 .emscripten => emscripten.Stat,6830 .emscripten => emscripten.Stat,
6804 .wasi => extern struct {6831 .wasi => extern struct {
6832 // Match wasi-libc's `struct stat` in lib/libc/include/wasm-wasi-musl/__struct_stat.h
6805 dev: dev_t,6833 dev: dev_t,
6806 ino: ino_t,6834 ino: ino_t,
6807 nlink: nlink_t,6835 nlink: nlink_t,
...@@ -7502,9 +7530,11 @@ pub const AT = switch (native_os) {...@@ -7502,9 +7530,11 @@ pub const AT = switch (native_os) {
7502 pub const RECURSIVE = 0x8000;7530 pub const RECURSIVE = 0x8000;
7503 },7531 },
7504 .wasi => struct {7532 .wasi => struct {
7505 pub const SYMLINK_NOFOLLOW = 0x100;7533 // Match `AT_*` constants in lib/libc/include/wasm-wasi-musl/__header_fcntl.h
7506 pub const SYMLINK_FOLLOW = 0x400;7534 pub const EACCESS = 0x0;
7507 pub const REMOVEDIR: u32 = 0x4;7535 pub const SYMLINK_NOFOLLOW = 0x1;
7536 pub const SYMLINK_FOLLOW = 0x2;
7537 pub const REMOVEDIR = 0x4;
7508 /// When linking libc, we follow their convention and use -2 for current working directory.7538 /// When linking libc, we follow their convention and use -2 for current working directory.
7509 /// However, without libc, Zig does a different convention: it assumes the7539 /// However, without libc, Zig does a different convention: it assumes the
7510 /// current working directory is the first preopen. This behavior can be7540 /// current working directory is the first preopen. This behavior can be
...@@ -7512,7 +7542,6 @@ pub const AT = switch (native_os) {...@@ -7512,7 +7542,6 @@ pub const AT = switch (native_os) {
7512 /// file.7542 /// file.
7513 pub const FDCWD: fd_t = if (builtin.link_libc) -2 else 3;7543 pub const FDCWD: fd_t = if (builtin.link_libc) -2 else 3;
7514 },7544 },
7515
7516 else => void,7545 else => void,
7517};7546};
75187547
...@@ -7541,6 +7570,7 @@ pub const O = switch (native_os) {...@@ -7541,6 +7570,7 @@ pub const O = switch (native_os) {
7541 _: u9 = 0,7570 _: u9 = 0,
7542 },7571 },
7543 .wasi => packed struct(u32) {7572 .wasi => packed struct(u32) {
7573 // Match `O_*` bits from lib/libc/include/wasm-wasi-musl/__header_fcntl.h
7544 APPEND: bool = false,7574 APPEND: bool = false,
7545 DSYNC: bool = false,7575 DSYNC: bool = false,
7546 NONBLOCK: bool = false,7576 NONBLOCK: bool = false,
...@@ -7557,6 +7587,8 @@ pub const O = switch (native_os) {...@@ -7557,6 +7587,8 @@ pub const O = switch (native_os) {
7557 read: bool = false,7587 read: bool = false,
7558 SEARCH: bool = false,7588 SEARCH: bool = false,
7559 write: bool = false,7589 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.
7560 _: u3 = 0,7592 _: u3 = 0,
7561 },7593 },
7562 .solaris, .illumos => packed struct(u32) {7594 .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...@@ -1295,17 +1295,10 @@ pub fn realpathZ(self: Dir, pathname: [*:0]const u8, out_buffer: []u8) RealPathE
1295 return self.realpathW(pathname_w.span(), out_buffer);1295 return self.realpathW(pathname_w.span(), out_buffer);
1296 }1296 }
12971297
1298 const flags: posix.O = switch (native_os) {1298 var flags: posix.O = .{};
1299 .linux => .{1299 if (@hasField(posix.O, "NONBLOCK")) flags.NONBLOCK = true;
1300 .NONBLOCK = true,1300 if (@hasField(posix.O, "CLOEXEC")) flags.CLOEXEC = true;
1301 .CLOEXEC = true,1301 if (@hasField(posix.O, "PATH")) flags.PATH = true;
1302 .PATH = true,
1303 },
1304 else => .{
1305 .NONBLOCK = true,
1306 .CLOEXEC = true,
1307 },
1308 };
13091302
1310 const fd = posix.openatZ(self.fd, pathname, flags, 0) catch |err| switch (err) {1303 const fd = posix.openatZ(self.fd, pathname, flags, 0) catch |err| switch (err) {
1311 error.FileLocksNotSupported => return error.Unexpected,1304 error.FileLocksNotSupported => return error.Unexpected,
lib/std/fs/test.zig+6-9
...@@ -361,9 +361,12 @@ test "openDirAbsolute" {...@@ -361,9 +361,12 @@ test "openDirAbsolute" {
361}361}
362362
363test "openDir cwd parent '..'" {363test "openDir cwd parent '..'" {
364 if (native_os == .wasi) return error.SkipZigTest;364 var dir = fs.cwd().openDir("..", .{}) catch |err| {
365365 if (native_os == .wasi and err == error.AccessDenied) {
366 var dir = try fs.cwd().openDir("..", .{});366 return; // This is okay. WASI disallows escaping from the fs sandbox
367 }
368 return err;
369 };
367 defer dir.close();370 defer dir.close();
368}371}
369372
...@@ -1678,8 +1681,6 @@ test "read from locked file" {...@@ -1678,8 +1681,6 @@ test "read from locked file" {
1678}1681}
16791682
1680test "walker" {1683test "walker" {
1681 if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest;
1682
1683 var tmp = tmpDir(.{ .iterate = true });1684 var tmp = tmpDir(.{ .iterate = true });
1684 defer tmp.cleanup();1685 defer tmp.cleanup();
16851686
...@@ -1731,8 +1732,6 @@ test "walker" {...@@ -1731,8 +1732,6 @@ test "walker" {
1731}1732}
17321733
1733test "walker without fully iterating" {1734test "walker without fully iterating" {
1734 if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest;
1735
1736 var tmp = tmpDir(.{ .iterate = true });1735 var tmp = tmpDir(.{ .iterate = true });
1737 defer tmp.cleanup();1736 defer tmp.cleanup();
17381737
...@@ -1754,8 +1753,6 @@ test "walker without fully iterating" {...@@ -1754,8 +1753,6 @@ test "walker without fully iterating" {
1754}1753}
17551754
1756test "'.' and '..' in fs.Dir functions" {1755test "'.' and '..' in fs.Dir functions" {
1757 if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest;
1758
1759 if (native_os == .windows and builtin.cpu.arch == .aarch64) {1756 if (native_os == .windows and builtin.cpu.arch == .aarch64) {
1760 // https://github.com/ziglang/zig/issues/171341757 // https://github.com/ziglang/zig/issues/17134
1761 return error.SkipZigTest;1758 return error.SkipZigTest;
lib/std/os/wasi.zig+1-1
...@@ -1,7 +1,7 @@...@@ -1,7 +1,7 @@
1//! wasi_snapshot_preview1 spec available (in witx format) here:1//! wasi_snapshot_preview1 spec available (in witx format) here:
2//! * typenames -- https://github.com/WebAssembly/WASI/blob/main/legacy/preview1/witx/typenames.witx2//! * typenames -- https://github.com/WebAssembly/WASI/blob/main/legacy/preview1/witx/typenames.witx
3//! * module -- https://github.com/WebAssembly/WASI/blob/main/legacy/preview1/witx/wasi_snapshot_preview1.witx3//! * 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.
5const builtin = @import("builtin");5const builtin = @import("builtin");
6const std = @import("std");6const std = @import("std");
7const assert = std.debug.assert;7const assert = std.debug.assert;
lib/std/posix.zig+4-4
...@@ -5143,10 +5143,10 @@ pub fn sysctl(...@@ -5143,10 +5143,10 @@ pub fn sysctl(
5143 newlen: usize,5143 newlen: usize,
5144) SysCtlError!void {5144) SysCtlError!void {
5145 if (native_os == .wasi) {5145 if (native_os == .wasi) {
5146 @panic("unsupported"); // TODO should be compile error, not panic5146 @compileError("sysctl not supported on WASI");
5147 }5147 }
5148 if (native_os == .haiku) {5148 if (native_os == .haiku) {
5149 @panic("unsupported"); // TODO should be compile error, not panic5149 @compileError("sysctl not supported on Haiku");
5150 }5150 }
51515151
5152 const name_len = cast(c_uint, name.len) orelse return error.NameTooLong;5152 const name_len = cast(c_uint, name.len) orelse return error.NameTooLong;
...@@ -5168,10 +5168,10 @@ pub fn sysctlbynameZ(...@@ -5168,10 +5168,10 @@ pub fn sysctlbynameZ(
5168 newlen: usize,5168 newlen: usize,
5169) SysCtlError!void {5169) SysCtlError!void {
5170 if (native_os == .wasi) {5170 if (native_os == .wasi) {
5171 @panic("unsupported"); // TODO should be compile error, not panic5171 @compileError("sysctl not supported on WASI");
5172 }5172 }
5173 if (native_os == .haiku) {5173 if (native_os == .haiku) {
5174 @panic("unsupported"); // TODO should be compile error, not panic5174 @compileError("sysctl not supported on Haiku");
5175 }5175 }
51765176
5177 switch (errno(system.sysctlbyname(name, oldp, oldlenp, newp, newlen))) {5177 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" {...@@ -31,6 +31,19 @@ test "WTF-8 to WTF-16 conversion buffer overflows" {
31 try expectError(error.NameTooLong, posix.chdirZ(input_wtf8));31 try expectError(error.NameTooLong, posix.chdirZ(input_wtf8));
32}32}
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
34test "chdir smoke test" {47test "chdir smoke test" {
35 if (native_os == .wasi) return error.SkipZigTest;48 if (native_os == .wasi) return error.SkipZigTest;
3649
...@@ -151,7 +164,6 @@ test "open smoke test" {...@@ -151,7 +164,6 @@ test "open smoke test" {
151}164}
152165
153test "openat smoke test" {166test "openat smoke test" {
154 if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest;
155 if (native_os == .windows) return error.SkipZigTest;167 if (native_os == .windows) return error.SkipZigTest;
156168
157 // TODO verify file attributes using `fstatat`169 // TODO verify file attributes using `fstatat`
...@@ -200,10 +212,13 @@ test "openat smoke test" {...@@ -200,10 +212,13 @@ test "openat smoke test" {
200 }), mode);212 }), mode);
201 posix.close(fd);213 posix.close(fd);
202214
203 // Try opening as file which should fail.215 // Try opening as file which should fail (skip on wasi+libc due to
204 try expectError(error.IsDir, posix.openat(tmp.dir.fd, "some_dir", CommonOpenFlags.lower(.{216 // https://github.com/bytecodealliance/wasmtime/issues/9054)
205 .ACCMODE = .RDWR,217 if (native_os != .wasi or !builtin.link_libc) {
206 }), mode));218 try expectError(error.IsDir, posix.openat(tmp.dir.fd, "some_dir", CommonOpenFlags.lower(.{
219 .ACCMODE = .RDWR,
220 }), mode));
221 }
207}222}
208223
209test "symlink with relative paths" {224test "symlink with relative paths" {
...@@ -366,8 +381,7 @@ test "fstatat" {...@@ -366,8 +381,7 @@ test "fstatat" {
366 defer file.close();381 defer file.close();
367382
368 // now repeat but using `fstatat` instead383 // now repeat but using `fstatat` instead
369 const flags = if (native_os == .wasi) 0x0 else posix.AT.SYMLINK_NOFOLLOW;384 const statat = try posix.fstatat(tmp.dir.fd, "file.txt", posix.AT.SYMLINK_NOFOLLOW);
370 const statat = try posix.fstatat(tmp.dir.fd, "file.txt", flags);
371385
372 // s390x-linux does not have nanosecond precision for fstat(), but it does for fstatat(). As a386 // s390x-linux does not have nanosecond precision for fstat(), but it does for fstatat(). As a
373 // result, comparing the two structures is doomed to fail.387 // result, comparing the two structures is doomed to fail.
...@@ -1308,22 +1322,17 @@ const CommonOpenFlags = packed struct {...@@ -1308,22 +1322,17 @@ const CommonOpenFlags = packed struct {
1308 NONBLOCK: bool = false,1322 NONBLOCK: bool = false,
13091323
1310 pub fn lower(cof: CommonOpenFlags) posix.O {1324 pub fn lower(cof: CommonOpenFlags) posix.O {
1311 if (native_os == .wasi) return .{1325 var result: posix.O = if (native_os == .wasi) .{
1312 .read = cof.ACCMODE != .WRONLY,1326 .read = cof.ACCMODE != .WRONLY,
1313 .write = cof.ACCMODE != .RDONLY,1327 .write = cof.ACCMODE != .RDONLY,
1314 .CREAT = cof.CREAT,1328 } else .{
1315 .EXCL = cof.EXCL,
1316 .DIRECTORY = cof.DIRECTORY,
1317 .NONBLOCK = cof.NONBLOCK,
1318 };
1319 var result: posix.O = .{
1320 .ACCMODE = cof.ACCMODE,1329 .ACCMODE = cof.ACCMODE,
1321 .CREAT = cof.CREAT,
1322 .EXCL = cof.EXCL,
1323 .DIRECTORY = cof.DIRECTORY,
1324 .NONBLOCK = cof.NONBLOCK,
1325 .CLOEXEC = cof.CLOEXEC,
1326 };1330 };
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;
1327 if (@hasField(posix.O, "LARGEFILE")) result.LARGEFILE = cof.LARGEFILE;1336 if (@hasField(posix.O, "LARGEFILE")) result.LARGEFILE = cof.LARGEFILE;
1328 return result;1337 return result;
1329 }1338 }