Skip to content

Commit d52d200

Browse files
committed
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 wasi-musl 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.
1 parent cdc9d65 commit d52d200

File tree

6 files changed

+91
-53
lines changed

6 files changed

+91
-53
lines changed

lib/std/c.zig

Lines changed: 48 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -691,6 +691,7 @@ pub const F = switch (native_os) {
691691
.linux => linux.F,
692692
.emscripten => emscripten.F,
693693
.wasi => struct {
694+
// Match `F_*` constants from lib/libc/include/wasm-wasi-musl/__header_fcntl.h
694695
pub const GETFD = 1;
695696
pub const SETFD = 2;
696697
pub const GETFL = 3;
@@ -1722,17 +1723,50 @@ pub const S = switch (native_os) {
17221723
.linux => linux.S,
17231724
.emscripten => emscripten.S,
17241725
.wasi => struct {
1725-
pub const IEXEC = @compileError("TODO audit this");
1726+
// Match `S_*` constants from lib/libc/include/wasm-wasi-musl/__mode_t.h
1727+
//
1728+
// Note, a bug in wasi-libc means both IFIFO and IFSOCK have the same value (0xc000).
1729+
// IFIFO should be 0x1000 (see https://github.com/WebAssembly/wasi-libc/pull/463), and
1730+
// 0x1000 is used by the wasi-libc bottom-half implementation. So we use 0x1000 here.
1731+
// But the actual bit values we get back from a wasi-libc may get masked with the wrong
1732+
// values, or may get mistranslated. So the FIFO and FSOCK file-type bits are not
1733+
// trustworthy.
17261734
pub const IFBLK = 0x6000;
17271735
pub const IFCHR = 0x2000;
17281736
pub const IFDIR = 0x4000;
1729-
pub const IFIFO = 0xc000;
1737+
pub const IFIFO = 0x1000; // also IFSOCK
17301738
pub const IFLNK = 0xa000;
17311739
pub const IFMT = IFBLK | IFCHR | IFDIR | IFIFO | IFLNK | IFREG | IFSOCK;
17321740
pub const IFREG = 0x8000;
1733-
/// There's no concept of UNIX domain socket but we define this value here
1734-
/// in order to line with other OSes.
1735-
pub const IFSOCK = 0x1;
1741+
pub const IFSOCK = 0xc000;
1742+
1743+
pub fn ISBLK(m: u32) bool {
1744+
return m & IFMT == IFBLK;
1745+
}
1746+
1747+
pub fn ISCHR(m: u32) bool {
1748+
return m & IFMT == IFCHR;
1749+
}
1750+
1751+
pub fn ISDIR(m: u32) bool {
1752+
return m & IFMT == IFDIR;
1753+
}
1754+
1755+
pub fn ISFIFO(m: u32) bool {
1756+
return m & IFMT == IFIFO;
1757+
}
1758+
1759+
pub fn ISLNK(m: u32) bool {
1760+
return m & IFMT == IFLNK;
1761+
}
1762+
1763+
pub fn ISREG(m: u32) bool {
1764+
return m & IFMT == IFREG;
1765+
}
1766+
1767+
pub fn ISSOCK(m: u32) bool {
1768+
return m & IFMT == IFSOCK;
1769+
}
17361770
},
17371771
.macos, .ios, .tvos, .watchos, .visionos => struct {
17381772
pub const IFMT = 0o170000;
@@ -6768,6 +6802,7 @@ pub const Stat = switch (native_os) {
67686802
},
67696803
.emscripten => emscripten.Stat,
67706804
.wasi => extern struct {
6805+
// Match wasi-libc's `struct stat` in lib/libc/include/wasm-wasi-musl/__struct_stat.h
67716806
dev: dev_t,
67726807
ino: ino_t,
67736808
nlink: nlink_t,
@@ -7468,17 +7503,18 @@ pub const AT = switch (native_os) {
74687503
pub const RECURSIVE = 0x8000;
74697504
},
74707505
.wasi => struct {
7471-
pub const SYMLINK_NOFOLLOW = 0x100;
7472-
pub const SYMLINK_FOLLOW = 0x400;
7473-
pub const REMOVEDIR: u32 = 0x4;
7506+
// Match `AT_*` constants in lib/libc/include/wasm-wasi-musl/__header_fcntl.h
7507+
pub const EACCESS = 0x0;
7508+
pub const SYMLINK_NOFOLLOW = 0x1;
7509+
pub const SYMLINK_FOLLOW = 0x2;
7510+
pub const REMOVEDIR = 0x4;
74747511
/// When linking libc, we follow their convention and use -2 for current working directory.
74757512
/// However, without libc, Zig does a different convention: it assumes the
74767513
/// current working directory is the first preopen. This behavior can be
74777514
/// overridden with a public function called `wasi_cwd` in the root source
74787515
/// file.
74797516
pub const FDCWD: fd_t = if (builtin.link_libc) -2 else 3;
74807517
},
7481-
74827518
else => void,
74837519
};
74847520

@@ -7507,6 +7543,7 @@ pub const O = switch (native_os) {
75077543
_: u9 = 0,
75087544
},
75097545
.wasi => packed struct(u32) {
7546+
// Match `O_*` bits from lib/libc/include/wasm-wasi-musl/__header_fcntl.h
75107547
APPEND: bool = false,
75117548
DSYNC: bool = false,
75127549
NONBLOCK: bool = false,
@@ -7523,6 +7560,8 @@ pub const O = switch (native_os) {
75237560
read: bool = false,
75247561
SEARCH: bool = false,
75257562
write: bool = false,
7563+
// O_CLOEXEC, O_TTY_ININT, O_NOCTTY are 0 in wasi-musl, so they're silently
7564+
// ignored in C code. Thus no mapping in Zig.
75267565
_: u3 = 0,
75277566
},
75287567
.solaris, .illumos => packed struct(u32) {

lib/std/fs/Dir.zig

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -361,9 +361,12 @@ test "openDirAbsolute" {
361361
}
362362

363363
test "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

16801683
test "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

17331734
test "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

17561755
test "'.' 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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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.
55
const builtin = @import("builtin");
66
const std = @import("std");
77
const assert = std.debug.assert;

lib/std/posix.zig

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5107,10 +5107,10 @@ pub fn sysctl(
51075107
newlen: usize,
51085108
) SysCtlError!void {
51095109
if (native_os == .wasi) {
5110-
@panic("unsupported"); // TODO should be compile error, not panic
5110+
@compileError("sysctl not supported on WASI");
51115111
}
51125112
if (native_os == .haiku) {
5113-
@panic("unsupported"); // TODO should be compile error, not panic
5113+
@compileError("sysctl not supported on Haiku");
51145114
}
51155115

51165116
const name_len = cast(c_uint, name.len) orelse return error.NameTooLong;
@@ -5132,10 +5132,10 @@ pub fn sysctlbynameZ(
51325132
newlen: usize,
51335133
) SysCtlError!void {
51345134
if (native_os == .wasi) {
5135-
@panic("unsupported"); // TODO should be compile error, not panic
5135+
@compileError("sysctl not supported on WASI");
51365136
}
51375137
if (native_os == .haiku) {
5138-
@panic("unsupported"); // TODO should be compile error, not panic
5138+
@compileError("sysctl not supported on Haiku");
51395139
}
51405140

51415141
switch (errno(system.sysctlbyname(name, oldp, oldlenp, newp, newlen))) {

lib/std/posix/test.zig

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,19 @@ test "WTF-8 to WTF-16 conversion buffer overflows" {
3131
try expectError(error.NameTooLong, posix.chdirZ(input_wtf8));
3232
}
3333

34+
test "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+
3447
test "chdir smoke test" {
3548
if (native_os == .wasi) return error.SkipZigTest;
3649

@@ -151,7 +164,6 @@ test "open smoke test" {
151164
}
152165

153166
test "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

209224
test "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
}

0 commit comments

Comments
 (0)