commit 6a5bb3ede36ab9dd7a5ce95e1339ca4e138886fc (tree)
parent d2d8b969a1674a6583292631ca7decc94cb56145
Author: Andrew Kelley <andrew@ziglang.org>
Date: Wed, 7 Jan 2026 12:09:09 -0800
std: find a better home for the "preopens" concept
Diffstat:
10 files changed, 106 insertions(+), 84 deletions(-)
diff --git a/doc/langref/wasi_preopens.zig b/doc/langref/wasi_preopens.zig
@@ -1,10 +1,8 @@
const std = @import("std");
-pub fn main(init: std.process.Init) !void {
- const preopens = try std.fs.wasi.preopensAlloc(init.arena.allocator());
-
- for (preopens.names, 0..) |preopen, i| {
- std.debug.print("{d}: {s}\n", .{ i, preopen });
+pub fn main(init: std.process.Init) void {
+ for (init.preopens.map.keys(), 0..) |preopen, i| {
+ std.log.info("{d}: {s}", .{ i, preopen });
}
}
diff --git a/lib/std/fs.zig b/lib/std/fs.zig
@@ -4,7 +4,6 @@ const std = @import("std.zig");
/// Deprecated, use `std.Io.Dir.path`.
pub const path = @import("fs/path.zig");
-pub const wasi = @import("fs/wasi.zig");
pub const base64_alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_".*;
diff --git a/lib/std/fs/wasi.zig b/lib/std/fs/wasi.zig
@@ -1,55 +0,0 @@
-const std = @import("std");
-const builtin = @import("builtin");
-const mem = std.mem;
-const math = std.math;
-const fs = std.fs;
-const assert = std.debug.assert;
-const Allocator = mem.Allocator;
-const wasi = std.os.wasi;
-const fd_t = wasi.fd_t;
-const prestat_t = wasi.prestat_t;
-
-pub const Preopens = struct {
- // Indexed by file descriptor number.
- names: []const []const u8,
-
- pub fn find(p: Preopens, name: []const u8) ?std.posix.fd_t {
- for (p.names, 0..) |elem_name, i| {
- if (mem.eql(u8, elem_name, name)) {
- return @intCast(i);
- }
- }
- return null;
- }
-};
-
-pub fn preopensAlloc(gpa: Allocator) Allocator.Error!Preopens {
- var names: std.ArrayList([]const u8) = .empty;
- defer names.deinit(gpa);
-
- try names.ensureUnusedCapacity(gpa, 3);
-
- names.appendAssumeCapacity("stdin"); // 0
- names.appendAssumeCapacity("stdout"); // 1
- names.appendAssumeCapacity("stderr"); // 2
- while (true) {
- const fd = @as(wasi.fd_t, @intCast(names.items.len));
- var prestat: prestat_t = undefined;
- switch (wasi.fd_prestat_get(fd, &prestat)) {
- .SUCCESS => {},
- .OPNOTSUPP, .BADF => return .{ .names = try names.toOwnedSlice(gpa) },
- else => @panic("fd_prestat_get: unexpected error"),
- }
- try names.ensureUnusedCapacity(gpa, 1);
- // This length does not include a null byte. Let's keep it this way to
- // gently encourage WASI implementations to behave properly.
- const name_len = prestat.u.dir.pr_name_len;
- const name = try gpa.alloc(u8, name_len);
- errdefer gpa.free(name);
- switch (wasi.fd_prestat_dir_name(fd, name.ptr, name.len)) {
- .SUCCESS => {},
- else => @panic("fd_prestat_dir_name: unexpected error"),
- }
- names.appendAssumeCapacity(name);
- }
-}
diff --git a/lib/std/os/wasi.zig b/lib/std/os/wasi.zig
@@ -288,8 +288,9 @@ pub const oflags_t = packed struct(u16) {
_: u12 = 0,
};
-pub const preopentype_t = u8;
-pub const PREOPENTYPE_DIR: preopentype_t = 0;
+pub const preopentype_t = enum(u8) {
+ DIR = 0,
+};
pub const prestat_t = extern struct {
pr_type: preopentype_t,
diff --git a/lib/std/process.zig b/lib/std/process.zig
@@ -18,6 +18,7 @@ const max_path_bytes = std.fs.max_path_bytes;
pub const Child = @import("process/Child.zig");
pub const Args = @import("process/Args.zig");
pub const Environ = @import("process/Environ.zig");
+pub const Preopens = @import("process/Preopens.zig");
/// This is the global, process-wide protection to coordinate stderr writes.
///
@@ -48,6 +49,10 @@ pub const Init = struct {
io: Io,
/// Environment variables, initialized with `gpa`. Not threadsafe.
environ_map: *Environ.Map,
+ /// Named files that have been provided by the parent process. This is
+ /// mainly useful on WASI, but can be used on other systems to mimic the
+ /// behavior with respect to stdio.
+ preopens: Preopens,
/// Alternative to `Init` as the first parameter of the main function.
pub const Minimal = struct {
diff --git a/lib/std/process/Preopens.zig b/lib/std/process/Preopens.zig
@@ -0,0 +1,75 @@
+const Preopens = @This();
+
+const builtin = @import("builtin");
+const native_os = builtin.os.tag;
+
+const std = @import("../std.zig");
+const Io = std.Io;
+const Allocator = std.mem.Allocator;
+
+map: Map,
+
+pub const empty: Preopens = switch (native_os) {
+ .wasi => .{ .map = .empty },
+ else => .{ .map = {} },
+};
+
+pub const Map = switch (native_os) {
+ // Indexed by file descriptor number.
+ .wasi => std.StringArrayHashMapUnmanaged(void),
+ else => void,
+};
+
+pub const Resource = union(enum) {
+ file: Io.File,
+ dir: Io.Dir,
+};
+
+pub fn get(p: *const Preopens, name: []const u8) ?Resource {
+ switch (native_os) {
+ .wasi => {
+ const index = p.map.getIndex(name) orelse return null;
+ if (index <= 2) return .{ .file = .{ .handle = @intCast(index) } };
+ return .{ .dir = .{ .handle = @intCast(index) } };
+ },
+ else => {
+ if (std.mem.eql(u8, name, "stdin")) return .{ .file = .stdin() };
+ if (std.mem.eql(u8, name, "stdout")) return .{ .file = .stdout() };
+ if (std.mem.eql(u8, name, "stderr")) return .{ .file = .stderr() };
+ return null;
+ },
+ }
+}
+
+pub const InitError = Allocator.Error || error{Unexpected};
+
+pub fn init(arena: Allocator) InitError!Preopens {
+ if (native_os != .wasi) return .{ .map = {} };
+ const wasi = std.os.wasi;
+ var map: Map = .empty;
+
+ try map.ensureUnusedCapacity(arena, 3);
+
+ map.putAssumeCapacityNoClobber("stdin", {}); // 0
+ map.putAssumeCapacityNoClobber("stdout", {}); // 1
+ map.putAssumeCapacityNoClobber("stderr", {}); // 2
+ while (true) {
+ const fd: wasi.fd_t = @intCast(map.entries.len);
+ var prestat: wasi.prestat_t = undefined;
+ switch (wasi.fd_prestat_get(fd, &prestat)) {
+ .SUCCESS => {},
+ .OPNOTSUPP, .BADF => return .{ .map = map },
+ else => return error.Unexpected,
+ }
+ try map.ensureUnusedCapacity(arena, 1);
+ // This length does not include a null byte. Let's keep it this way to
+ // gently encourage WASI implementations to behave properly.
+ const name_len = prestat.u.dir.pr_name_len;
+ const name = try arena.alloc(u8, name_len);
+ switch (wasi.fd_prestat_dir_name(fd, name.ptr, name.len)) {
+ .SUCCESS => {},
+ else => return error.Unexpected,
+ }
+ map.putAssumeCapacityNoClobber(name, {});
+ }
+}
diff --git a/lib/std/start.zig b/lib/std/start.zig
@@ -708,6 +708,9 @@ inline fn callMain(args: std.process.Args.Vector, environ: std.process.Environ.B
std.process.fatal("failed to parse environment variables: {t}", .{err});
defer environ_map.deinit();
+ const preopens = std.process.Preopens.init(arena_allocator.allocator()) catch |err|
+ std.process.fatal("failed to init preopens: {t}", .{err});
+
return wrapMain(root.main(.{
.minimal = .{
.args = .{ .vector = args },
@@ -717,6 +720,7 @@ inline fn callMain(args: std.process.Args.Vector, environ: std.process.Environ.B
.gpa = gpa,
.io = threaded.io(),
.environ_map = &environ_map,
+ .preopens = preopens,
}));
}
diff --git a/src/Compilation.zig b/src/Compilation.zig
@@ -758,10 +758,7 @@ pub const Directories = struct {
search,
global,
},
- wasi_preopens: switch (builtin.target.os.tag) {
- .wasi => fs.wasi.Preopens,
- else => void,
- },
+ preopens: std.process.Preopens,
self_exe_path: switch (builtin.target.os.tag) {
.wasi => void,
else => []const u8,
@@ -776,7 +773,7 @@ pub const Directories = struct {
const zig_lib: Cache.Directory = d: {
if (override_zig_lib) |path| break :d openUnresolved(arena, io, cwd, path, .@"zig lib");
- if (wasi) break :d openWasiPreopen(wasi_preopens, "/lib");
+ if (wasi) break :d getPreopen(preopens, "/lib");
break :d introspect.findZigLibDirFromSelfExe(arena, io, cwd, self_exe_path) catch |err| {
fatal("unable to find zig installation directory '{s}': {t}", .{ self_exe_path, err });
};
@@ -784,7 +781,7 @@ pub const Directories = struct {
const global_cache: Cache.Directory = d: {
if (override_global_cache) |path| break :d openUnresolved(arena, io, cwd, path, .@"global cache");
- if (wasi) break :d openWasiPreopen(wasi_preopens, "/cache");
+ if (wasi) break :d getPreopen(preopens, "/cache");
const path = introspect.resolveGlobalCacheDir(arena, environ_map) catch |err| {
fatal("unable to resolve zig cache directory: {t}", .{err});
};
@@ -817,11 +814,12 @@ pub const Directories = struct {
.local_cache = local_cache,
};
}
- fn openWasiPreopen(preopens: fs.wasi.Preopens, name: []const u8) Cache.Directory {
+ fn getPreopen(preopens: std.process.Preopens, name: []const u8) Cache.Directory {
return .{
.path = if (std.mem.eql(u8, name, ".")) null else name,
- .handle = .{
- .handle = preopens.find(name) orelse fatal("WASI preopen not found: '{s}'", .{name}),
+ .handle = switch (preopens.get(name) orelse fatal("preopen not found: '{s}'", .{name})) {
+ .file => fatal("preopen {s} is not a directory", .{name}),
+ .dir => |d| d,
},
};
}
diff --git a/src/main.zig b/src/main.zig
@@ -55,11 +55,11 @@ pub const std_options_cwd = if (native_os == .wasi) wasi_cwd else null;
pub const panic = crash_report.panic;
pub const debug = crash_report.debug;
-var wasi_preopens: fs.wasi.Preopens = undefined;
+var preopens: std.process.Preopens = .empty;
pub fn wasi_cwd() Io.Dir {
// Expect the first preopen to be current working directory.
const cwd_fd: std.posix.fd_t = 3;
- assert(mem.eql(u8, wasi_preopens.names[cwd_fd], "."));
+ assert(mem.eql(u8, preopens.map.keys()[cwd_fd], "."));
return .{ .handle = cwd_fd };
}
@@ -210,7 +210,7 @@ pub fn main(init: std.process.Init.Minimal) anyerror!void {
}
if (native_os == .wasi) {
- wasi_preopens = try fs.wasi.preopensAlloc(arena);
+ preopens = try .init(arena);
}
return mainArgs(gpa, arena, io, args, &environ_map);
@@ -360,7 +360,7 @@ fn mainArgs(
io,
&stdout_writer.interface,
args,
- if (native_os == .wasi) wasi_preopens,
+ preopens,
&host,
environ_map,
);
@@ -3107,7 +3107,7 @@ fn buildOutputType(
else => .search,
};
},
- if (native_os == .wasi) wasi_preopens,
+ preopens,
self_exe_path,
environ_map,
);
@@ -5141,7 +5141,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8,
if (override_local_cache_dir) |d| break :path d;
break :path try build_root.directory.join(arena, &.{introspect.default_local_zig_cache_basename});
} },
- {},
+ .empty,
self_exe_path,
environ_map,
);
@@ -5556,7 +5556,7 @@ fn jitCmd(
override_lib_dir,
override_global_cache_dir,
.global,
- if (native_os == .wasi) wasi_preopens,
+ preopens,
self_exe_path,
environ_map,
);
diff --git a/src/print_env.zig b/src/print_env.zig
@@ -14,10 +14,7 @@ pub fn cmdEnv(
io: Io,
out: *std.Io.Writer,
args: []const []const u8,
- wasi_preopens: switch (builtin.target.os.tag) {
- .wasi => std.fs.wasi.Preopens,
- else => void,
- },
+ preopens: std.process.Preopens,
host: *const std.Target,
environ_map: *std.process.Environ.Map,
) !void {
@@ -37,7 +34,7 @@ pub fn cmdEnv(
override_lib_dir,
override_global_cache_dir,
.global,
- if (builtin.target.os.tag == .wasi) wasi_preopens,
+ preopens,
if (builtin.target.os.tag != .wasi) self_exe_path,
environ_map,
);