diff --git a/doc/langref/wasi_preopens.zig b/doc/langref/wasi_preopens.zig index 99ab36f314..423e79f3c9 100644 --- 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 index 4d149dbbda..93c2a9a0ae 100644 --- 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 deleted file mode 100644 index e17a852a9b..0000000000 --- a/lib/std/fs/wasi.zig +++ /dev/null @@ -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 index caef010683..87e1bc59c2 100644 --- 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 index d8db82cc05..0936116bf0 100644 --- 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 new file mode 100644 index 0000000000..8223c29f83 --- /dev/null +++ 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 index 8d2e2a9df2..685fda3635 100644 --- 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 index 5d369594c8..d617f0a0e5 100644 --- 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 index 97a4e53c2f..1da2c76a1a 100644 --- 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 index ac0578852e..163648321e 100644 --- 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, );