From d6a1e73142396732be80a7e0757cca1c07551d30 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 28 Dec 2025 20:46:02 -0800 Subject: [PATCH 01/60] std: start wrangling environment variables and process args this commit is unfinished. It marks a spot where I wanted to start moving child process stuff below the std.Io.VTable --- lib/compiler/build_runner.zig | 14 +- lib/std/Build.zig | 15 +- lib/std/Build/Step.zig | 40 +- lib/std/Build/Step/Options.zig | 2 +- lib/std/Build/Step/Run.zig | 31 +- lib/std/Build/WebServer.zig | 12 +- lib/std/Io.zig | 6 + lib/std/Io/Dir.zig | 7 +- lib/std/Io/Threaded.zig | 4 +- lib/std/Progress.zig | 2 +- lib/std/debug.zig | 2 +- lib/std/debug/ElfFile.zig | 8 +- lib/std/debug/SelfInfo/Elf.zig | 5 +- lib/std/os.zig | 65 +- lib/std/posix.zig | 118 +- lib/std/process.zig | 1898 ++------------------- lib/std/process/Args.zig | 958 +++++++++++ lib/std/process/Child.zig | 68 +- lib/std/process/Environ.zig | 764 +++++++++ lib/std/start.zig | 134 +- lib/std/std.zig | 6 +- lib/std/zig.zig | 17 +- lib/std/zig/system/darwin.zig | 7 +- test/link/macho.zig | 2 +- test/standalone/ios/build.zig | 2 +- test/standalone/windows_bat_args/fuzz.zig | 4 +- test/standalone/windows_bat_args/test.zig | 4 +- test/standalone/windows_paths/test.zig | 6 +- tools/doctest.zig | 2 +- 29 files changed, 2096 insertions(+), 2107 deletions(-) create mode 100644 lib/std/process/Args.zig create mode 100644 lib/std/process/Environ.zig diff --git a/lib/compiler/build_runner.zig b/lib/compiler/build_runner.zig index f5315d6496213795d6570d3654b9d4db8cc7361e..91e9d20dbb668f2bbff19bf9852d2ba54863a903 100644 --- a/lib/compiler/build_runner.zig +++ b/lib/compiler/build_runner.zig @@ -24,7 +24,7 @@ pub const std_options: std.Options = .{ .crypto_fork_safety = false, }; -pub fn main() !void { +pub fn main(init: process.Init.Minimal) !void { // The build runner is often short-lived, but thanks to `--watch` and `--webui`, that's not // always the case. So, we do need a true gpa for some things. var debug_gpa_state: std.heap.DebugAllocator(.{}) = .init; @@ -37,7 +37,7 @@ pub fn main() !void { var thread_safe_arena: std.heap.ThreadSafeAllocator = .{ .child_allocator = single_threaded_arena.allocator() }; const arena = thread_safe_arena.allocator(); - const args = try process.argsAlloc(arena); + const args = try init.args.toSlice(arena); var threaded: std.Io.Threaded = .init(gpa, .{}); defer threaded.deinit(); @@ -83,7 +83,7 @@ pub fn main() !void { .manifest_dir = try local_cache_directory.handle.createDirPathOpen(io, "h", .{}), }, .zig_exe = zig_exe, - .env_map = try process.getEnvMap(arena), + .env_map = try init.environ.createMap(arena), .global_cache_root = global_cache_directory, .zig_lib_directory = zig_lib_directory, .host = .{ @@ -126,13 +126,13 @@ pub fn main() !void { var debounce_interval_ms: u16 = 50; var webui_listen: ?Io.net.IpAddress = null; - if (try std.zig.EnvVar.ZIG_BUILD_ERROR_STYLE.get(arena)) |str| { + if (std.zig.EnvVar.ZIG_BUILD_ERROR_STYLE.get(&graph.env_map)) |str| { if (std.meta.stringToEnum(ErrorStyle, str)) |style| { error_style = style; } } - if (try std.zig.EnvVar.ZIG_BUILD_MULTILINE_ERRORS.get(arena)) |str| { + if (std.zig.EnvVar.ZIG_BUILD_MULTILINE_ERRORS.get(&graph.env_map)) |str| { if (std.meta.stringToEnum(MultilineErrors, str)) |style| { multiline_errors = style; } @@ -429,8 +429,8 @@ pub fn main() !void { } } - const NO_COLOR = std.zig.EnvVar.NO_COLOR.isSet(); - const CLICOLOR_FORCE = std.zig.EnvVar.CLICOLOR_FORCE.isSet(); + const NO_COLOR = std.zig.EnvVar.NO_COLOR.isSet(&graph.env_map); + const CLICOLOR_FORCE = std.zig.EnvVar.CLICOLOR_FORCE.isSet(&graph.env_map); graph.stderr_mode = switch (color) { .auto => try .detect(io, .stderr(), NO_COLOR, CLICOLOR_FORCE), diff --git a/lib/std/Build.zig b/lib/std/Build.zig index dc3489bba3104a312568a95eb15bac7d3eec3dd3..8867ac05df4a77b8cf5b88d7b13b0bd6440e6f30 100644 --- a/lib/std/Build.zig +++ b/lib/std/Build.zig @@ -12,7 +12,7 @@ const StringHashMap = std.StringHashMap; const Allocator = std.mem.Allocator; const Target = std.Target; const process = std.process; -const EnvMap = std.process.EnvMap; +const EnvMap = std.process.Environ.Map; const File = std.Io.File; const Sha256 = std.crypto.hash.sha2.Sha256; const ArrayList = std.ArrayList; @@ -1840,13 +1840,12 @@ pub fn runAllowFail( const io = b.graph.io; const max_output_size = 400 * 1024; - var child = std.process.Child.init(argv, b.allocator); + var child = std.process.Child.init(b.allocator, argv, .{ .map = &b.graph.env_map }); child.stdin_behavior = .Ignore; child.stdout_behavior = .Pipe; child.stderr_behavior = stderr_behavior; - child.env_map = &b.graph.env_map; - try Step.handleVerbose2(b, null, child.env_map, argv); + try Step.handleVerbose2(b, null, child.environ.map, argv); try child.spawn(io); var stdout_reader = child.stdout.?.readerStreaming(io, &.{}); @@ -1877,17 +1876,15 @@ pub fn runAllowFail( pub fn run(b: *Build, argv: []const []const u8) []u8 { if (!process.can_spawn) { std.debug.print("unable to spawn the following command: cannot spawn child process\n{s}\n", .{ - try Step.allocPrintCmd(b.allocator, null, argv), + try Step.allocPrintCmd(b.allocator, null, null, argv), }); process.exit(1); } var code: u8 = undefined; return b.runAllowFail(argv, &code, .Inherit) catch |err| { - const printed_cmd = Step.allocPrintCmd(b.allocator, null, argv) catch @panic("OOM"); - std.debug.print("unable to spawn the following command: {s}\n{s}\n", .{ - @errorName(err), printed_cmd, - }); + const printed_cmd = Step.allocPrintCmd(b.allocator, null, null, argv) catch @panic("OOM"); + std.debug.print("unable to spawn the following command: {t}\n{s}\n", .{ err, printed_cmd }); process.exit(1); }; } diff --git a/lib/std/Build/Step.zig b/lib/std/Build/Step.zig index 243dee8604da4eca431012923409fdc1b48e2303..5d6daf9a89b711113880fdc517adae3b529e3a9e 100644 --- a/lib/std/Build/Step.zig +++ b/lib/std/Build/Step.zig @@ -349,18 +349,20 @@ pub fn captureChildProcess( progress_node: std.Progress.Node, argv: []const []const u8, ) !std.process.Child.RunResult { - const arena = s.owner.allocator; - const io = s.owner.graph.io; + const graph = s.owner.graph; + const arena = graph.arena; + const io = graph.io; // If an error occurs, it's happened in this command: assert(s.result_failed_command == null); - s.result_failed_command = try allocPrintCmd(gpa, null, argv); + s.result_failed_command = try allocPrintCmd(gpa, null, null, argv); try handleChildProcUnsupported(s); try handleVerbose(s.owner, null, argv); const result = std.process.Child.run(arena, io, .{ .argv = argv, + .environ = .{ .map = &graph.env_map }, .progress_node = progress_node, }) catch |err| return s.fail("failed to run {s}: {t}", .{ argv[0], err }); @@ -406,7 +408,7 @@ pub fn evalZigProcess( // If an error occurs, it's happened in this command: assert(s.result_failed_command == null); - s.result_failed_command = try allocPrintCmd(gpa, null, argv); + s.result_failed_command = try allocPrintCmd(gpa, null, null, argv); if (s.getZigProcess()) |zp| update: { assert(watch); @@ -447,8 +449,7 @@ pub fn evalZigProcess( try handleChildProcUnsupported(s); try handleVerbose(s.owner, null, argv); - var child = std.process.Child.init(argv, arena); - child.env_map = &b.graph.env_map; + var child = std.process.Child.init(arena, argv, .{ .map = &b.graph.env_map }); child.stdin_behavior = .Pipe; child.stdout_behavior = .Pipe; child.stderr_behavior = .Pipe; @@ -692,13 +693,17 @@ pub fn handleVerbose( pub fn handleVerbose2( b: *Build, opt_cwd: ?[]const u8, - opt_env: ?*const std.process.EnvMap, + opt_env: ?*const std.process.Environ.Map, argv: []const []const u8, ) error{OutOfMemory}!void { if (b.verbose) { + const graph = b.graph; // Intention of verbose is to print all sub-process command lines to // stderr before spawning them. - const text = try allocPrintCmd2(b.allocator, opt_cwd, opt_env, argv); + const text = try allocPrintCmd(b.allocator, opt_cwd, if (opt_env) |env| .{ + .child = env, + .parent = &graph.env_map, + } else null, argv); std.debug.print("{s}\n", .{text}); } } @@ -728,15 +733,10 @@ pub fn handleChildProcessTerm(s: *Step, term: std.process.Child.Term) error{ Mak pub fn allocPrintCmd( gpa: Allocator, opt_cwd: ?[]const u8, - argv: []const []const u8, -) Allocator.Error![]u8 { - return allocPrintCmd2(gpa, opt_cwd, null, argv); -} - -pub fn allocPrintCmd2( - gpa: Allocator, - opt_cwd: ?[]const u8, - opt_env: ?*const std.process.EnvMap, + opt_env: ?struct { + child: *const std.process.Environ.Map, + parent: *const std.process.Environ.Map, + }, argv: []const []const u8, ) Allocator.Error![]u8 { const shell = struct { @@ -779,13 +779,11 @@ pub fn allocPrintCmd2( const writer = &aw.writer; if (opt_cwd) |cwd| writer.print("cd {s} && ", .{cwd}) catch return error.OutOfMemory; if (opt_env) |env| { - var process_env_map = std.process.getEnvMap(gpa) catch std.process.EnvMap.init(gpa); - defer process_env_map.deinit(); - var it = env.iterator(); + var it = env.child.iterator(); while (it.next()) |entry| { const key = entry.key_ptr.*; const value = entry.value_ptr.*; - if (process_env_map.get(key)) |process_value| { + if (env.parent.get(key)) |process_value| { if (std.mem.eql(u8, value, process_value)) continue; } writer.print("{s}=", .{key}) catch return error.OutOfMemory; diff --git a/lib/std/Build/Step/Options.zig b/lib/std/Build/Step/Options.zig index 610d417aeabf53cc1881e0d57563224f6b29cda2..8cfa7c1261d9a54c5d64dfb84c5a0452f82b7c6c 100644 --- a/lib/std/Build/Step/Options.zig +++ b/lib/std/Build/Step/Options.zig @@ -546,7 +546,7 @@ test Options { .manifest_dir = Io.Dir.cwd(), }, .zig_exe = "test", - .env_map = std.process.EnvMap.init(arena.allocator()), + .env_map = std.process.Environ.Map.init(arena.allocator()), .global_cache_root = .{ .path = "test", .handle = Io.Dir.cwd() }, .host = .{ .query = .{}, diff --git a/lib/std/Build/Step/Run.zig b/lib/std/Build/Step/Run.zig index 258fadd06ccc46c6bcadcf86fec737fe57da93b9..4a5386f8cdc9b8702bfac62dbe7320d2a46afc22 100644 --- a/lib/std/Build/Step/Run.zig +++ b/lib/std/Build/Step/Run.zig @@ -8,7 +8,7 @@ const Step = std.Build.Step; const Dir = std.Io.Dir; const mem = std.mem; const process = std.process; -const EnvMap = std.process.EnvMap; +const EnvMap = std.process.Environ.Map; const assert = std.debug.assert; const Path = std.Build.Cache.Path; @@ -581,23 +581,24 @@ pub fn getEnvMap(run: *Run) *EnvMap { } fn getEnvMapInternal(run: *Run) *EnvMap { - const arena = run.step.owner.allocator; + const graph = run.step.owner.graph; + const arena = graph.arena; return run.env_map orelse { - const env_map = arena.create(EnvMap) catch @panic("OOM"); - env_map.* = process.getEnvMap(arena) catch @panic("unhandled error"); - run.env_map = env_map; - return env_map; + const cloned_map = arena.create(EnvMap) catch @panic("OOM"); + cloned_map.* = graph.env_map.clone(arena) catch @panic("OOM"); + run.env_map = cloned_map; + return cloned_map; }; } pub fn setEnvironmentVariable(run: *Run, key: []const u8, value: []const u8) void { - const b = run.step.owner; const env_map = run.getEnvMap(); - env_map.put(b.dupe(key), b.dupe(value)) catch @panic("unhandled error"); + // This data structure already dupes keys and values. + env_map.put(key, value) catch @panic("OOM"); } pub fn removeEnvironmentVariable(run: *Run, key: []const u8) void { - run.getEnvMap().remove(key); + _ = run.getEnvMap().swapRemove(key); } /// Adds a check for exact stderr match. Does not add any other checks. @@ -1563,11 +1564,10 @@ fn spawnChildAndCollect( assert(run.stdio == .zig_test); } - var child = std.process.Child.init(argv, arena); + var child = std.process.Child.init(arena, argv, .{ .map = env_map }); if (run.cwd) |lazy_cwd| { child.cwd = lazy_cwd.getPath2(b, &run.step); } - child.env_map = env_map; child.request_resource_usage_statistics = true; child.stdin_behavior = switch (run.stdio) { @@ -1597,7 +1597,10 @@ fn spawnChildAndCollect( // If an error occurs, it's caused by this command: assert(run.step.result_failed_command == null); - run.step.result_failed_command = try Step.allocPrintCmd(options.gpa, child.cwd, argv); + run.step.result_failed_command = try Step.allocPrintCmd(options.gpa, child.cwd, .{ + .child = env_map, + .parent = &graph.env_map, + }, argv); if (run.stdio == .zig_test) { var timer = try std.time.Timer.start(); @@ -1627,11 +1630,11 @@ fn setColorEnvironmentVariables(run: *Run, env_map: *EnvMap, terminal_mode: Io.T .manual => {}, .enable => { try env_map.put("CLICOLOR_FORCE", "1"); - env_map.remove("NO_COLOR"); + _ = env_map.swapRemove("NO_COLOR"); }, .disable => { try env_map.put("NO_COLOR", "1"); - env_map.remove("CLICOLOR_FORCE"); + _ = env_map.swapRemove("CLICOLOR_FORCE"); }, .inherit => switch (terminal_mode) { .no_color, .windows_api => continue :color .disable, diff --git a/lib/std/Build/WebServer.zig b/lib/std/Build/WebServer.zig index a2b35e35229aa716799bfe46a1c53dc2b452f2c7..2c53e103cc161b5cb1d67c9724f027df8462267c 100644 --- a/lib/std/Build/WebServer.zig +++ b/lib/std/Build/WebServer.zig @@ -528,13 +528,13 @@ pub fn serveTarFile(ws: *WebServer, request: *http.Server.Request, paths: []cons } fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.OptimizeMode) !Cache.Path { - const io = ws.graph.io; const root_name = "build-web"; const arch_os_abi = "wasm32-freestanding"; const cpu_features = "baseline+atomics+bulk_memory+multivalue+mutable_globals+nontrapping_fptoint+reference_types+sign_ext"; const gpa = ws.gpa; const graph = ws.graph; + const io = graph.io; const main_src_path: Cache.Path = .{ .root_dir = graph.zig_lib_directory, @@ -572,7 +572,7 @@ fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.Optim "--listen=-", }); - var child: std.process.Child = .init(argv.items, gpa); + var child: std.process.Child = .init(gpa, argv.items, .{ .map = &graph.env_map }); child.stdin_behavior = .Pipe; child.stdout_behavior = .Pipe; child.stderr_behavior = .Pipe; @@ -640,7 +640,7 @@ fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.Optim if (code != 0) { log.err( "the following command exited with error code {d}:\n{s}", - .{ code, try Build.Step.allocPrintCmd(arena, null, argv.items) }, + .{ code, try Build.Step.allocPrintCmd(arena, null, null, argv.items) }, ); return error.WasmCompilationFailed; } @@ -648,7 +648,7 @@ fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.Optim .Signal, .Stopped, .Unknown => { log.err( "the following command terminated unexpectedly:\n{s}", - .{try Build.Step.allocPrintCmd(arena, null, argv.items)}, + .{try Build.Step.allocPrintCmd(arena, null, null, argv.items)}, ); return error.WasmCompilationFailed; }, @@ -658,14 +658,14 @@ fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.Optim try result_error_bundle.renderToStderr(io, .{}, .auto); log.err("the following command failed with {d} compilation errors:\n{s}", .{ result_error_bundle.errorMessageCount(), - try Build.Step.allocPrintCmd(arena, null, argv.items), + try Build.Step.allocPrintCmd(arena, null, null, argv.items), }); return error.WasmCompilationFailed; } const base_path = result orelse { log.err("child process failed to report result\n{s}", .{ - try Build.Step.allocPrintCmd(arena, null, argv.items), + try Build.Step.allocPrintCmd(arena, null, null, argv.items), }); return error.WasmCompilationFailed; }; diff --git a/lib/std/Io.zig b/lib/std/Io.zig index 21fb71286f8a26b15e3827e4f21f0d54e76b15d4..31dd500df76b5628bf10b5c6ce2b7f70b73d95bf 100644 --- a/lib/std/Io.zig +++ b/lib/std/Io.zig @@ -2232,3 +2232,9 @@ pub fn tryLockStderr(io: Io, buffer: []u8, terminal_mode: ?Terminal.Mode) Cancel pub fn unlockStderr(io: Io) void { return io.vtable.unlockStderr(io.userdata); } + +pub fn environ(io: Io, name: []const u8) ?[]const u8 { + _ = io; + _ = name; + if (true) @panic("TODO"); +} diff --git a/lib/std/Io/Dir.zig b/lib/std/Io/Dir.zig index 82bf3b927df0cd22a14f640e1b1f58aedc23c373..cc1dec8efe2281702ec35a5de5b44e7eff4e496e 100644 --- a/lib/std/Io/Dir.zig +++ b/lib/std/Io/Dir.zig @@ -82,13 +82,14 @@ pub const Entry = struct { /// /// On POSIX targets, this function is comptime-callable. /// -/// On WASI, the value this returns is application-configurable. +/// This function is overridable via `std.Options.cwd`. pub fn cwd() Dir { - return switch (native_os) { + const cwdFn = std.Options.cwd orelse return switch (native_os) { .windows => .{ .handle = std.os.windows.peb().ProcessParameters.CurrentDirectory.Handle }, - .wasi => .{ .handle = std.options.wasiCwd() }, + .wasi => .{ .handle = 3 }, // Expect the first preopen to be current working directory. else => .{ .handle = std.posix.AT.FDCWD }, }; + return cwdFn(); } pub const Reader = struct { diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index e16e70d6a715c799cb65661e471246421f4fdbc2..50e9f4cf150bb3715fffce73083c32e5b3a2dd6f 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -72,7 +72,7 @@ pub const Argv0 = switch (native_os) { pub const Environ = struct { /// Unmodified data directly from the OS. - block: Block = &.{}, + block: std.process.Environ.Block = &.{}, /// Protected by `mutex`. Determines whether the other fields have been /// memoized based on `block`. initialized: bool = false, @@ -89,8 +89,6 @@ pub const Environ = struct { pub const Error = Allocator.Error || Io.UnexpectedError; - pub const Block = []const [*:0]const u8; - pub const Exist = struct { NO_COLOR: bool = false, CLICOLOR_FORCE: bool = false, diff --git a/lib/std/Progress.zig b/lib/std/Progress.zig index f0baca2784f0ae912488d1ed4a0a1dad28bf1406..94fd6b47a053d217a234f11de66b6ab9ec8eec1b 100644 --- a/lib/std/Progress.zig +++ b/lib/std/Progress.zig @@ -476,7 +476,7 @@ pub fn start(io: Io, options: Options) Node { global_progress.io = io; - if (std.process.parseEnvVarInt("ZIG_PROGRESS", u31, 10)) |ipc_fd| { + if (std.process.Environ.parseInt(io, "ZIG_PROGRESS", u31, 10)) |ipc_fd| { global_progress.update_worker = io.concurrent(ipcThreadRun, .{ io, @as(Io.File, .{ .handle = switch (@typeInfo(Io.File.Handle)) { diff --git a/lib/std/debug.zig b/lib/std/debug.zig index d000bda62e99011ec9a5258411bf35aafbd6e0ee..0f151447fc56b4d7afdde22709b2bc0fe414d554 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -40,7 +40,7 @@ pub const cpu_context = @import("debug/cpu_context.zig"); /// pub fn deinit(si: *SelfInfo, gpa: Allocator) void; /// /// /// Returns the symbol and source location of the instruction at `address`. -/// pub fn getSymbol(si: *SelfInfo, gpa: Allocator, address: usize) SelfInfoError!Symbol; +/// pub fn getSymbol(si: *SelfInfo, gpa: Allocator, io: Io, address: usize) SelfInfoError!Symbol; /// /// Returns a name for the "module" (e.g. shared library or executable image) containing `address`. /// pub fn getModuleName(si: *SelfInfo, gpa: Allocator, address: usize) SelfInfoError![]const u8; /// diff --git a/lib/std/debug/ElfFile.zig b/lib/std/debug/ElfFile.zig index a101309d22c52f4d324fbb43452bef23cd0e958a..e17c518271f65c66eba95a2b414c791bef5cd14b 100644 --- a/lib/std/debug/ElfFile.zig +++ b/lib/std/debug/ElfFile.zig @@ -66,16 +66,16 @@ pub const DebugInfoSearchPaths = struct { .exe_dir = null, }; - pub fn native(exe_path: []const u8) DebugInfoSearchPaths { + pub fn native(exe_path: []const u8, io: Io) DebugInfoSearchPaths { return .{ .debuginfod_client = p: { - if (std.posix.getenv("DEBUGINFOD_CACHE_PATH")) |p| { + if (io.environ("DEBUGINFOD_CACHE_PATH")) |p| { break :p .{ p, "" }; } - if (std.posix.getenv("XDG_CACHE_HOME")) |cache_path| { + if (io.environ("XDG_CACHE_HOME")) |cache_path| { break :p .{ cache_path, "/debuginfod_client" }; } - if (std.posix.getenv("HOME")) |home_path| { + if (io.environ("HOME")) |home_path| { break :p .{ home_path, "/.cache/debuginfod_client" }; } break :p null; diff --git a/lib/std/debug/SelfInfo/Elf.zig b/lib/std/debug/SelfInfo/Elf.zig index be76f3a8c24fa92e256588359350d65767b031aa..c62c2df4b892d0773b52b7a72241acd0f38465e0 100644 --- a/lib/std/debug/SelfInfo/Elf.zig +++ b/lib/std/debug/SelfInfo/Elf.zig @@ -322,11 +322,12 @@ const Module = struct { if (mod.loaded_elf == null) mod.loaded_elf = loadElf(mod, gpa, io); return if (mod.loaded_elf.?) |*elf| elf else |err| err; } + fn loadElf(mod: *Module, gpa: Allocator, io: Io) Error!LoadedElf { const load_result = if (mod.name.len > 0) res: { var file = Io.Dir.cwd().openFile(io, mod.name, .{}) catch return error.MissingDebugInfo; defer file.close(io); - break :res std.debug.ElfFile.load(gpa, io, file, mod.build_id, &.native(mod.name)); + break :res std.debug.ElfFile.load(gpa, io, file, mod.build_id, &.native(mod.name, io)); } else res: { const path = std.process.executablePathAlloc(io, gpa) catch |err| switch (err) { error.OutOfMemory => |e| return e, @@ -335,7 +336,7 @@ const Module = struct { defer gpa.free(path); var file = Io.Dir.cwd().openFile(io, path, .{}) catch return error.MissingDebugInfo; defer file.close(io); - break :res std.debug.ElfFile.load(gpa, io, file, mod.build_id, &.native(path)); + break :res std.debug.ElfFile.load(gpa, io, file, mod.build_id, &.native(path, io)); }; var elf_file = load_result catch |err| switch (err) { diff --git a/lib/std/os.zig b/lib/std/os.zig index 667d743f3d89abdd6ec1cac7d9877bd319530db5..4f6643c3af33381ec38b6490600e4b64fe879109 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -1,27 +1,4 @@ -//! This file contains thin wrappers around OS-specific APIs, with these -//! specific goals in mind: -//! * Convert "errno"-style error codes into Zig errors. -//! * When null-terminated byte buffers are required, provide APIs which accept -//! slices as well as APIs which accept null-terminated byte buffers. Same goes -//! for WTF-16LE encoding. -//! * Where operating systems share APIs, e.g. POSIX, these thin wrappers provide -//! cross platform abstracting. -//! * When there exists a corresponding libc function and linking libc, the libc -//! implementation is used. Exceptions are made for known buggy areas of libc. -//! On Linux libc can be side-stepped by using `std.os.linux` directly. -//! * For Windows, this file represents the API that libc would provide for -//! Windows. For thin wrappers around Windows-specific APIs, see `std.os.windows`. - -const root = @import("root"); -const std = @import("std.zig"); const builtin = @import("builtin"); -const assert = std.debug.assert; -const math = std.math; -const mem = std.mem; -const elf = std.elf; -const fs = std.fs; -const dl = @import("dynamic_library.zig"); -const posix = std.posix; const native_os = builtin.os.tag; pub const linux = @import("os/linux.zig"); @@ -33,47 +10,7 @@ pub const windows = @import("os/windows.zig"); test { _ = linux; - if (native_os == .uefi) { - _ = uefi; - } + if (native_os == .uefi) _ = uefi; _ = wasi; _ = windows; } - -/// See also `getenv`. Populated by startup code before main(). -/// TODO this is a footgun because the value will be undefined when using `zig build-lib`. -/// https://github.com/ziglang/zig/issues/4524 -pub var environ: [][*:0]u8 = undefined; - -/// Populated by startup code before main(). -/// Not available on WASI or Windows without libc. See `std.process.argsAlloc` -/// or `std.process.argsWithAllocator` for a cross-platform alternative. -pub var argv: [][*:0]u8 = if (builtin.link_libc) undefined else switch (native_os) { - .windows => @compileError("argv isn't supported on Windows: use std.process.argsAlloc instead"), - .wasi => @compileError("argv isn't supported on WASI: use std.process.argsAlloc instead"), - else => undefined, -}; - -pub const FstatError = error{ - SystemResources, - AccessDenied, - Unexpected, -}; - -pub fn fstat_wasi(fd: posix.fd_t) FstatError!wasi.filestat_t { - var stat: wasi.filestat_t = undefined; - switch (wasi.fd_filestat_get(fd, &stat)) { - .SUCCESS => return stat, - .INVAL => unreachable, - .BADF => unreachable, // Always a race condition. - .NOMEM => return error.SystemResources, - .ACCES => return error.AccessDenied, - .NOTCAPABLE => return error.AccessDenied, - else => |err| return posix.unexpectedErrno(err), - } -} - -pub fn defaultWasiCwd() std.os.wasi.fd_t { - // Expect the first preopen to be current working directory. - return 3; -} diff --git a/lib/std/posix.zig b/lib/std/posix.zig index 8bcf7e82ee13d140d322d4667b8d743a40d39524..55b19df995e1db0930690c9be9767cebc17db5e6 100644 --- a/lib/std/posix.zig +++ b/lib/std/posix.zig @@ -857,127 +857,15 @@ pub fn execveZ( } } -pub const Arg0Expand = enum { - expand, - no_expand, -}; - -/// Like `execvpeZ` except if `arg0_expand` is `.expand`, then `argv` is mutable, -/// and `argv[0]` is expanded to be the same absolute path that is passed to the execve syscall. -/// If this function returns with an error, `argv[0]` will be restored to the value it was when it was passed in. -pub fn execvpeZ_expandArg0( - comptime arg0_expand: Arg0Expand, - file: [*:0]const u8, - child_argv: switch (arg0_expand) { - .expand => [*:null]?[*:0]const u8, - .no_expand => [*:null]const ?[*:0]const u8, - }, - envp: [*:null]const ?[*:0]const u8, -) ExecveError { - const file_slice = mem.sliceTo(file, 0); - if (mem.findScalar(u8, file_slice, '/') != null) return execveZ(file, child_argv, envp); - - const PATH = getenvZ("PATH") orelse "/usr/local/bin:/bin/:/usr/bin"; - // Use of PATH_MAX here is valid as the path_buf will be passed - // directly to the operating system in execveZ. - var path_buf: [PATH_MAX]u8 = undefined; - var it = mem.tokenizeScalar(u8, PATH, ':'); - var seen_eacces = false; - var err: ExecveError = error.FileNotFound; - - // In case of expanding arg0 we must put it back if we return with an error. - const prev_arg0 = child_argv[0]; - defer switch (arg0_expand) { - .expand => child_argv[0] = prev_arg0, - .no_expand => {}, - }; - - while (it.next()) |search_path| { - const path_len = search_path.len + file_slice.len + 1; - if (path_buf.len < path_len + 1) return error.NameTooLong; - @memcpy(path_buf[0..search_path.len], search_path); - path_buf[search_path.len] = '/'; - @memcpy(path_buf[search_path.len + 1 ..][0..file_slice.len], file_slice); - path_buf[path_len] = 0; - const full_path = path_buf[0..path_len :0].ptr; - switch (arg0_expand) { - .expand => child_argv[0] = full_path, - .no_expand => {}, - } - err = execveZ(full_path, child_argv, envp); - switch (err) { - error.AccessDenied => seen_eacces = true, - error.FileNotFound, error.NotDir => {}, - else => |e| return e, - } - } - if (seen_eacces) return error.AccessDenied; - return err; -} - /// This function also uses the PATH environment variable to get the full path to the executable. /// If `file` is an absolute path, this is the same as `execveZ`. pub fn execvpeZ( file: [*:0]const u8, argv_ptr: [*:null]const ?[*:0]const u8, envp: [*:null]const ?[*:0]const u8, + optional_PATH: ?[]const u8, ) ExecveError { - return execvpeZ_expandArg0(.no_expand, file, argv_ptr, envp); -} - -/// Get an environment variable. -/// See also `getenvZ`. -pub fn getenv(key: []const u8) ?[:0]const u8 { - if (native_os == .windows) { - @compileError("std.posix.getenv is unavailable for Windows because environment strings are in WTF-16 format. See std.process.getEnvVarOwned for a cross-platform API or std.process.getenvW for a Windows-specific API."); - } - if (mem.findScalar(u8, key, '=') != null) { - return null; - } - if (builtin.link_libc) { - var ptr = std.c.environ; - while (ptr[0]) |line| : (ptr += 1) { - var line_i: usize = 0; - while (line[line_i] != 0) : (line_i += 1) { - if (line_i == key.len) break; - if (line[line_i] != key[line_i]) break; - } - if ((line_i != key.len) or (line[line_i] != '=')) continue; - - return mem.sliceTo(line + line_i + 1, 0); - } - return null; - } - if (native_os == .wasi) { - @compileError("std.posix.getenv is unavailable for WASI. See std.process.getEnvMap or std.process.getEnvVarOwned for a cross-platform API."); - } - // The simplified start logic doesn't populate environ. - if (std.start.simplified_logic) return null; - // TODO see https://github.com/ziglang/zig/issues/4524 - for (std.os.environ) |ptr| { - var line_i: usize = 0; - while (ptr[line_i] != 0) : (line_i += 1) { - if (line_i == key.len) break; - if (ptr[line_i] != key[line_i]) break; - } - if ((line_i != key.len) or (ptr[line_i] != '=')) continue; - - return mem.sliceTo(ptr + line_i + 1, 0); - } - return null; -} - -/// Get an environment variable with a null-terminated name. -/// See also `getenv`. -pub fn getenvZ(key: [*:0]const u8) ?[:0]const u8 { - if (builtin.link_libc) { - const value = system.getenv(key) orelse return null; - return mem.sliceTo(value, 0); - } - if (native_os == .windows) { - @compileError("std.posix.getenvZ is unavailable for Windows because environment string is in WTF-16 format. See std.process.getEnvVarOwned for cross-platform API or std.process.getenvW for Windows-specific API."); - } - return getenv(mem.sliceTo(key, 0)); + return execvpeZ_expandArg0(.no_expand, file, argv_ptr, envp, optional_PATH); } pub const GetCwdError = error{ @@ -1945,7 +1833,7 @@ pub const FStatError = std.Io.File.StatError; /// Return information about a file descriptor. pub fn fstat(fd: fd_t) FStatError!Stat { if (native_os == .wasi and !builtin.link_libc) { - return Stat.fromFilestat(try std.os.fstat_wasi(fd)); + @compileError("unsupported OS"); } var stat = mem.zeroes(Stat); diff --git a/lib/std/process.zig b/lib/std/process.zig index 865376d907a71a9ff0ce2e1cb0ab1d91c4ce8121..4221356bddbc8a9d419966a374873721a9f6a36b 100644 --- a/lib/std/process.zig +++ b/lib/std/process.zig @@ -16,10 +16,8 @@ const unicode = std.unicode; const max_path_bytes = std.fs.max_path_bytes; pub const Child = @import("process/Child.zig"); -pub const changeCurDir = posix.chdir; -pub const changeCurDirZ = posix.chdirZ; - -pub const GetCwdError = posix.GetCwdError; +pub const Args = @import("process/Args.zig"); +pub const Environ = @import("process/Environ.zig"); /// This is the global, process-wide protection to coordinate stderr writes. /// @@ -28,6 +26,39 @@ pub const GetCwdError = posix.GetCwdError; /// information. pub var stderr_thread_mutex: std.Thread.Mutex.Recursive = .init; +/// A standard set of pre-initialized useful APIs for programs to take +/// advantage of. This is the type of the first parameter of the main function. +/// Applications wanting more flexibility can accept `Init.Minimal` instead. +/// +/// Completion of https://github.com/ziglang/zig/issues/24510 will also allow +/// the second parameter of the main function to be a custom struct that +/// contain auto-parsed CLI arguments. +pub const Init = struct { + /// `Init` is a superset of `Minimal`; the latter is included here. + minimal: Minimal, + /// Permanent storage for the entire process, cleaned automatically on + /// exit. Not threadsafe. + arena: *std.heap.ArenaAllocator, + /// A default-selected general purpose allocator for temporary heap + /// allocations. Debug mode will set up leak checking. Threadsafe. + gpa: Allocator, + /// An appropriate default Io implementation based on the target + /// configuration. Debug mode will set up leak checking. + io: Io, + /// Environment variables, initialized with `gpa`. Not threadsafe. + env_map: *Environ.Map, + + /// Alternative to `Init` as the first parameter of the main function. + pub const Minimal = struct { + /// Environment variables. + environ: Environ, + /// Command line arguments. + args: Args, + }; +}; + +pub const GetCwdError = posix.GetCwdError; + /// The result is a slice of `out_buffer`, from index `0`. /// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/). /// On other platforms, the result is an opaque sequence of bytes with no particular encoding. @@ -73,1484 +104,6 @@ test getCwdAlloc { testing.allocator.free(cwd); } -pub const EnvMap = struct { - hash_map: HashMap, - - const HashMap = std.HashMap( - []const u8, - []const u8, - EnvNameHashContext, - std.hash_map.default_max_load_percentage, - ); - - pub const Size = HashMap.Size; - - pub const EnvNameHashContext = struct { - fn upcase(c: u21) u21 { - if (c <= std.math.maxInt(u16)) - return windows.ntdll.RtlUpcaseUnicodeChar(@as(u16, @intCast(c))); - return c; - } - - pub fn hash(self: @This(), s: []const u8) u64 { - _ = self; - if (native_os == .windows) { - var h = std.hash.Wyhash.init(0); - var it = unicode.Wtf8View.initUnchecked(s).iterator(); - while (it.nextCodepoint()) |cp| { - const cp_upper = upcase(cp); - h.update(&[_]u8{ - @as(u8, @intCast((cp_upper >> 16) & 0xff)), - @as(u8, @intCast((cp_upper >> 8) & 0xff)), - @as(u8, @intCast((cp_upper >> 0) & 0xff)), - }); - } - return h.final(); - } - return std.hash_map.hashString(s); - } - - pub fn eql(self: @This(), a: []const u8, b: []const u8) bool { - _ = self; - if (native_os == .windows) { - var it_a = unicode.Wtf8View.initUnchecked(a).iterator(); - var it_b = unicode.Wtf8View.initUnchecked(b).iterator(); - while (true) { - const c_a = it_a.nextCodepoint() orelse break; - const c_b = it_b.nextCodepoint() orelse return false; - if (upcase(c_a) != upcase(c_b)) - return false; - } - return if (it_b.nextCodepoint()) |_| false else true; - } - return std.hash_map.eqlString(a, b); - } - }; - - /// Create a EnvMap backed by a specific allocator. - /// That allocator will be used for both backing allocations - /// and string deduplication. - pub fn init(allocator: Allocator) EnvMap { - return EnvMap{ .hash_map = HashMap.init(allocator) }; - } - - /// Free the backing storage of the map, as well as all - /// of the stored keys and values. - pub fn deinit(self: *EnvMap) void { - var it = self.hash_map.iterator(); - while (it.next()) |entry| { - self.free(entry.key_ptr.*); - self.free(entry.value_ptr.*); - } - - self.hash_map.deinit(); - } - - /// Same as `put` but the key and value become owned by the EnvMap rather - /// than being copied. - /// If `putMove` fails, the ownership of key and value does not transfer. - /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string. - pub fn putMove(self: *EnvMap, key: []u8, value: []u8) !void { - assert(unicode.wtf8ValidateSlice(key)); - const get_or_put = try self.hash_map.getOrPut(key); - if (get_or_put.found_existing) { - self.free(get_or_put.key_ptr.*); - self.free(get_or_put.value_ptr.*); - get_or_put.key_ptr.* = key; - } - get_or_put.value_ptr.* = value; - } - - /// `key` and `value` are copied into the EnvMap. - /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string. - pub fn put(self: *EnvMap, key: []const u8, value: []const u8) !void { - assert(unicode.wtf8ValidateSlice(key)); - const value_copy = try self.copy(value); - errdefer self.free(value_copy); - const get_or_put = try self.hash_map.getOrPut(key); - if (get_or_put.found_existing) { - self.free(get_or_put.value_ptr.*); - } else { - get_or_put.key_ptr.* = self.copy(key) catch |err| { - _ = self.hash_map.remove(key); - return err; - }; - } - get_or_put.value_ptr.* = value_copy; - } - - /// Find the address of the value associated with a key. - /// The returned pointer is invalidated if the map resizes. - /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string. - pub fn getPtr(self: EnvMap, key: []const u8) ?*[]const u8 { - assert(unicode.wtf8ValidateSlice(key)); - return self.hash_map.getPtr(key); - } - - /// Return the map's copy of the value associated with - /// a key. The returned string is invalidated if this - /// key is removed from the map. - /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string. - pub fn get(self: EnvMap, key: []const u8) ?[]const u8 { - assert(unicode.wtf8ValidateSlice(key)); - return self.hash_map.get(key); - } - - /// Removes the item from the map and frees its value. - /// This invalidates the value returned by get() for this key. - /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string. - pub fn remove(self: *EnvMap, key: []const u8) void { - assert(unicode.wtf8ValidateSlice(key)); - const kv = self.hash_map.fetchRemove(key) orelse return; - self.free(kv.key); - self.free(kv.value); - } - - /// Returns the number of KV pairs stored in the map. - pub fn count(self: EnvMap) HashMap.Size { - return self.hash_map.count(); - } - - /// Returns an iterator over entries in the map. - pub fn iterator(self: *const EnvMap) HashMap.Iterator { - return self.hash_map.iterator(); - } - - /// Returns a full copy of `em` allocated with `gpa`, which is not necessarily - /// the same allocator used to allocate `em`. - pub fn clone(em: *const EnvMap, gpa: Allocator) Allocator.Error!EnvMap { - var new: EnvMap = .init(gpa); - errdefer new.deinit(); - // Since we need to dupe the keys and values, the only way for error handling to not be a - // nightmare is to add keys to an empty map one-by-one. This could be avoided if this - // abstraction were a bit less... OOP-esque. - try new.hash_map.ensureUnusedCapacity(em.hash_map.count()); - var it = em.hash_map.iterator(); - while (it.next()) |entry| { - try new.put(entry.key_ptr.*, entry.value_ptr.*); - } - return new; - } - - fn free(self: EnvMap, value: []const u8) void { - self.hash_map.allocator.free(value); - } - - fn copy(self: EnvMap, value: []const u8) ![]u8 { - return self.hash_map.allocator.dupe(u8, value); - } -}; - -test EnvMap { - var env = EnvMap.init(testing.allocator); - defer env.deinit(); - - try env.put("SOMETHING_NEW", "hello"); - try testing.expectEqualStrings("hello", env.get("SOMETHING_NEW").?); - try testing.expectEqual(@as(EnvMap.Size, 1), env.count()); - - // overwrite - try env.put("SOMETHING_NEW", "something"); - try testing.expectEqualStrings("something", env.get("SOMETHING_NEW").?); - try testing.expectEqual(@as(EnvMap.Size, 1), env.count()); - - // a new longer name to test the Windows-specific conversion buffer - try env.put("SOMETHING_NEW_AND_LONGER", "1"); - try testing.expectEqualStrings("1", env.get("SOMETHING_NEW_AND_LONGER").?); - try testing.expectEqual(@as(EnvMap.Size, 2), env.count()); - - // case insensitivity on Windows only - if (native_os == .windows) { - try testing.expectEqualStrings("1", env.get("something_New_aNd_LONGER").?); - } else { - try testing.expect(null == env.get("something_New_aNd_LONGER")); - } - - var it = env.iterator(); - var count: EnvMap.Size = 0; - while (it.next()) |entry| { - const is_an_expected_name = std.mem.eql(u8, "SOMETHING_NEW", entry.key_ptr.*) or std.mem.eql(u8, "SOMETHING_NEW_AND_LONGER", entry.key_ptr.*); - try testing.expect(is_an_expected_name); - count += 1; - } - try testing.expectEqual(@as(EnvMap.Size, 2), count); - - env.remove("SOMETHING_NEW"); - try testing.expect(env.get("SOMETHING_NEW") == null); - - try testing.expectEqual(@as(EnvMap.Size, 1), env.count()); - - if (native_os == .windows) { - // test Unicode case-insensitivity on Windows - try env.put("КИРиллИЦА", "something else"); - try testing.expectEqualStrings("something else", env.get("кириллица").?); - - // and WTF-8 that's not valid UTF-8 - const wtf8_with_surrogate_pair = try unicode.wtf16LeToWtf8Alloc(testing.allocator, &[_]u16{ - std.mem.nativeToLittle(u16, 0xD83D), // unpaired high surrogate - }); - defer testing.allocator.free(wtf8_with_surrogate_pair); - - try env.put(wtf8_with_surrogate_pair, wtf8_with_surrogate_pair); - try testing.expectEqualSlices(u8, wtf8_with_surrogate_pair, env.get(wtf8_with_surrogate_pair).?); - } -} - -pub const GetEnvMapError = error{ - OutOfMemory, - /// WASI-only. `environ_sizes_get` or `environ_get` - /// failed for an unexpected reason. - Unexpected, -}; - -/// Returns a snapshot of the environment variables of the current process. -/// Any modifications to the resulting EnvMap will not be reflected in the environment, and -/// likewise, any future modifications to the environment will not be reflected in the EnvMap. -/// Caller owns resulting `EnvMap` and should call its `deinit` fn when done. -pub fn getEnvMap(allocator: Allocator) GetEnvMapError!EnvMap { - var result = EnvMap.init(allocator); - errdefer result.deinit(); - - if (native_os == .windows) { - const ptr = windows.peb().ProcessParameters.Environment; - - var i: usize = 0; - while (ptr[i] != 0) { - const key_start = i; - - // There are some special environment variables that start with =, - // so we need a special case to not treat = as a key/value separator - // if it's the first character. - // https://devblogs.microsoft.com/oldnewthing/20100506-00/?p=14133 - if (ptr[key_start] == '=') i += 1; - - while (ptr[i] != 0 and ptr[i] != '=') : (i += 1) {} - const key_w = ptr[key_start..i]; - const key = try unicode.wtf16LeToWtf8Alloc(allocator, key_w); - errdefer allocator.free(key); - - if (ptr[i] == '=') i += 1; - - const value_start = i; - while (ptr[i] != 0) : (i += 1) {} - const value_w = ptr[value_start..i]; - const value = try unicode.wtf16LeToWtf8Alloc(allocator, value_w); - errdefer allocator.free(value); - - i += 1; // skip over null byte - - try result.putMove(key, value); - } - return result; - } else if (native_os == .wasi and !builtin.link_libc) { - var environ_count: usize = undefined; - var environ_buf_size: usize = undefined; - - const environ_sizes_get_ret = std.os.wasi.environ_sizes_get(&environ_count, &environ_buf_size); - if (environ_sizes_get_ret != .SUCCESS) { - return posix.unexpectedErrno(environ_sizes_get_ret); - } - - if (environ_count == 0) { - return result; - } - - const environ = try allocator.alloc([*:0]u8, environ_count); - defer allocator.free(environ); - const environ_buf = try allocator.alloc(u8, environ_buf_size); - defer allocator.free(environ_buf); - - const environ_get_ret = std.os.wasi.environ_get(environ.ptr, environ_buf.ptr); - if (environ_get_ret != .SUCCESS) { - return posix.unexpectedErrno(environ_get_ret); - } - - for (environ) |env| { - const pair = mem.sliceTo(env, 0); - var parts = mem.splitScalar(u8, pair, '='); - const key = parts.first(); - const value = parts.rest(); - try result.put(key, value); - } - return result; - } else if (builtin.link_libc) { - var ptr = std.c.environ; - while (ptr[0]) |line| : (ptr += 1) { - var line_i: usize = 0; - while (line[line_i] != 0 and line[line_i] != '=') : (line_i += 1) {} - const key = line[0..line_i]; - - var end_i: usize = line_i; - while (line[end_i] != 0) : (end_i += 1) {} - const value = line[line_i + 1 .. end_i]; - - try result.put(key, value); - } - return result; - } else { - for (std.os.environ) |line| { - var line_i: usize = 0; - while (line[line_i] != 0 and line[line_i] != '=') : (line_i += 1) {} - const key = line[0..line_i]; - - var end_i: usize = line_i; - while (line[end_i] != 0) : (end_i += 1) {} - const value = line[line_i + 1 .. end_i]; - - try result.put(key, value); - } - return result; - } -} - -test getEnvMap { - var env = try getEnvMap(testing.allocator); - defer env.deinit(); -} - -pub const GetEnvVarOwnedError = error{ - OutOfMemory, - EnvironmentVariableNotFound, - - /// On Windows, environment variable keys provided by the user must be valid WTF-8. - /// https://wtf-8.codeberg.page/ - InvalidWtf8, -}; - -/// Caller must free returned memory. -/// On Windows, if `key` is not valid [WTF-8](https://wtf-8.codeberg.page/), -/// then `error.InvalidWtf8` is returned. -/// On Windows, the value is encoded as [WTF-8](https://wtf-8.codeberg.page/). -/// On other platforms, the value is an opaque sequence of bytes with no particular encoding. -pub fn getEnvVarOwned(allocator: Allocator, key: []const u8) GetEnvVarOwnedError![]u8 { - if (native_os == .windows) { - const result_w = blk: { - var stack_alloc = std.heap.stackFallback(256 * @sizeOf(u16), allocator); - const stack_allocator = stack_alloc.get(); - const key_w = try unicode.wtf8ToWtf16LeAllocZ(stack_allocator, key); - defer stack_allocator.free(key_w); - - break :blk getenvW(key_w) orelse return error.EnvironmentVariableNotFound; - }; - // wtf16LeToWtf8Alloc can only fail with OutOfMemory - return unicode.wtf16LeToWtf8Alloc(allocator, result_w); - } else if (native_os == .wasi and !builtin.link_libc) { - var envmap = getEnvMap(allocator) catch return error.OutOfMemory; - defer envmap.deinit(); - const val = envmap.get(key) orelse return error.EnvironmentVariableNotFound; - return allocator.dupe(u8, val); - } else { - const result = posix.getenv(key) orelse return error.EnvironmentVariableNotFound; - return allocator.dupe(u8, result); - } -} - -/// On Windows, `key` must be valid WTF-8. -pub inline fn hasEnvVarConstant(comptime key: []const u8) bool { - if (native_os == .windows) { - const key_w = comptime unicode.wtf8ToWtf16LeStringLiteral(key); - return getenvW(key_w) != null; - } else if (native_os == .wasi and !builtin.link_libc) { - return false; - } else { - return posix.getenv(key) != null; - } -} - -/// On Windows, `key` must be valid WTF-8. -pub inline fn hasNonEmptyEnvVarConstant(comptime key: []const u8) bool { - if (native_os == .windows) { - const key_w = comptime unicode.wtf8ToWtf16LeStringLiteral(key); - const value = getenvW(key_w) orelse return false; - return value.len != 0; - } else if (native_os == .wasi and !builtin.link_libc) { - return false; - } else { - const value = posix.getenv(key) orelse return false; - return value.len != 0; - } -} - -pub const ParseEnvVarIntError = std.fmt.ParseIntError || error{EnvironmentVariableNotFound}; - -/// Parses an environment variable as an integer. -/// -/// Since the key is comptime-known, no allocation is needed. -/// -/// On Windows, `key` must be valid WTF-8. -pub fn parseEnvVarInt(comptime key: []const u8, comptime I: type, base: u8) ParseEnvVarIntError!I { - if (native_os == .windows) { - const key_w = comptime std.unicode.wtf8ToWtf16LeStringLiteral(key); - const text = getenvW(key_w) orelse return error.EnvironmentVariableNotFound; - return std.fmt.parseIntWithGenericCharacter(I, u16, text, base); - } else if (native_os == .wasi and !builtin.link_libc) { - @compileError("parseEnvVarInt is not supported for WASI without libc"); - } else { - const text = posix.getenv(key) orelse return error.EnvironmentVariableNotFound; - return std.fmt.parseInt(I, text, base); - } -} - -pub const HasEnvVarError = error{ - OutOfMemory, - - /// On Windows, environment variable keys provided by the user must be valid WTF-8. - /// https://wtf-8.codeberg.page/ - InvalidWtf8, -}; - -/// On Windows, if `key` is not valid [WTF-8](https://wtf-8.codeberg.page/), -/// then `error.InvalidWtf8` is returned. -pub fn hasEnvVar(allocator: Allocator, key: []const u8) HasEnvVarError!bool { - if (native_os == .windows) { - var stack_alloc = std.heap.stackFallback(256 * @sizeOf(u16), allocator); - const stack_allocator = stack_alloc.get(); - const key_w = try unicode.wtf8ToWtf16LeAllocZ(stack_allocator, key); - defer stack_allocator.free(key_w); - return getenvW(key_w) != null; - } else if (native_os == .wasi and !builtin.link_libc) { - var envmap = getEnvMap(allocator) catch return error.OutOfMemory; - defer envmap.deinit(); - return envmap.getPtr(key) != null; - } else { - return posix.getenv(key) != null; - } -} - -/// On Windows, if `key` is not valid [WTF-8](https://wtf-8.codeberg.page/), -/// then `error.InvalidWtf8` is returned. -pub fn hasNonEmptyEnvVar(allocator: Allocator, key: []const u8) HasEnvVarError!bool { - if (native_os == .windows) { - var stack_alloc = std.heap.stackFallback(256 * @sizeOf(u16), allocator); - const stack_allocator = stack_alloc.get(); - const key_w = try unicode.wtf8ToWtf16LeAllocZ(stack_allocator, key); - defer stack_allocator.free(key_w); - const value = getenvW(key_w) orelse return false; - return value.len != 0; - } else if (native_os == .wasi and !builtin.link_libc) { - var envmap = getEnvMap(allocator) catch return error.OutOfMemory; - defer envmap.deinit(); - const value = envmap.getPtr(key) orelse return false; - return value.len != 0; - } else { - const value = posix.getenv(key) orelse return false; - return value.len != 0; - } -} - -/// Windows-only. Get an environment variable with a null-terminated, WTF-16 encoded name. -/// The returned slice points to memory in the PEB. -/// -/// This function performs a Unicode-aware case-insensitive lookup using RtlEqualUnicodeString. -/// -/// See also: -/// * `std.posix.getenv` -/// * `getEnvMap` -/// * `getEnvVarOwned` -/// * `hasEnvVarConstant` -/// * `hasEnvVar` -pub fn getenvW(key: [*:0]const u16) ?[:0]const u16 { - if (native_os != .windows) { - @compileError("Windows-only"); - } - const key_slice = mem.sliceTo(key, 0); - // '=' anywhere but the start makes this an invalid environment variable name - if (key_slice.len > 0 and std.mem.findScalar(u16, key_slice[1..], '=') != null) { - return null; - } - const ptr = windows.peb().ProcessParameters.Environment; - var i: usize = 0; - while (ptr[i] != 0) { - const key_value = mem.sliceTo(ptr[i..], 0); - - // There are some special environment variables that start with =, - // so we need a special case to not treat = as a key/value separator - // if it's the first character. - // https://devblogs.microsoft.com/oldnewthing/20100506-00/?p=14133 - const equal_search_start: usize = if (key_value[0] == '=') 1 else 0; - const equal_index = std.mem.findScalarPos(u16, key_value, equal_search_start, '=') orelse { - // This is enforced by CreateProcess. - // If violated, CreateProcess will fail with INVALID_PARAMETER. - unreachable; // must contain a = - }; - - const this_key = key_value[0..equal_index]; - if (windows.eqlIgnoreCaseWtf16(key_slice, this_key)) { - return key_value[equal_index + 1 ..]; - } - - // skip past the NUL terminator - i += key_value.len + 1; - } - return null; -} - -test getEnvVarOwned { - try testing.expectError( - error.EnvironmentVariableNotFound, - getEnvVarOwned(std.testing.allocator, "BADENV"), - ); -} - -test hasEnvVarConstant { - if (native_os == .wasi and !builtin.link_libc) return error.SkipZigTest; - - try testing.expect(!hasEnvVarConstant("BADENV")); -} - -test hasEnvVar { - const has_env = try hasEnvVar(std.testing.allocator, "BADENV"); - try testing.expect(!has_env); -} - -pub const ArgIteratorPosix = struct { - index: usize, - count: usize, - - pub const InitError = error{}; - - pub fn init() ArgIteratorPosix { - return ArgIteratorPosix{ - .index = 0, - .count = std.os.argv.len, - }; - } - - pub fn next(self: *ArgIteratorPosix) ?[:0]const u8 { - if (self.index == self.count) return null; - - const s = std.os.argv[self.index]; - self.index += 1; - return mem.sliceTo(s, 0); - } - - pub fn skip(self: *ArgIteratorPosix) bool { - if (self.index == self.count) return false; - - self.index += 1; - return true; - } -}; - -pub const ArgIteratorWasi = struct { - allocator: Allocator, - index: usize, - args: [][:0]u8, - - pub const InitError = error{OutOfMemory} || posix.UnexpectedError; - - /// You must call deinit to free the internal buffer of the - /// iterator after you are done. - pub fn init(allocator: Allocator) InitError!ArgIteratorWasi { - const fetched_args = try ArgIteratorWasi.internalInit(allocator); - return ArgIteratorWasi{ - .allocator = allocator, - .index = 0, - .args = fetched_args, - }; - } - - fn internalInit(allocator: Allocator) InitError![][:0]u8 { - var count: usize = undefined; - var buf_size: usize = undefined; - - switch (std.os.wasi.args_sizes_get(&count, &buf_size)) { - .SUCCESS => {}, - else => |err| return posix.unexpectedErrno(err), - } - - if (count == 0) { - return &[_][:0]u8{}; - } - - const argv = try allocator.alloc([*:0]u8, count); - defer allocator.free(argv); - - const argv_buf = try allocator.alloc(u8, buf_size); - - switch (std.os.wasi.args_get(argv.ptr, argv_buf.ptr)) { - .SUCCESS => {}, - else => |err| return posix.unexpectedErrno(err), - } - - var result_args = try allocator.alloc([:0]u8, count); - var i: usize = 0; - while (i < count) : (i += 1) { - result_args[i] = mem.sliceTo(argv[i], 0); - } - - return result_args; - } - - pub fn next(self: *ArgIteratorWasi) ?[:0]const u8 { - if (self.index == self.args.len) return null; - - const arg = self.args[self.index]; - self.index += 1; - return arg; - } - - pub fn skip(self: *ArgIteratorWasi) bool { - if (self.index == self.args.len) return false; - - self.index += 1; - return true; - } - - /// Call to free the internal buffer of the iterator. - pub fn deinit(self: *ArgIteratorWasi) void { - // Nothing is allocated when there are no args - if (self.args.len == 0) return; - - const last_item = self.args[self.args.len - 1]; - const last_byte_addr = @intFromPtr(last_item.ptr) + last_item.len + 1; // null terminated - const first_item_ptr = self.args[0].ptr; - const len = last_byte_addr - @intFromPtr(first_item_ptr); - self.allocator.free(first_item_ptr[0..len]); - self.allocator.free(self.args); - } -}; - -/// Iterator that implements the Windows command-line parsing algorithm. -/// The implementation is intended to be compatible with the post-2008 C runtime, -/// but is *not* intended to be compatible with `CommandLineToArgvW` since -/// `CommandLineToArgvW` uses the pre-2008 parsing rules. -/// -/// This iterator faithfully implements the parsing behavior observed from the C runtime with -/// one exception: if the command-line string is empty, the iterator will immediately complete -/// without returning any arguments (whereas the C runtime will return a single argument -/// representing the name of the current executable). -/// -/// The essential parts of the algorithm are described in Microsoft's documentation: -/// -/// - https://learn.microsoft.com/en-us/cpp/cpp/main-function-command-line-args?view=msvc-170#parsing-c-command-line-arguments -/// -/// David Deley explains some additional undocumented quirks in great detail: -/// -/// - https://daviddeley.com/autohotkey/parameters/parameters.htm#WINCRULES -pub const ArgIteratorWindows = struct { - allocator: Allocator, - /// Encoded as WTF-16 LE. - cmd_line: []const u16, - index: usize = 0, - /// Owned by the iterator. Long enough to hold contiguous NUL-terminated slices - /// of each argument encoded as WTF-8. - buffer: []u8, - start: usize = 0, - end: usize = 0, - - pub const InitError = error{OutOfMemory}; - - /// `cmd_line_w` *must* be a WTF16-LE-encoded string. - /// - /// The iterator stores and uses `cmd_line_w`, so its memory must be valid for - /// at least as long as the returned ArgIteratorWindows. - pub fn init(allocator: Allocator, cmd_line_w: []const u16) InitError!ArgIteratorWindows { - const wtf8_len = unicode.calcWtf8Len(cmd_line_w); - - // This buffer must be large enough to contain contiguous NUL-terminated slices - // of each argument. - // - During parsing, the length of a parsed argument will always be equal to - // to less than its unparsed length - // - The first argument needs one extra byte of space allocated for its NUL - // terminator, but for each subsequent argument the necessary whitespace - // between arguments guarantees room for their NUL terminator(s). - const buffer = try allocator.alloc(u8, wtf8_len + 1); - errdefer allocator.free(buffer); - - return .{ - .allocator = allocator, - .cmd_line = cmd_line_w, - .buffer = buffer, - }; - } - - /// Returns the next argument and advances the iterator. Returns `null` if at the end of the - /// command-line string. The iterator owns the returned slice. - /// The result is encoded as [WTF-8](https://wtf-8.codeberg.page/). - pub fn next(self: *ArgIteratorWindows) ?[:0]const u8 { - return self.nextWithStrategy(next_strategy); - } - - /// Skips the next argument and advances the iterator. Returns `true` if an argument was - /// skipped, `false` if at the end of the command-line string. - pub fn skip(self: *ArgIteratorWindows) bool { - return self.nextWithStrategy(skip_strategy); - } - - const next_strategy = struct { - const T = ?[:0]const u8; - - const eof = null; - - /// Returns '\' if any backslashes are emitted, otherwise returns `last_emitted_code_unit`. - fn emitBackslashes(self: *ArgIteratorWindows, count: usize, last_emitted_code_unit: ?u16) ?u16 { - for (0..count) |_| { - self.buffer[self.end] = '\\'; - self.end += 1; - } - return if (count != 0) '\\' else last_emitted_code_unit; - } - - /// If `last_emitted_code_unit` and `code_unit` form a surrogate pair, then - /// the previously emitted high surrogate is overwritten by the codepoint encoded - /// by the surrogate pair, and `null` is returned. - /// Otherwise, `code_unit` is emitted and returned. - fn emitCharacter(self: *ArgIteratorWindows, code_unit: u16, last_emitted_code_unit: ?u16) ?u16 { - // Because we are emitting WTF-8, we need to - // check to see if we've emitted two consecutive surrogate - // codepoints that form a valid surrogate pair in order - // to ensure that we're always emitting well-formed WTF-8 - // (https://wtf-8.codeberg.page/#concatenating). - // - // If we do have a valid surrogate pair, we need to emit - // the UTF-8 sequence for the codepoint that they encode - // instead of the WTF-8 encoding for the two surrogate pairs - // separately. - // - // This is relevant when dealing with a WTF-16 encoded - // command line like this: - // "<0xD801>"<0xDC37> - // which would get parsed and converted to WTF-8 as: - // <0xED><0xA0><0x81><0xED><0xB0><0xB7> - // but instead, we need to recognize the surrogate pair - // and emit the codepoint it encodes, which in this - // example is U+10437 (𐐷), which is encoded in UTF-8 as: - // <0xF0><0x90><0x90><0xB7> - if (last_emitted_code_unit != null and - std.unicode.utf16IsLowSurrogate(code_unit) and - std.unicode.utf16IsHighSurrogate(last_emitted_code_unit.?)) - { - const codepoint = std.unicode.utf16DecodeSurrogatePair(&.{ last_emitted_code_unit.?, code_unit }) catch unreachable; - - // Unpaired surrogate is 3 bytes long - const dest = self.buffer[self.end - 3 ..]; - const len = unicode.utf8Encode(codepoint, dest) catch unreachable; - // All codepoints that require a surrogate pair (> U+FFFF) are encoded as 4 bytes - assert(len == 4); - self.end += 1; - return null; - } - - const wtf8_len = std.unicode.wtf8Encode(code_unit, self.buffer[self.end..]) catch unreachable; - self.end += wtf8_len; - return code_unit; - } - - fn yieldArg(self: *ArgIteratorWindows) [:0]const u8 { - self.buffer[self.end] = 0; - const arg = self.buffer[self.start..self.end :0]; - self.end += 1; - self.start = self.end; - return arg; - } - }; - - const skip_strategy = struct { - const T = bool; - - const eof = false; - - fn emitBackslashes(_: *ArgIteratorWindows, _: usize, last_emitted_code_unit: ?u16) ?u16 { - return last_emitted_code_unit; - } - - fn emitCharacter(_: *ArgIteratorWindows, _: u16, last_emitted_code_unit: ?u16) ?u16 { - return last_emitted_code_unit; - } - - fn yieldArg(_: *ArgIteratorWindows) bool { - return true; - } - }; - - fn nextWithStrategy(self: *ArgIteratorWindows, comptime strategy: type) strategy.T { - var last_emitted_code_unit: ?u16 = null; - // The first argument (the executable name) uses different parsing rules. - if (self.index == 0) { - if (self.cmd_line.len == 0 or self.cmd_line[0] == 0) { - // Immediately complete the iterator. - // The C runtime would return the name of the current executable here. - return strategy.eof; - } - - var inside_quotes = false; - while (true) : (self.index += 1) { - const char = if (self.index != self.cmd_line.len) - mem.littleToNative(u16, self.cmd_line[self.index]) - else - 0; - switch (char) { - 0 => { - return strategy.yieldArg(self); - }, - '"' => { - inside_quotes = !inside_quotes; - }, - ' ', '\t' => { - if (inside_quotes) { - last_emitted_code_unit = strategy.emitCharacter(self, char, last_emitted_code_unit); - } else { - self.index += 1; - return strategy.yieldArg(self); - } - }, - else => { - last_emitted_code_unit = strategy.emitCharacter(self, char, last_emitted_code_unit); - }, - } - } - } - - // Skip spaces and tabs. The iterator completes if we reach the end of the string here. - while (true) : (self.index += 1) { - const char = if (self.index != self.cmd_line.len) - mem.littleToNative(u16, self.cmd_line[self.index]) - else - 0; - switch (char) { - 0 => return strategy.eof, - ' ', '\t' => continue, - else => break, - } - } - - // Parsing rules for subsequent arguments: - // - // - The end of the string always terminates the current argument. - // - When not in 'inside_quotes' mode, a space or tab terminates the current argument. - // - 2n backslashes followed by a quote emit n backslashes (note: n can be zero). - // If in 'inside_quotes' and the quote is immediately followed by a second quote, - // one quote is emitted and the other is skipped, otherwise, the quote is skipped - // and 'inside_quotes' is toggled. - // - 2n + 1 backslashes followed by a quote emit n backslashes followed by a quote. - // - n backslashes not followed by a quote emit n backslashes. - var backslash_count: usize = 0; - var inside_quotes = false; - while (true) : (self.index += 1) { - const char = if (self.index != self.cmd_line.len) - mem.littleToNative(u16, self.cmd_line[self.index]) - else - 0; - switch (char) { - 0 => { - last_emitted_code_unit = strategy.emitBackslashes(self, backslash_count, last_emitted_code_unit); - return strategy.yieldArg(self); - }, - ' ', '\t' => { - last_emitted_code_unit = strategy.emitBackslashes(self, backslash_count, last_emitted_code_unit); - backslash_count = 0; - if (inside_quotes) { - last_emitted_code_unit = strategy.emitCharacter(self, char, last_emitted_code_unit); - } else return strategy.yieldArg(self); - }, - '"' => { - const char_is_escaped_quote = backslash_count % 2 != 0; - last_emitted_code_unit = strategy.emitBackslashes(self, backslash_count / 2, last_emitted_code_unit); - backslash_count = 0; - if (char_is_escaped_quote) { - last_emitted_code_unit = strategy.emitCharacter(self, '"', last_emitted_code_unit); - } else { - if (inside_quotes and - self.index + 1 != self.cmd_line.len and - mem.littleToNative(u16, self.cmd_line[self.index + 1]) == '"') - { - last_emitted_code_unit = strategy.emitCharacter(self, '"', last_emitted_code_unit); - self.index += 1; - } else { - inside_quotes = !inside_quotes; - } - } - }, - '\\' => { - backslash_count += 1; - }, - else => { - last_emitted_code_unit = strategy.emitBackslashes(self, backslash_count, last_emitted_code_unit); - backslash_count = 0; - last_emitted_code_unit = strategy.emitCharacter(self, char, last_emitted_code_unit); - }, - } - } - } - - /// Frees the iterator's copy of the command-line string and all previously returned - /// argument slices. - pub fn deinit(self: *ArgIteratorWindows) void { - self.allocator.free(self.buffer); - } -}; - -/// Optional parameters for `ArgIteratorGeneral` -pub const ArgIteratorGeneralOptions = struct { - comments: bool = false, - single_quotes: bool = false, -}; - -/// A general Iterator to parse a string into a set of arguments -pub fn ArgIteratorGeneral(comptime options: ArgIteratorGeneralOptions) type { - return struct { - allocator: Allocator, - index: usize = 0, - cmd_line: []const u8, - - /// Should the cmd_line field be free'd (using the allocator) on deinit()? - free_cmd_line_on_deinit: bool, - - /// buffer MUST be long enough to hold the cmd_line plus a null terminator. - /// buffer will we free'd (using the allocator) on deinit() - buffer: []u8, - start: usize = 0, - end: usize = 0, - - pub const Self = @This(); - - pub const InitError = error{OutOfMemory}; - - /// cmd_line_utf8 MUST remain valid and constant while using this instance - pub fn init(allocator: Allocator, cmd_line_utf8: []const u8) InitError!Self { - const buffer = try allocator.alloc(u8, cmd_line_utf8.len + 1); - errdefer allocator.free(buffer); - - return Self{ - .allocator = allocator, - .cmd_line = cmd_line_utf8, - .free_cmd_line_on_deinit = false, - .buffer = buffer, - }; - } - - /// cmd_line_utf8 will be free'd (with the allocator) on deinit() - pub fn initTakeOwnership(allocator: Allocator, cmd_line_utf8: []const u8) InitError!Self { - const buffer = try allocator.alloc(u8, cmd_line_utf8.len + 1); - errdefer allocator.free(buffer); - - return Self{ - .allocator = allocator, - .cmd_line = cmd_line_utf8, - .free_cmd_line_on_deinit = true, - .buffer = buffer, - }; - } - - // Skips over whitespace in the cmd_line. - // Returns false if the terminating sentinel is reached, true otherwise. - // Also skips over comments (if supported). - fn skipWhitespace(self: *Self) bool { - while (true) : (self.index += 1) { - const character = if (self.index != self.cmd_line.len) self.cmd_line[self.index] else 0; - switch (character) { - 0 => return false, - ' ', '\t', '\r', '\n' => continue, - '#' => { - if (options.comments) { - while (true) : (self.index += 1) { - switch (self.cmd_line[self.index]) { - '\n' => break, - 0 => return false, - else => continue, - } - } - continue; - } else { - break; - } - }, - else => break, - } - } - return true; - } - - pub fn skip(self: *Self) bool { - if (!self.skipWhitespace()) { - return false; - } - - var backslash_count: usize = 0; - var in_quote = false; - while (true) : (self.index += 1) { - const character = if (self.index != self.cmd_line.len) self.cmd_line[self.index] else 0; - switch (character) { - 0 => return true, - '"', '\'' => { - if (!options.single_quotes and character == '\'') { - backslash_count = 0; - continue; - } - const quote_is_real = backslash_count % 2 == 0; - if (quote_is_real) { - in_quote = !in_quote; - } - }, - '\\' => { - backslash_count += 1; - }, - ' ', '\t', '\r', '\n' => { - if (!in_quote) { - return true; - } - backslash_count = 0; - }, - else => { - backslash_count = 0; - continue; - }, - } - } - } - - /// Returns a slice of the internal buffer that contains the next argument. - /// Returns null when it reaches the end. - pub fn next(self: *Self) ?[:0]const u8 { - if (!self.skipWhitespace()) { - return null; - } - - var backslash_count: usize = 0; - var in_quote = false; - while (true) : (self.index += 1) { - const character = if (self.index != self.cmd_line.len) self.cmd_line[self.index] else 0; - switch (character) { - 0 => { - self.emitBackslashes(backslash_count); - self.buffer[self.end] = 0; - const token = self.buffer[self.start..self.end :0]; - self.end += 1; - self.start = self.end; - return token; - }, - '"', '\'' => { - if (!options.single_quotes and character == '\'') { - self.emitBackslashes(backslash_count); - backslash_count = 0; - self.emitCharacter(character); - continue; - } - const quote_is_real = backslash_count % 2 == 0; - self.emitBackslashes(backslash_count / 2); - backslash_count = 0; - - if (quote_is_real) { - in_quote = !in_quote; - } else { - self.emitCharacter('"'); - } - }, - '\\' => { - backslash_count += 1; - }, - ' ', '\t', '\r', '\n' => { - self.emitBackslashes(backslash_count); - backslash_count = 0; - if (in_quote) { - self.emitCharacter(character); - } else { - self.buffer[self.end] = 0; - const token = self.buffer[self.start..self.end :0]; - self.end += 1; - self.start = self.end; - return token; - } - }, - else => { - self.emitBackslashes(backslash_count); - backslash_count = 0; - self.emitCharacter(character); - }, - } - } - } - - fn emitBackslashes(self: *Self, emit_count: usize) void { - var i: usize = 0; - while (i < emit_count) : (i += 1) { - self.emitCharacter('\\'); - } - } - - fn emitCharacter(self: *Self, char: u8) void { - self.buffer[self.end] = char; - self.end += 1; - } - - /// Call to free the internal buffer of the iterator. - pub fn deinit(self: *Self) void { - self.allocator.free(self.buffer); - - if (self.free_cmd_line_on_deinit) { - self.allocator.free(self.cmd_line); - } - } - }; -} - -/// Cross-platform command line argument iterator. -pub const ArgIterator = struct { - const InnerType = switch (native_os) { - .windows => ArgIteratorWindows, - .wasi => if (builtin.link_libc) ArgIteratorPosix else ArgIteratorWasi, - else => ArgIteratorPosix, - }; - - inner: InnerType, - - /// Initialize the args iterator. Consider using initWithAllocator() instead - /// for cross-platform compatibility. - pub fn init() ArgIterator { - if (native_os == .wasi) { - @compileError("In WASI, use initWithAllocator instead."); - } - if (native_os == .windows) { - @compileError("In Windows, use initWithAllocator instead."); - } - - return ArgIterator{ .inner = InnerType.init() }; - } - - pub const InitError = InnerType.InitError; - - /// You must deinitialize iterator's internal buffers by calling `deinit` when done. - pub fn initWithAllocator(allocator: Allocator) InitError!ArgIterator { - if (native_os == .wasi and !builtin.link_libc) { - return ArgIterator{ .inner = try InnerType.init(allocator) }; - } - if (native_os == .windows) { - const cmd_line = std.os.windows.peb().ProcessParameters.CommandLine; - const cmd_line_w = cmd_line.Buffer.?[0 .. cmd_line.Length / 2]; - return ArgIterator{ .inner = try InnerType.init(allocator, cmd_line_w) }; - } - - return ArgIterator{ .inner = InnerType.init() }; - } - - /// Get the next argument. Returns 'null' if we are at the end. - /// Returned slice is pointing to the iterator's internal buffer. - /// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/). - /// On other platforms, the result is an opaque sequence of bytes with no particular encoding. - pub fn next(self: *ArgIterator) ?([:0]const u8) { - return self.inner.next(); - } - - /// Parse past 1 argument without capturing it. - /// Returns `true` if skipped an arg, `false` if we are at the end. - pub fn skip(self: *ArgIterator) bool { - return self.inner.skip(); - } - - /// Call this to free the iterator's internal buffer if the iterator - /// was created with `initWithAllocator` function. - pub fn deinit(self: *ArgIterator) void { - // Unless we're targeting WASI or Windows, this is a no-op. - if (native_os == .wasi and !builtin.link_libc) { - self.inner.deinit(); - } - - if (native_os == .windows) { - self.inner.deinit(); - } - } -}; - -/// Holds the command-line arguments, with the program name as the first entry. -/// Use argsWithAllocator() for cross-platform code. -pub fn args() ArgIterator { - return ArgIterator.init(); -} - -/// You must deinitialize iterator's internal buffers by calling `deinit` when done. -pub fn argsWithAllocator(allocator: Allocator) ArgIterator.InitError!ArgIterator { - return ArgIterator.initWithAllocator(allocator); -} - -/// Caller must call argsFree on result. -/// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/). -/// On other platforms, the result is an opaque sequence of bytes with no particular encoding. -pub fn argsAlloc(allocator: Allocator) ![][:0]u8 { - // TODO refactor to only make 1 allocation. - var it = try argsWithAllocator(allocator); - defer it.deinit(); - - var contents = std.array_list.Managed(u8).init(allocator); - defer contents.deinit(); - - var slice_list = std.array_list.Managed(usize).init(allocator); - defer slice_list.deinit(); - - while (it.next()) |arg| { - try contents.appendSlice(arg[0 .. arg.len + 1]); - try slice_list.append(arg.len); - } - - const contents_slice = contents.items; - const slice_sizes = slice_list.items; - const slice_list_bytes = try math.mul(usize, @sizeOf([]u8), slice_sizes.len); - const total_bytes = try math.add(usize, slice_list_bytes, contents_slice.len); - const buf = try allocator.alignedAlloc(u8, .of([]u8), total_bytes); - errdefer allocator.free(buf); - - const result_slice_list = mem.bytesAsSlice([:0]u8, buf[0..slice_list_bytes]); - const result_contents = buf[slice_list_bytes..]; - @memcpy(result_contents[0..contents_slice.len], contents_slice); - - var contents_index: usize = 0; - for (slice_sizes, 0..) |len, i| { - const new_index = contents_index + len; - result_slice_list[i] = result_contents[contents_index..new_index :0]; - contents_index = new_index + 1; - } - - return result_slice_list; -} - -pub fn argsFree(allocator: Allocator, args_alloc: []const [:0]u8) void { - var total_bytes: usize = 0; - for (args_alloc) |arg| { - total_bytes += @sizeOf([]u8) + arg.len + 1; - } - const unaligned_allocated_buf = @as([*]const u8, @ptrCast(args_alloc.ptr))[0..total_bytes]; - const aligned_allocated_buf: []align(@alignOf([]u8)) const u8 = @alignCast(unaligned_allocated_buf); - return allocator.free(aligned_allocated_buf); -} - -test ArgIteratorWindows { - const t = testArgIteratorWindows; - - try t( - \\"C:\Program Files\zig\zig.exe" run .\src\main.zig -target x86_64-windows-gnu -O ReleaseSafe -- --emoji=🗿 --eval="new Regex(\"Dwayne \\\"The Rock\\\" Johnson\")" - , &.{ - \\C:\Program Files\zig\zig.exe - , - \\run - , - \\.\src\main.zig - , - \\-target - , - \\x86_64-windows-gnu - , - \\-O - , - \\ReleaseSafe - , - \\-- - , - \\--emoji=🗿 - , - \\--eval=new Regex("Dwayne \"The Rock\" Johnson") - , - }); - - // Empty - try t("", &.{}); - - // Separators - try t("aa bb cc", &.{ "aa", "bb", "cc" }); - try t("aa\tbb\tcc", &.{ "aa", "bb", "cc" }); - try t("aa\nbb\ncc", &.{"aa\nbb\ncc"}); - try t("aa\r\nbb\r\ncc", &.{"aa\r\nbb\r\ncc"}); - try t("aa\rbb\rcc", &.{"aa\rbb\rcc"}); - try t("aa\x07bb\x07cc", &.{"aa\x07bb\x07cc"}); - try t("aa\x7Fbb\x7Fcc", &.{"aa\x7Fbb\x7Fcc"}); - try t("aa🦎bb🦎cc", &.{"aa🦎bb🦎cc"}); - - // Leading/trailing whitespace - try t(" ", &.{""}); - try t(" aa bb ", &.{ "", "aa", "bb" }); - try t("\t\t", &.{""}); - try t("\t\taa\t\tbb\t\t", &.{ "", "aa", "bb" }); - try t("\n\n", &.{"\n\n"}); - try t("\n\naa\n\nbb\n\n", &.{"\n\naa\n\nbb\n\n"}); - - // Executable name with quotes/backslashes - try t("\"aa bb\tcc\ndd\"", &.{"aa bb\tcc\ndd"}); - try t("\"", &.{""}); - try t("\"\"", &.{""}); - try t("\"\"\"", &.{""}); - try t("\"\"\"\"", &.{""}); - try t("\"\"\"\"\"", &.{""}); - try t("aa\"bb\"cc\"dd", &.{"aabbccdd"}); - try t("aa\"bb cc\"dd", &.{"aabb ccdd"}); - try t("\"aa\\\"bb\"", &.{"aa\\bb"}); - try t("\"aa\\\\\"", &.{"aa\\\\"}); - try t("aa\\\"bb", &.{"aa\\bb"}); - try t("aa\\\\\"bb", &.{"aa\\\\bb"}); - - // Arguments with quotes/backslashes - try t(". \"aa bb\tcc\ndd\"", &.{ ".", "aa bb\tcc\ndd" }); - try t(". aa\" \"bb\"\t\"cc\"\n\"dd\"", &.{ ".", "aa bb\tcc\ndd" }); - try t(". ", &.{"."}); - try t(". \"", &.{ ".", "" }); - try t(". \"\"", &.{ ".", "" }); - try t(". \"\"\"", &.{ ".", "\"" }); - try t(". \"\"\"\"", &.{ ".", "\"" }); - try t(". \"\"\"\"\"", &.{ ".", "\"\"" }); - try t(". \"\"\"\"\"\"", &.{ ".", "\"\"" }); - try t(". \" \"", &.{ ".", " " }); - try t(". \" \"\"", &.{ ".", " \"" }); - try t(". \" \"\"\"", &.{ ".", " \"" }); - try t(". \" \"\"\"\"", &.{ ".", " \"\"" }); - try t(". \" \"\"\"\"\"", &.{ ".", " \"\"" }); - try t(". \" \"\"\"\"\"\"", &.{ ".", " \"\"\"" }); - try t(". \\\"", &.{ ".", "\"" }); - try t(". \\\"\"", &.{ ".", "\"" }); - try t(". \\\"\"\"", &.{ ".", "\"" }); - try t(". \\\"\"\"\"", &.{ ".", "\"\"" }); - try t(". \\\"\"\"\"\"", &.{ ".", "\"\"" }); - try t(". \\\"\"\"\"\"\"", &.{ ".", "\"\"\"" }); - try t(". \" \\\"", &.{ ".", " \"" }); - try t(". \" \\\"\"", &.{ ".", " \"" }); - try t(". \" \\\"\"\"", &.{ ".", " \"\"" }); - try t(". \" \\\"\"\"\"", &.{ ".", " \"\"" }); - try t(". \" \\\"\"\"\"\"", &.{ ".", " \"\"\"" }); - try t(". \" \\\"\"\"\"\"\"", &.{ ".", " \"\"\"" }); - try t(". aa\\bb\\\\cc\\\\\\dd", &.{ ".", "aa\\bb\\\\cc\\\\\\dd" }); - try t(". \\\\\\\"aa bb\"", &.{ ".", "\\\"aa", "bb" }); - try t(". \\\\\\\\\"aa bb\"", &.{ ".", "\\\\aa bb" }); - - // From https://learn.microsoft.com/en-us/cpp/cpp/main-function-command-line-args#results-of-parsing-command-lines - try t( - \\foo.exe "abc" d e - , &.{ "foo.exe", "abc", "d", "e" }); - try t( - \\foo.exe a\\b d"e f"g h - , &.{ "foo.exe", "a\\\\b", "de fg", "h" }); - try t( - \\foo.exe a\\\"b c d - , &.{ "foo.exe", "a\\\"b", "c", "d" }); - try t( - \\foo.exe a\\\\"b c" d e - , &.{ "foo.exe", "a\\\\b c", "d", "e" }); - try t( - \\foo.exe a"b"" c d - , &.{ "foo.exe", "ab\" c d" }); - - // From https://daviddeley.com/autohotkey/parameters/parameters.htm#WINCRULESEX - try t("foo.exe CallMeIshmael", &.{ "foo.exe", "CallMeIshmael" }); - try t("foo.exe \"Call Me Ishmael\"", &.{ "foo.exe", "Call Me Ishmael" }); - try t("foo.exe Cal\"l Me I\"shmael", &.{ "foo.exe", "Call Me Ishmael" }); - try t("foo.exe CallMe\\\"Ishmael", &.{ "foo.exe", "CallMe\"Ishmael" }); - try t("foo.exe \"CallMe\\\"Ishmael\"", &.{ "foo.exe", "CallMe\"Ishmael" }); - try t("foo.exe \"Call Me Ishmael\\\\\"", &.{ "foo.exe", "Call Me Ishmael\\" }); - try t("foo.exe \"CallMe\\\\\\\"Ishmael\"", &.{ "foo.exe", "CallMe\\\"Ishmael" }); - try t("foo.exe a\\\\\\b", &.{ "foo.exe", "a\\\\\\b" }); - try t("foo.exe \"a\\\\\\b\"", &.{ "foo.exe", "a\\\\\\b" }); - - // Surrogate pair encoding of 𐐷 separated by quotes. - // Encoded as WTF-16: - // "<0xD801>"<0xDC37> - // Encoded as WTF-8: - // "<0xED><0xA0><0x81>"<0xED><0xB0><0xB7> - // During parsing, the quotes drop out and the surrogate pair - // should end up encoded as its normal UTF-8 representation. - try t("foo.exe \"\xed\xa0\x81\"\xed\xb0\xb7", &.{ "foo.exe", "𐐷" }); -} - -fn testArgIteratorWindows(cmd_line: []const u8, expected_args: []const []const u8) !void { - const cmd_line_w = try unicode.wtf8ToWtf16LeAllocZ(testing.allocator, cmd_line); - defer testing.allocator.free(cmd_line_w); - - // next - { - var it = try ArgIteratorWindows.init(testing.allocator, cmd_line_w); - defer it.deinit(); - - for (expected_args) |expected| { - if (it.next()) |actual| { - try testing.expectEqualStrings(expected, actual); - } else { - return error.TestUnexpectedResult; - } - } - try testing.expect(it.next() == null); - } - - // skip - { - var it = try ArgIteratorWindows.init(testing.allocator, cmd_line_w); - defer it.deinit(); - - for (0..expected_args.len) |_| { - try testing.expect(it.skip()); - } - try testing.expect(!it.skip()); - } -} - -test "general arg parsing" { - try testGeneralCmdLine("a b\tc d", &.{ "a", "b", "c", "d" }); - try testGeneralCmdLine("\"abc\" d e", &.{ "abc", "d", "e" }); - try testGeneralCmdLine("a\\\\\\b d\"e f\"g h", &.{ "a\\\\\\b", "de fg", "h" }); - try testGeneralCmdLine("a\\\\\\\"b c d", &.{ "a\\\"b", "c", "d" }); - try testGeneralCmdLine("a\\\\\\\\\"b c\" d e", &.{ "a\\\\b c", "d", "e" }); - try testGeneralCmdLine("a b\tc \"d f", &.{ "a", "b", "c", "d f" }); - try testGeneralCmdLine("j k l\\", &.{ "j", "k", "l\\" }); - try testGeneralCmdLine("\"\" x y z\\\\", &.{ "", "x", "y", "z\\\\" }); - - try testGeneralCmdLine("\".\\..\\zig-cache\\build\" \"bin\\zig.exe\" \".\\..\" \".\\..\\zig-cache\" \"--help\"", &.{ - ".\\..\\zig-cache\\build", - "bin\\zig.exe", - ".\\..", - ".\\..\\zig-cache", - "--help", - }); - - try testGeneralCmdLine( - \\ 'foo' "bar" - , &.{ "'foo'", "bar" }); -} - -fn testGeneralCmdLine(input_cmd_line: []const u8, expected_args: []const []const u8) !void { - var it = try ArgIteratorGeneral(.{}).init(std.testing.allocator, input_cmd_line); - defer it.deinit(); - for (expected_args) |expected_arg| { - const arg = it.next().?; - try testing.expectEqualStrings(expected_arg, arg); - } - try testing.expect(it.next() == null); -} - -test "response file arg parsing" { - try testResponseFileCmdLine( - \\a b - \\c d\ - , &.{ "a", "b", "c", "d\\" }); - try testResponseFileCmdLine("a b c d\\", &.{ "a", "b", "c", "d\\" }); - - try testResponseFileCmdLine( - \\j - \\ k l # this is a comment \\ \\\ \\\\ "none" "\\" "\\\" - \\ "m" #another comment - \\ - , &.{ "j", "k", "l", "m" }); - - try testResponseFileCmdLine( - \\ "" q "" - \\ "r s # t" "u\" v" #another comment - \\ - , &.{ "", "q", "", "r s # t", "u\" v" }); - - try testResponseFileCmdLine( - \\ -l"advapi32" a# b#c d# - \\e\\\ - , &.{ "-ladvapi32", "a#", "b#c", "d#", "e\\\\\\" }); - - try testResponseFileCmdLine( - \\ 'foo' "bar" - , &.{ "foo", "bar" }); -} - -fn testResponseFileCmdLine(input_cmd_line: []const u8, expected_args: []const []const u8) !void { - var it = try ArgIteratorGeneral(.{ .comments = true, .single_quotes = true }) - .init(std.testing.allocator, input_cmd_line); - defer it.deinit(); - for (expected_args) |expected_arg| { - const arg = it.next().?; - try testing.expectEqualStrings(expected_arg, arg); - } - try testing.expect(it.next() == null); -} - pub const UserInfo = struct { uid: posix.uid_t, gid: posix.gid_t, @@ -1706,73 +259,143 @@ pub fn getBaseAddress() usize { } } -/// Tells whether calling the `execv` or `execve` functions will be a compile error. -pub const can_execv = switch (native_os) { +/// Deprecated in favor of `Child.can_spawn`. +pub const can_spawn = Child.can_spawn; +/// Deprecated in favor of `can_replace`. +pub const can_execv = can_replace; + +/// Tells whether the target operating system supports replacing the current +/// process image. If this is `false` then calling `execv` or `replace` +/// functions will cause compilation to fail. +pub const can_replace = switch (native_os) { .windows, .haiku, .wasi => false, else => true, }; -/// Tells whether spawning child processes is supported (e.g. via Child) -pub const can_spawn = switch (native_os) { - .wasi, .ios, .tvos, .visionos, .watchos => false, - else => true, -}; - -pub const ExecvError = std.posix.ExecveError || error{OutOfMemory}; - -/// Replaces the current process image with the executed process. -/// This function must allocate memory to add a null terminating bytes on path and each arg. -/// It must also convert to KEY=VALUE\0 format for environment variables, and include null -/// pointers after the args and after the environment variables. -/// `argv[0]` is the executable path. -/// This function also uses the PATH environment variable to get the full path to the executable. -/// Due to the heap-allocation, it is illegal to call this function in a fork() child. -/// For that use case, use the `std.posix` functions directly. -pub fn execv(allocator: Allocator, argv: []const []const u8) ExecvError { - return execve(allocator, argv, null); +pub const ReplaceError = std.posix.ExecveError || error{OutOfMemory}; + +/// Replaces the current process image with the executed process. If this +/// function succeeds, it does not return. +/// +/// `argv[0]` is the name of the process to replace the current one with. If it +/// is not already a file path (i.e. it contains '/'), it is resolved into a +/// file path based on PATH from the parent environment. +/// +/// This operation is not available on targets for which `can_replace` is +/// `false`. +/// +/// This function must allocate memory to add a null terminating bytes on path +/// and each arg. +/// +/// Due to the heap allocation, it is illegal to call this function in a fork() +/// child. +pub fn replace(io: Io, gpa: Allocator, argv: []const []const u8, env: Environ.Block) ReplaceError { + if (!can_replace) @compileError("unsupported operation: replace"); + + var arena_allocator = std.heap.ArenaAllocator.init(gpa); + defer arena_allocator.deinit(); + const arena = arena_allocator.allocator(); + + const argv_buf = try arena.allocSentinel(?[*:0]const u8, argv.len, null); + for (argv, 0..) |arg, i| argv_buf[i] = (try arena.dupeZ(u8, arg)).ptr; + + return posix.execvpeZ_expandArg0(.no_expand, argv_buf.ptr[0].?, argv_buf.ptr, env); } -/// Replaces the current process image with the executed process. -/// This function must allocate memory to add a null terminating bytes on path and each arg. -/// It must also convert to KEY=VALUE\0 format for environment variables, and include null -/// pointers after the args and after the environment variables. -/// `argv[0]` is the executable path. -/// This function also uses the PATH environment variable to get the full path to the executable. -/// Due to the heap-allocation, it is illegal to call this function in a fork() child. -/// For that use case, use the `std.posix` functions directly. -pub fn execve( - allocator: Allocator, - argv: []const []const u8, - env_map: ?*const EnvMap, -) ExecvError { - if (!can_execv) @compileError("The target OS does not support execv"); - - var arena_allocator = std.heap.ArenaAllocator.init(allocator); +/// Replaces the current process image with the executed process. If this +/// function succeeds, it does not return. +/// +/// `argv[0]` is the file path of the process to replace the current one with, +/// relative to `dir`. It is *always* treated as a file path, even if it does +/// not contain '/'. +/// +/// This operation is not available on targets for which `can_replace` is +/// `false`. +/// +/// This function must allocate memory to add a null terminating bytes on path +/// and each arg. +/// +/// Due to the heap allocation, it is illegal to call this +/// function in a fork() child. For that use case, use the `std.posix` +/// functions directly. +pub fn replaceFile(io: Io, gpa: Allocator, argv: []const []const u8, env: Environ.Block) ReplaceError { + if (!can_replace) @compileError("unsupported operation: replaceFile"); + + var arena_allocator = std.heap.ArenaAllocator.init(gpa); defer arena_allocator.deinit(); const arena = arena_allocator.allocator(); const argv_buf = try arena.allocSentinel(?[*:0]const u8, argv.len, null); for (argv, 0..) |arg, i| argv_buf[i] = (try arena.dupeZ(u8, arg)).ptr; - const envp = m: { - if (env_map) |m| { - const envp_buf = try createNullDelimitedEnvMap(arena, m); - break :m envp_buf.ptr; - } else if (builtin.link_libc) { - break :m std.c.environ; - } else if (builtin.output_mode == .Exe) { - // Then we have Zig start code and this works. - // TODO type-safety for null-termination of `os.environ`. - break :m @as([*:null]const ?[*:0]const u8, @ptrCast(std.os.environ.ptr)); - } else { - // TODO come up with a solution for this. - @compileError("missing std lib enhancement: std.process.execv implementation has no way to collect the environment variables to forward to the child process"); - } + return posix.execvpeZ_expandArg0(.no_expand, argv_buf.ptr[0].?, argv_buf.ptr, env); +} + +pub const Arg0Expand = enum { expand, no_expand }; + +/// Replaces the current process image with the executed process. If this +/// function succeeds, it does not return. +/// +/// This operation is not available on all targets. `can_execv` +/// +/// This function also uses the PATH environment variable to get the full path to the executable. +/// If `file` is an absolute path, this is the same as `execveZ`. +/// +/// Like `execvpeZ` except if `arg0_expand` is `.expand`, then `argv` is mutable, +/// and `argv[0]` is expanded to be the same absolute path that is passed to the execve syscall. +/// If this function returns with an error, `argv[0]` will be restored to the value it was when it was passed in. +pub fn replace( + comptime arg0_expand: Arg0Expand, + file: [*:0]const u8, + child_argv: switch (arg0_expand) { + .expand => [*:null]?[*:0]const u8, + .no_expand => [*:null]const ?[*:0]const u8, + }, + envp: [*:null]const ?[*:0]const u8, + optional_PATH: ?[]const u8, +) ExecveError { + const file_slice = mem.sliceTo(file, 0); + if (mem.findScalar(u8, file_slice, '/') != null) return execveZ(file, child_argv, envp); + + const PATH = optional_PATH orelse "/usr/local/bin:/bin/:/usr/bin"; + // Use of PATH_MAX here is valid as the path_buf will be passed + // directly to the operating system in execveZ. + var path_buf: [PATH_MAX]u8 = undefined; + var it = mem.tokenizeScalar(u8, PATH, ':'); + var seen_eacces = false; + var err: ExecveError = error.FileNotFound; + + // In case of expanding arg0 we must put it back if we return with an error. + const prev_arg0 = child_argv[0]; + defer switch (arg0_expand) { + .expand => child_argv[0] = prev_arg0, + .no_expand => {}, }; - return posix.execvpeZ_expandArg0(.no_expand, argv_buf.ptr[0].?, argv_buf.ptr, envp); + while (it.next()) |search_path| { + const path_len = search_path.len + file_slice.len + 1; + if (path_buf.len < path_len + 1) return error.NameTooLong; + @memcpy(path_buf[0..search_path.len], search_path); + path_buf[search_path.len] = '/'; + @memcpy(path_buf[search_path.len + 1 ..][0..file_slice.len], file_slice); + path_buf[path_len] = 0; + const full_path = path_buf[0..path_len :0].ptr; + switch (arg0_expand) { + .expand => child_argv[0] = full_path, + .no_expand => {}, + } + err = execveZ(full_path, child_argv, envp); + switch (err) { + error.AccessDenied => seen_eacces = true, + error.FileNotFound, error.NotDir => {}, + else => |e| return e, + } + } + if (seen_eacces) return error.AccessDenied; + return err; } + pub const TotalSystemMemoryError = error{ UnknownTotalSystemMemory, }; @@ -1903,215 +526,6 @@ test raiseFileDescriptorLimit { raiseFileDescriptorLimit(); } -pub const CreateEnvironOptions = struct { - /// `null` means to leave the `ZIG_PROGRESS` environment variable unmodified. - /// If non-null, negative means to remove the environment variable, and >= 0 - /// means to provide it with the given integer. - zig_progress_fd: ?i32 = null, -}; - -/// Creates a null-delimited environment variable block in the format -/// expected by POSIX, from a hash map plus options. -pub fn createEnvironFromMap( - arena: Allocator, - map: *const EnvMap, - options: CreateEnvironOptions, -) Allocator.Error![:null]?[*:0]u8 { - const ZigProgressAction = enum { nothing, edit, delete, add }; - const zig_progress_action: ZigProgressAction = a: { - const fd = options.zig_progress_fd orelse break :a .nothing; - const contains = map.get("ZIG_PROGRESS") != null; - if (fd >= 0) { - break :a if (contains) .edit else .add; - } else { - if (contains) break :a .delete; - } - break :a .nothing; - }; - - const envp_count: usize = c: { - var count: usize = map.count(); - switch (zig_progress_action) { - .add => count += 1, - .delete => count -= 1, - .nothing, .edit => {}, - } - break :c count; - }; - - const envp_buf = try arena.allocSentinel(?[*:0]u8, envp_count, null); - var i: usize = 0; - - if (zig_progress_action == .add) { - envp_buf[i] = try std.fmt.allocPrintSentinel(arena, "ZIG_PROGRESS={d}", .{options.zig_progress_fd.?}, 0); - i += 1; - } - - { - var it = map.iterator(); - while (it.next()) |pair| { - if (mem.eql(u8, pair.key_ptr.*, "ZIG_PROGRESS")) switch (zig_progress_action) { - .add => unreachable, - .delete => continue, - .edit => { - envp_buf[i] = try std.fmt.allocPrintSentinel(arena, "{s}={d}", .{ - pair.key_ptr.*, options.zig_progress_fd.?, - }, 0); - i += 1; - continue; - }, - .nothing => {}, - }; - - envp_buf[i] = try std.fmt.allocPrintSentinel(arena, "{s}={s}", .{ pair.key_ptr.*, pair.value_ptr.* }, 0); - i += 1; - } - } - - assert(i == envp_count); - return envp_buf; -} - -/// Creates a null-delimited environment variable block in the format -/// expected by POSIX, from a hash map plus options. -pub fn createEnvironFromExisting( - arena: Allocator, - existing: [*:null]const ?[*:0]const u8, - options: CreateEnvironOptions, -) Allocator.Error![:null]?[*:0]u8 { - const existing_count, const contains_zig_progress = c: { - var count: usize = 0; - var contains = false; - while (existing[count]) |line| : (count += 1) { - contains = contains or mem.eql(u8, mem.sliceTo(line, '='), "ZIG_PROGRESS"); - } - break :c .{ count, contains }; - }; - const ZigProgressAction = enum { nothing, edit, delete, add }; - const zig_progress_action: ZigProgressAction = a: { - const fd = options.zig_progress_fd orelse break :a .nothing; - if (fd >= 0) { - break :a if (contains_zig_progress) .edit else .add; - } else { - if (contains_zig_progress) break :a .delete; - } - break :a .nothing; - }; - - const envp_count: usize = c: { - var count: usize = existing_count; - switch (zig_progress_action) { - .add => count += 1, - .delete => count -= 1, - .nothing, .edit => {}, - } - break :c count; - }; - - const envp_buf = try arena.allocSentinel(?[*:0]u8, envp_count, null); - var i: usize = 0; - var existing_index: usize = 0; - - if (zig_progress_action == .add) { - envp_buf[i] = try std.fmt.allocPrintSentinel(arena, "ZIG_PROGRESS={d}", .{options.zig_progress_fd.?}, 0); - i += 1; - } - - while (existing[existing_index]) |line| : (existing_index += 1) { - if (mem.eql(u8, mem.sliceTo(line, '='), "ZIG_PROGRESS")) switch (zig_progress_action) { - .add => unreachable, - .delete => continue, - .edit => { - envp_buf[i] = try std.fmt.allocPrintSentinel(arena, "ZIG_PROGRESS={d}", .{options.zig_progress_fd.?}, 0); - i += 1; - continue; - }, - .nothing => {}, - }; - envp_buf[i] = try arena.dupeZ(u8, mem.span(line)); - i += 1; - } - - assert(i == envp_count); - return envp_buf; -} - -pub fn createNullDelimitedEnvMap(arena: mem.Allocator, env_map: *const EnvMap) Allocator.Error![:null]?[*:0]u8 { - return createEnvironFromMap(arena, env_map, .{}); -} - -test createNullDelimitedEnvMap { - const allocator = testing.allocator; - var envmap = EnvMap.init(allocator); - defer envmap.deinit(); - - try envmap.put("HOME", "/home/ifreund"); - try envmap.put("WAYLAND_DISPLAY", "wayland-1"); - try envmap.put("DISPLAY", ":1"); - try envmap.put("DEBUGINFOD_URLS", " "); - try envmap.put("XCURSOR_SIZE", "24"); - - var arena = std.heap.ArenaAllocator.init(allocator); - defer arena.deinit(); - const environ = try createNullDelimitedEnvMap(arena.allocator(), &envmap); - - try testing.expectEqual(@as(usize, 5), environ.len); - - inline for (.{ - "HOME=/home/ifreund", - "WAYLAND_DISPLAY=wayland-1", - "DISPLAY=:1", - "DEBUGINFOD_URLS= ", - "XCURSOR_SIZE=24", - }) |target| { - for (environ) |variable| { - if (mem.eql(u8, mem.span(variable orelse continue), target)) break; - } else { - try testing.expect(false); // Environment variable not found - } - } -} - -/// Caller must free result. -pub fn createWindowsEnvBlock(allocator: mem.Allocator, env_map: *const EnvMap) ![]u16 { - // count bytes needed - const max_chars_needed = x: { - // Only need 2 trailing NUL code units for an empty environment - var max_chars_needed: usize = if (env_map.count() == 0) 2 else 1; - var it = env_map.iterator(); - while (it.next()) |pair| { - // +1 for '=' - // +1 for null byte - max_chars_needed += pair.key_ptr.len + pair.value_ptr.len + 2; - } - break :x max_chars_needed; - }; - const result = try allocator.alloc(u16, max_chars_needed); - errdefer allocator.free(result); - - var it = env_map.iterator(); - var i: usize = 0; - while (it.next()) |pair| { - i += try unicode.wtf8ToWtf16Le(result[i..], pair.key_ptr.*); - result[i] = '='; - i += 1; - i += try unicode.wtf8ToWtf16Le(result[i..], pair.value_ptr.*); - result[i] = 0; - i += 1; - } - result[i] = 0; - i += 1; - // An empty environment is a special case that requires a redundant - // NUL terminator. CreateProcess will read the second code unit even - // though theoretically the first should be enough to recognize that the - // environment is empty (see https://nullprogram.com/blog/2023/08/23/) - if (env_map.count() == 0) { - result[i] = 0; - i += 1; - } - return try allocator.realloc(result, i); -} - /// Logs an error and then terminates the process with exit code 1. pub fn fatal(comptime format: []const u8, format_arguments: anytype) noreturn { std.log.err(format, format_arguments); diff --git a/lib/std/process/Args.zig b/lib/std/process/Args.zig new file mode 100644 index 0000000000000000000000000000000000000000..0eb5d813090c0cc498d3d11b80638524dde55805 --- /dev/null +++ b/lib/std/process/Args.zig @@ -0,0 +1,958 @@ +const Args = @This(); + +const builtin = @import("builtin"); +const native_os = builtin.os.tag; + +const std = @import("../std.zig"); +const Allocator = std.mem.Allocator; +const assert = std.debug.assert; +const testing = std.debug.testing; + +vector: Vector, + +pub const Vector = switch (native_os) { + .windows => []const u16, // WTF-16 encoded + else => []const [*:0]const u8, +}; + +/// Cross-platform access to command line one argument at a time. +pub const Iterator = struct { + const Inner = switch (native_os) { + .windows => Windows, + .wasi => if (builtin.link_libc) Posix else Wasi, + else => Posix, + }; + + inner: Inner, + + /// Initialize the args iterator. Consider using `initAllocator` instead + /// for cross-platform compatibility. + pub fn init(a: Args) Iterator { + if (native_os == .wasi) { + @compileError("In WASI, use initAllocator instead."); + } + if (native_os == .windows) { + @compileError("In Windows, use initAllocator instead."); + } + + return .{ .inner = .init(a) }; + } + + pub const InitError = Inner.InitError; + + /// You must deinitialize iterator's internal buffers by calling `deinit` when done. + pub fn initAllocator(a: Args, gpa: Allocator) InitError!Iterator { + if (native_os == .wasi and !builtin.link_libc) { + return .{ .inner = try .init(a, gpa) }; + } + if (native_os == .windows) { + return .{ .inner = try .init(a, gpa) }; + } + + return .{ .inner = .init(a) }; + } + + /// Return subsequent argument, or `null` if no more remaining. + /// + /// Returned slice is pointing to the iterator's internal buffer. + /// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/). + /// On other platforms, the result is an opaque sequence of bytes with no particular encoding. + pub fn next(it: *Iterator) ?([:0]const u8) { + return it.inner.next(); + } + + /// Parse past 1 argument without capturing it. + /// Returns `true` if skipped an arg, `false` if we are at the end. + pub fn skip(it: *Iterator) bool { + return it.inner.skip(); + } + + /// Required to release resources if the iterator was initialized with + /// `initAllocator` function. + pub fn deinit(it: *Iterator) void { + // Unless we're targeting WASI or Windows, this is a no-op. + if (native_os == .wasi and !builtin.link_libc) it.inner.deinit(); + if (native_os == .windows) it.inner.deinit(); + } + + /// Iterator that implements the Windows command-line parsing algorithm. + /// + /// The implementation is intended to be compatible with the post-2008 C runtime, + /// but is *not* intended to be compatible with `CommandLineToArgvW` since + /// `CommandLineToArgvW` uses the pre-2008 parsing rules. + /// + /// This iterator faithfully implements the parsing behavior observed from the C runtime with + /// one exception: if the command-line string is empty, the iterator will immediately complete + /// without returning any arguments (whereas the C runtime will return a single argument + /// representing the name of the current executable). + /// + /// The essential parts of the algorithm are described in Microsoft's documentation: + /// + /// - https://learn.microsoft.com/en-us/cpp/cpp/main-function-command-line-args?view=msvc-170#parsing-c-command-line-arguments + /// + /// David Deley explains some additional undocumented quirks in great detail: + /// + /// - https://daviddeley.com/autohotkey/parameters/parameters.htm#WINCRULES + pub const Windows = struct { + allocator: Allocator, + /// Encoded as WTF-16 LE. + cmd_line: []const u16, + index: usize = 0, + /// Owned by the iterator. Long enough to hold contiguous NUL-terminated slices + /// of each argument encoded as WTF-8. + buffer: []u8, + start: usize = 0, + end: usize = 0, + + pub const InitError = error{OutOfMemory}; + + /// `cmd_line_w` *must* be a WTF16-LE-encoded string. + /// + /// The iterator stores and uses `cmd_line_w`, so its memory must be valid for + /// at least as long as the returned Windows. + pub fn init(allocator: Allocator, cmd_line_w: []const u16) Windows.InitError!Windows { + const wtf8_len = std.unicode.calcWtf8Len(cmd_line_w); + + // This buffer must be large enough to contain contiguous NUL-terminated slices + // of each argument. + // - During parsing, the length of a parsed argument will always be equal to + // to less than its unparsed length + // - The first argument needs one extra byte of space allocated for its NUL + // terminator, but for each subsequent argument the necessary whitespace + // between arguments guarantees room for their NUL terminator(s). + const buffer = try allocator.alloc(u8, wtf8_len + 1); + errdefer allocator.free(buffer); + + return .{ + .allocator = allocator, + .cmd_line = cmd_line_w, + .buffer = buffer, + }; + } + + /// Returns the next argument and advances the iterator. Returns `null` if at the end of the + /// command-line string. The iterator owns the returned slice. + /// The result is encoded as [WTF-8](https://wtf-8.codeberg.page/). + pub fn next(self: *Windows) ?[:0]const u8 { + return self.nextWithStrategy(next_strategy); + } + + /// Skips the next argument and advances the iterator. Returns `true` if an argument was + /// skipped, `false` if at the end of the command-line string. + pub fn skip(self: *Windows) bool { + return self.nextWithStrategy(skip_strategy); + } + + const next_strategy = struct { + const T = ?[:0]const u8; + + const eof = null; + + /// Returns '\' if any backslashes are emitted, otherwise returns `last_emitted_code_unit`. + fn emitBackslashes(self: *Windows, count: usize, last_emitted_code_unit: ?u16) ?u16 { + for (0..count) |_| { + self.buffer[self.end] = '\\'; + self.end += 1; + } + return if (count != 0) '\\' else last_emitted_code_unit; + } + + /// If `last_emitted_code_unit` and `code_unit` form a surrogate pair, then + /// the previously emitted high surrogate is overwritten by the codepoint encoded + /// by the surrogate pair, and `null` is returned. + /// Otherwise, `code_unit` is emitted and returned. + fn emitCharacter(self: *Windows, code_unit: u16, last_emitted_code_unit: ?u16) ?u16 { + // Because we are emitting WTF-8, we need to + // check to see if we've emitted two consecutive surrogate + // codepoints that form a valid surrogate pair in order + // to ensure that we're always emitting well-formed WTF-8 + // (https://wtf-8.codeberg.page/#concatenating). + // + // If we do have a valid surrogate pair, we need to emit + // the UTF-8 sequence for the codepoint that they encode + // instead of the WTF-8 encoding for the two surrogate pairs + // separately. + // + // This is relevant when dealing with a WTF-16 encoded + // command line like this: + // "<0xD801>"<0xDC37> + // which would get parsed and converted to WTF-8 as: + // <0xED><0xA0><0x81><0xED><0xB0><0xB7> + // but instead, we need to recognize the surrogate pair + // and emit the codepoint it encodes, which in this + // example is U+10437 (𐐷), which is encoded in UTF-8 as: + // <0xF0><0x90><0x90><0xB7> + if (last_emitted_code_unit != null and + std.unicode.utf16IsLowSurrogate(code_unit) and + std.unicode.utf16IsHighSurrogate(last_emitted_code_unit.?)) + { + const codepoint = std.unicode.utf16DecodeSurrogatePair(&.{ last_emitted_code_unit.?, code_unit }) catch unreachable; + + // Unpaired surrogate is 3 bytes long + const dest = self.buffer[self.end - 3 ..]; + const len = std.unicode.utf8Encode(codepoint, dest) catch unreachable; + // All codepoints that require a surrogate pair (> U+FFFF) are encoded as 4 bytes + assert(len == 4); + self.end += 1; + return null; + } + + const wtf8_len = std.unicode.wtf8Encode(code_unit, self.buffer[self.end..]) catch unreachable; + self.end += wtf8_len; + return code_unit; + } + + fn yieldArg(self: *Windows) [:0]const u8 { + self.buffer[self.end] = 0; + const arg = self.buffer[self.start..self.end :0]; + self.end += 1; + self.start = self.end; + return arg; + } + }; + + const skip_strategy = struct { + const T = bool; + + const eof = false; + + fn emitBackslashes(_: *Windows, _: usize, last_emitted_code_unit: ?u16) ?u16 { + return last_emitted_code_unit; + } + + fn emitCharacter(_: *Windows, _: u16, last_emitted_code_unit: ?u16) ?u16 { + return last_emitted_code_unit; + } + + fn yieldArg(_: *Windows) bool { + return true; + } + }; + + fn nextWithStrategy(self: *Windows, comptime strategy: type) strategy.T { + var last_emitted_code_unit: ?u16 = null; + // The first argument (the executable name) uses different parsing rules. + if (self.index == 0) { + if (self.cmd_line.len == 0 or self.cmd_line[0] == 0) { + // Immediately complete the iterator. + // The C runtime would return the name of the current executable here. + return strategy.eof; + } + + var inside_quotes = false; + while (true) : (self.index += 1) { + const char = if (self.index != self.cmd_line.len) + std.mem.littleToNative(u16, self.cmd_line[self.index]) + else + 0; + switch (char) { + 0 => { + return strategy.yieldArg(self); + }, + '"' => { + inside_quotes = !inside_quotes; + }, + ' ', '\t' => { + if (inside_quotes) { + last_emitted_code_unit = strategy.emitCharacter(self, char, last_emitted_code_unit); + } else { + self.index += 1; + return strategy.yieldArg(self); + } + }, + else => { + last_emitted_code_unit = strategy.emitCharacter(self, char, last_emitted_code_unit); + }, + } + } + } + + // Skip spaces and tabs. The iterator completes if we reach the end of the string here. + while (true) : (self.index += 1) { + const char = if (self.index != self.cmd_line.len) + std.mem.littleToNative(u16, self.cmd_line[self.index]) + else + 0; + switch (char) { + 0 => return strategy.eof, + ' ', '\t' => continue, + else => break, + } + } + + // Parsing rules for subsequent arguments: + // + // - The end of the string always terminates the current argument. + // - When not in 'inside_quotes' mode, a space or tab terminates the current argument. + // - 2n backslashes followed by a quote emit n backslashes (note: n can be zero). + // If in 'inside_quotes' and the quote is immediately followed by a second quote, + // one quote is emitted and the other is skipped, otherwise, the quote is skipped + // and 'inside_quotes' is toggled. + // - 2n + 1 backslashes followed by a quote emit n backslashes followed by a quote. + // - n backslashes not followed by a quote emit n backslashes. + var backslash_count: usize = 0; + var inside_quotes = false; + while (true) : (self.index += 1) { + const char = if (self.index != self.cmd_line.len) + std.mem.littleToNative(u16, self.cmd_line[self.index]) + else + 0; + switch (char) { + 0 => { + last_emitted_code_unit = strategy.emitBackslashes(self, backslash_count, last_emitted_code_unit); + return strategy.yieldArg(self); + }, + ' ', '\t' => { + last_emitted_code_unit = strategy.emitBackslashes(self, backslash_count, last_emitted_code_unit); + backslash_count = 0; + if (inside_quotes) { + last_emitted_code_unit = strategy.emitCharacter(self, char, last_emitted_code_unit); + } else return strategy.yieldArg(self); + }, + '"' => { + const char_is_escaped_quote = backslash_count % 2 != 0; + last_emitted_code_unit = strategy.emitBackslashes(self, backslash_count / 2, last_emitted_code_unit); + backslash_count = 0; + if (char_is_escaped_quote) { + last_emitted_code_unit = strategy.emitCharacter(self, '"', last_emitted_code_unit); + } else { + if (inside_quotes and + self.index + 1 != self.cmd_line.len and + std.mem.littleToNative(u16, self.cmd_line[self.index + 1]) == '"') + { + last_emitted_code_unit = strategy.emitCharacter(self, '"', last_emitted_code_unit); + self.index += 1; + } else { + inside_quotes = !inside_quotes; + } + } + }, + '\\' => { + backslash_count += 1; + }, + else => { + last_emitted_code_unit = strategy.emitBackslashes(self, backslash_count, last_emitted_code_unit); + backslash_count = 0; + last_emitted_code_unit = strategy.emitCharacter(self, char, last_emitted_code_unit); + }, + } + } + } + + /// Frees the iterator's copy of the command-line string and all previously returned + /// argument slices. + pub fn deinit(self: *Windows) void { + self.allocator.free(self.buffer); + } + }; + + pub const Posix = struct { + remaining: Vector, + + pub const InitError = error{}; + + pub fn init(a: Args) Posix { + return .{ .remaining = a.vector }; + } + + pub fn next(it: *Posix) ?[:0]const u8 { + if (it.remaining.len == 0) return null; + const arg = it.remaining[0]; + it.remaining = it.remaining[1..]; + return std.mem.sliceTo(arg, 0); + } + + pub fn skip(it: *Posix) bool { + if (it.remaining.len == 0) return false; + it.remaining = it.remaining[1..]; + return true; + } + }; + + pub const Wasi = struct { + allocator: Allocator, + index: usize, + args: [][:0]u8, + + pub const InitError = error{OutOfMemory} || std.posix.UnexpectedError; + + /// You must call deinit to free the internal buffer of the + /// iterator after you are done. + pub fn init(allocator: Allocator) Wasi.InitError!Wasi { + const fetched_args = try Wasi.internalInit(allocator); + return Wasi{ + .allocator = allocator, + .index = 0, + .args = fetched_args, + }; + } + + fn internalInit(allocator: Allocator) Wasi.InitError![][:0]u8 { + var count: usize = undefined; + var buf_size: usize = undefined; + + switch (std.os.wasi.args_sizes_get(&count, &buf_size)) { + .SUCCESS => {}, + else => |err| return std.posix.unexpectedErrno(err), + } + + if (count == 0) { + return &[_][:0]u8{}; + } + + const argv = try allocator.alloc([*:0]u8, count); + defer allocator.free(argv); + + const argv_buf = try allocator.alloc(u8, buf_size); + + switch (std.os.wasi.args_get(argv.ptr, argv_buf.ptr)) { + .SUCCESS => {}, + else => |err| return std.posix.unexpectedErrno(err), + } + + var result_args = try allocator.alloc([:0]u8, count); + var i: usize = 0; + while (i < count) : (i += 1) { + result_args[i] = std.mem.sliceTo(argv[i], 0); + } + + return result_args; + } + + pub fn next(self: *Wasi) ?[:0]const u8 { + if (self.index == self.args.len) return null; + + const arg = self.args[self.index]; + self.index += 1; + return arg; + } + + pub fn skip(self: *Wasi) bool { + if (self.index == self.args.len) return false; + + self.index += 1; + return true; + } + + /// Call to free the internal buffer of the iterator. + pub fn deinit(self: *Wasi) void { + // Nothing is allocated when there are no args + if (self.args.len == 0) return; + + const last_item = self.args[self.args.len - 1]; + const last_byte_addr = @intFromPtr(last_item.ptr) + last_item.len + 1; // null terminated + const first_item_ptr = self.args[0].ptr; + const len = last_byte_addr - @intFromPtr(first_item_ptr); + self.allocator.free(first_item_ptr[0..len]); + self.allocator.free(self.args); + } + }; +}; + +/// Holds the command-line arguments, with the program name as the first entry. +/// Use `iterateAllocator` for cross-platform code. +pub fn iterate(a: Args) Iterator { + return .init(a); +} + +/// You must deinitialize iterator's internal buffers by calling `deinit` when +/// done. +pub fn iterateAllocator(a: Args, gpa: Allocator) Iterator.InitError!Iterator { + return .initAllocator(a, gpa); +} + +/// Returned value may reference several allocations; call `freeSlice` to +/// release. +/// +/// * On Windows, the result is encoded as +/// [WTF-8](https://wtf-8.codeberg.page/). +/// * On other platforms, the result is an opaque sequence of bytes with no +/// particular encoding. +pub fn toSlice(a: Args, gpa: Allocator) Allocator.Error![][:0]u8 { + var it = try a.iterateAllocator(gpa); + defer it.deinit(); + + var contents = std.array_list.Managed(u8).init(gpa); + defer contents.deinit(); + + var slice_list = std.array_list.Managed(usize).init(gpa); + defer slice_list.deinit(); + + while (it.next()) |arg| { + try contents.appendSlice(arg[0 .. arg.len + 1]); + try slice_list.append(arg.len); + } + + const contents_slice = contents.items; + const slice_sizes = slice_list.items; + const slice_list_bytes = std.math.mul(usize, @sizeOf([]u8), slice_sizes.len) catch return error.OutOfMemory; + const total_bytes = std.math.add(usize, slice_list_bytes, contents_slice.len) catch return error.OutOfMemory; + const buf = try gpa.alignedAlloc(u8, .of([]u8), total_bytes); + errdefer gpa.free(buf); + + const result_slice_list = std.mem.bytesAsSlice([:0]u8, buf[0..slice_list_bytes]); + const result_contents = buf[slice_list_bytes..]; + @memcpy(result_contents[0..contents_slice.len], contents_slice); + + var contents_index: usize = 0; + for (slice_sizes, 0..) |len, i| { + const new_index = contents_index + len; + result_slice_list[i] = result_contents[contents_index..new_index :0]; + contents_index = new_index + 1; + } + + return result_slice_list; +} + +/// Frees memory allocate by `toSlice`. +pub fn freeSlice(gpa: Allocator, to_slice_result: []const [:0]u8) void { + var total_bytes: usize = 0; + for (to_slice_result) |arg| { + total_bytes += @sizeOf([]u8) + arg.len + 1; + } + const unaligned_allocated_buf = @as([*]const u8, @ptrCast(to_slice_result.ptr))[0..total_bytes]; + const aligned_allocated_buf: []align(@alignOf([]u8)) const u8 = @alignCast(unaligned_allocated_buf); + return gpa.free(aligned_allocated_buf); +} + +test "Iterator.Windows" { + const t = testArgIteratorWindows; + + try t( + \\"C:\Program Files\zig\zig.exe" run .\src\main.zig -target x86_64-windows-gnu -O ReleaseSafe -- --emoji=🗿 --eval="new Regex(\"Dwayne \\\"The Rock\\\" Johnson\")" + , &.{ + \\C:\Program Files\zig\zig.exe + , + \\run + , + \\.\src\main.zig + , + \\-target + , + \\x86_64-windows-gnu + , + \\-O + , + \\ReleaseSafe + , + \\-- + , + \\--emoji=🗿 + , + \\--eval=new Regex("Dwayne \"The Rock\" Johnson") + , + }); + + // Empty + try t("", &.{}); + + // Separators + try t("aa bb cc", &.{ "aa", "bb", "cc" }); + try t("aa\tbb\tcc", &.{ "aa", "bb", "cc" }); + try t("aa\nbb\ncc", &.{"aa\nbb\ncc"}); + try t("aa\r\nbb\r\ncc", &.{"aa\r\nbb\r\ncc"}); + try t("aa\rbb\rcc", &.{"aa\rbb\rcc"}); + try t("aa\x07bb\x07cc", &.{"aa\x07bb\x07cc"}); + try t("aa\x7Fbb\x7Fcc", &.{"aa\x7Fbb\x7Fcc"}); + try t("aa🦎bb🦎cc", &.{"aa🦎bb🦎cc"}); + + // Leading/trailing whitespace + try t(" ", &.{""}); + try t(" aa bb ", &.{ "", "aa", "bb" }); + try t("\t\t", &.{""}); + try t("\t\taa\t\tbb\t\t", &.{ "", "aa", "bb" }); + try t("\n\n", &.{"\n\n"}); + try t("\n\naa\n\nbb\n\n", &.{"\n\naa\n\nbb\n\n"}); + + // Executable name with quotes/backslashes + try t("\"aa bb\tcc\ndd\"", &.{"aa bb\tcc\ndd"}); + try t("\"", &.{""}); + try t("\"\"", &.{""}); + try t("\"\"\"", &.{""}); + try t("\"\"\"\"", &.{""}); + try t("\"\"\"\"\"", &.{""}); + try t("aa\"bb\"cc\"dd", &.{"aabbccdd"}); + try t("aa\"bb cc\"dd", &.{"aabb ccdd"}); + try t("\"aa\\\"bb\"", &.{"aa\\bb"}); + try t("\"aa\\\\\"", &.{"aa\\\\"}); + try t("aa\\\"bb", &.{"aa\\bb"}); + try t("aa\\\\\"bb", &.{"aa\\\\bb"}); + + // Arguments with quotes/backslashes + try t(". \"aa bb\tcc\ndd\"", &.{ ".", "aa bb\tcc\ndd" }); + try t(". aa\" \"bb\"\t\"cc\"\n\"dd\"", &.{ ".", "aa bb\tcc\ndd" }); + try t(". ", &.{"."}); + try t(". \"", &.{ ".", "" }); + try t(". \"\"", &.{ ".", "" }); + try t(". \"\"\"", &.{ ".", "\"" }); + try t(". \"\"\"\"", &.{ ".", "\"" }); + try t(". \"\"\"\"\"", &.{ ".", "\"\"" }); + try t(". \"\"\"\"\"\"", &.{ ".", "\"\"" }); + try t(". \" \"", &.{ ".", " " }); + try t(". \" \"\"", &.{ ".", " \"" }); + try t(". \" \"\"\"", &.{ ".", " \"" }); + try t(". \" \"\"\"\"", &.{ ".", " \"\"" }); + try t(". \" \"\"\"\"\"", &.{ ".", " \"\"" }); + try t(". \" \"\"\"\"\"\"", &.{ ".", " \"\"\"" }); + try t(". \\\"", &.{ ".", "\"" }); + try t(". \\\"\"", &.{ ".", "\"" }); + try t(". \\\"\"\"", &.{ ".", "\"" }); + try t(". \\\"\"\"\"", &.{ ".", "\"\"" }); + try t(". \\\"\"\"\"\"", &.{ ".", "\"\"" }); + try t(". \\\"\"\"\"\"\"", &.{ ".", "\"\"\"" }); + try t(". \" \\\"", &.{ ".", " \"" }); + try t(". \" \\\"\"", &.{ ".", " \"" }); + try t(". \" \\\"\"\"", &.{ ".", " \"\"" }); + try t(". \" \\\"\"\"\"", &.{ ".", " \"\"" }); + try t(". \" \\\"\"\"\"\"", &.{ ".", " \"\"\"" }); + try t(". \" \\\"\"\"\"\"\"", &.{ ".", " \"\"\"" }); + try t(". aa\\bb\\\\cc\\\\\\dd", &.{ ".", "aa\\bb\\\\cc\\\\\\dd" }); + try t(". \\\\\\\"aa bb\"", &.{ ".", "\\\"aa", "bb" }); + try t(". \\\\\\\\\"aa bb\"", &.{ ".", "\\\\aa bb" }); + + // From https://learn.microsoft.com/en-us/cpp/cpp/main-function-command-line-args#results-of-parsing-command-lines + try t( + \\foo.exe "abc" d e + , &.{ "foo.exe", "abc", "d", "e" }); + try t( + \\foo.exe a\\b d"e f"g h + , &.{ "foo.exe", "a\\\\b", "de fg", "h" }); + try t( + \\foo.exe a\\\"b c d + , &.{ "foo.exe", "a\\\"b", "c", "d" }); + try t( + \\foo.exe a\\\\"b c" d e + , &.{ "foo.exe", "a\\\\b c", "d", "e" }); + try t( + \\foo.exe a"b"" c d + , &.{ "foo.exe", "ab\" c d" }); + + // From https://daviddeley.com/autohotkey/parameters/parameters.htm#WINCRULESEX + try t("foo.exe CallMeIshmael", &.{ "foo.exe", "CallMeIshmael" }); + try t("foo.exe \"Call Me Ishmael\"", &.{ "foo.exe", "Call Me Ishmael" }); + try t("foo.exe Cal\"l Me I\"shmael", &.{ "foo.exe", "Call Me Ishmael" }); + try t("foo.exe CallMe\\\"Ishmael", &.{ "foo.exe", "CallMe\"Ishmael" }); + try t("foo.exe \"CallMe\\\"Ishmael\"", &.{ "foo.exe", "CallMe\"Ishmael" }); + try t("foo.exe \"Call Me Ishmael\\\\\"", &.{ "foo.exe", "Call Me Ishmael\\" }); + try t("foo.exe \"CallMe\\\\\\\"Ishmael\"", &.{ "foo.exe", "CallMe\\\"Ishmael" }); + try t("foo.exe a\\\\\\b", &.{ "foo.exe", "a\\\\\\b" }); + try t("foo.exe \"a\\\\\\b\"", &.{ "foo.exe", "a\\\\\\b" }); + + // Surrogate pair encoding of 𐐷 separated by quotes. + // Encoded as WTF-16: + // "<0xD801>"<0xDC37> + // Encoded as WTF-8: + // "<0xED><0xA0><0x81>"<0xED><0xB0><0xB7> + // During parsing, the quotes drop out and the surrogate pair + // should end up encoded as its normal UTF-8 representation. + try t("foo.exe \"\xed\xa0\x81\"\xed\xb0\xb7", &.{ "foo.exe", "𐐷" }); +} + +fn testArgIteratorWindows(cmd_line: []const u8, expected_args: []const []const u8) !void { + const cmd_line_w = try std.unicode.wtf8ToWtf16LeAllocZ(testing.allocator, cmd_line); + defer testing.allocator.free(cmd_line_w); + + // next + { + var it = try Iterator.Windows.init(testing.allocator, cmd_line_w); + defer it.deinit(); + + for (expected_args) |expected| { + if (it.next()) |actual| { + try testing.expectEqualStrings(expected, actual); + } else { + return error.TestUnexpectedResult; + } + } + try testing.expect(it.next() == null); + } + + // skip + { + var it = try Iterator.Windows.init(testing.allocator, cmd_line_w); + defer it.deinit(); + + for (0..expected_args.len) |_| { + try testing.expect(it.skip()); + } + try testing.expect(!it.skip()); + } +} + +test "general arg parsing" { + try testGeneralCmdLine("a b\tc d", &.{ "a", "b", "c", "d" }); + try testGeneralCmdLine("\"abc\" d e", &.{ "abc", "d", "e" }); + try testGeneralCmdLine("a\\\\\\b d\"e f\"g h", &.{ "a\\\\\\b", "de fg", "h" }); + try testGeneralCmdLine("a\\\\\\\"b c d", &.{ "a\\\"b", "c", "d" }); + try testGeneralCmdLine("a\\\\\\\\\"b c\" d e", &.{ "a\\\\b c", "d", "e" }); + try testGeneralCmdLine("a b\tc \"d f", &.{ "a", "b", "c", "d f" }); + try testGeneralCmdLine("j k l\\", &.{ "j", "k", "l\\" }); + try testGeneralCmdLine("\"\" x y z\\\\", &.{ "", "x", "y", "z\\\\" }); + + try testGeneralCmdLine("\".\\..\\zig-cache\\build\" \"bin\\zig.exe\" \".\\..\" \".\\..\\zig-cache\" \"--help\"", &.{ + ".\\..\\zig-cache\\build", + "bin\\zig.exe", + ".\\..", + ".\\..\\zig-cache", + "--help", + }); + + try testGeneralCmdLine( + \\ 'foo' "bar" + , &.{ "'foo'", "bar" }); +} + +fn testGeneralCmdLine(input_cmd_line: []const u8, expected_args: []const []const u8) !void { + var it = try ArgIteratorGeneral(.{}).init(std.testing.allocator, input_cmd_line); + defer it.deinit(); + for (expected_args) |expected_arg| { + const arg = it.next().?; + try testing.expectEqualStrings(expected_arg, arg); + } + try testing.expect(it.next() == null); +} + +/// Optional parameters for `ArgIteratorGeneral` +pub const ArgIteratorGeneralOptions = struct { + comments: bool = false, + single_quotes: bool = false, +}; + +/// A general Iterator to parse a string into a set of arguments +pub fn ArgIteratorGeneral(comptime options: ArgIteratorGeneralOptions) type { + return struct { + allocator: Allocator, + index: usize = 0, + cmd_line: []const u8, + + /// Should the cmd_line field be free'd (using the allocator) on deinit()? + free_cmd_line_on_deinit: bool, + + /// buffer MUST be long enough to hold the cmd_line plus a null terminator. + /// buffer will we free'd (using the allocator) on deinit() + buffer: []u8, + start: usize = 0, + end: usize = 0, + + pub const Self = @This(); + + pub const InitError = error{OutOfMemory}; + + /// cmd_line_utf8 MUST remain valid and constant while using this instance + pub fn init(allocator: Allocator, cmd_line_utf8: []const u8) InitError!Self { + const buffer = try allocator.alloc(u8, cmd_line_utf8.len + 1); + errdefer allocator.free(buffer); + + return Self{ + .allocator = allocator, + .cmd_line = cmd_line_utf8, + .free_cmd_line_on_deinit = false, + .buffer = buffer, + }; + } + + /// cmd_line_utf8 will be free'd (with the allocator) on deinit() + pub fn initTakeOwnership(allocator: Allocator, cmd_line_utf8: []const u8) InitError!Self { + const buffer = try allocator.alloc(u8, cmd_line_utf8.len + 1); + errdefer allocator.free(buffer); + + return Self{ + .allocator = allocator, + .cmd_line = cmd_line_utf8, + .free_cmd_line_on_deinit = true, + .buffer = buffer, + }; + } + + // Skips over whitespace in the cmd_line. + // Returns false if the terminating sentinel is reached, true otherwise. + // Also skips over comments (if supported). + fn skipWhitespace(self: *Self) bool { + while (true) : (self.index += 1) { + const character = if (self.index != self.cmd_line.len) self.cmd_line[self.index] else 0; + switch (character) { + 0 => return false, + ' ', '\t', '\r', '\n' => continue, + '#' => { + if (options.comments) { + while (true) : (self.index += 1) { + switch (self.cmd_line[self.index]) { + '\n' => break, + 0 => return false, + else => continue, + } + } + continue; + } else { + break; + } + }, + else => break, + } + } + return true; + } + + pub fn skip(self: *Self) bool { + if (!self.skipWhitespace()) { + return false; + } + + var backslash_count: usize = 0; + var in_quote = false; + while (true) : (self.index += 1) { + const character = if (self.index != self.cmd_line.len) self.cmd_line[self.index] else 0; + switch (character) { + 0 => return true, + '"', '\'' => { + if (!options.single_quotes and character == '\'') { + backslash_count = 0; + continue; + } + const quote_is_real = backslash_count % 2 == 0; + if (quote_is_real) { + in_quote = !in_quote; + } + }, + '\\' => { + backslash_count += 1; + }, + ' ', '\t', '\r', '\n' => { + if (!in_quote) { + return true; + } + backslash_count = 0; + }, + else => { + backslash_count = 0; + continue; + }, + } + } + } + + /// Returns a slice of the internal buffer that contains the next argument. + /// Returns null when it reaches the end. + pub fn next(self: *Self) ?[:0]const u8 { + if (!self.skipWhitespace()) { + return null; + } + + var backslash_count: usize = 0; + var in_quote = false; + while (true) : (self.index += 1) { + const character = if (self.index != self.cmd_line.len) self.cmd_line[self.index] else 0; + switch (character) { + 0 => { + self.emitBackslashes(backslash_count); + self.buffer[self.end] = 0; + const token = self.buffer[self.start..self.end :0]; + self.end += 1; + self.start = self.end; + return token; + }, + '"', '\'' => { + if (!options.single_quotes and character == '\'') { + self.emitBackslashes(backslash_count); + backslash_count = 0; + self.emitCharacter(character); + continue; + } + const quote_is_real = backslash_count % 2 == 0; + self.emitBackslashes(backslash_count / 2); + backslash_count = 0; + + if (quote_is_real) { + in_quote = !in_quote; + } else { + self.emitCharacter('"'); + } + }, + '\\' => { + backslash_count += 1; + }, + ' ', '\t', '\r', '\n' => { + self.emitBackslashes(backslash_count); + backslash_count = 0; + if (in_quote) { + self.emitCharacter(character); + } else { + self.buffer[self.end] = 0; + const token = self.buffer[self.start..self.end :0]; + self.end += 1; + self.start = self.end; + return token; + } + }, + else => { + self.emitBackslashes(backslash_count); + backslash_count = 0; + self.emitCharacter(character); + }, + } + } + } + + fn emitBackslashes(self: *Self, emit_count: usize) void { + var i: usize = 0; + while (i < emit_count) : (i += 1) { + self.emitCharacter('\\'); + } + } + + fn emitCharacter(self: *Self, char: u8) void { + self.buffer[self.end] = char; + self.end += 1; + } + + /// Call to free the internal buffer of the iterator. + pub fn deinit(self: *Self) void { + self.allocator.free(self.buffer); + + if (self.free_cmd_line_on_deinit) { + self.allocator.free(self.cmd_line); + } + } + }; +} + +test "response file arg parsing" { + try testResponseFileCmdLine( + \\a b + \\c d\ + , &.{ "a", "b", "c", "d\\" }); + try testResponseFileCmdLine("a b c d\\", &.{ "a", "b", "c", "d\\" }); + + try testResponseFileCmdLine( + \\j + \\ k l # this is a comment \\ \\\ \\\\ "none" "\\" "\\\" + \\ "m" #another comment + \\ + , &.{ "j", "k", "l", "m" }); + + try testResponseFileCmdLine( + \\ "" q "" + \\ "r s # t" "u\" v" #another comment + \\ + , &.{ "", "q", "", "r s # t", "u\" v" }); + + try testResponseFileCmdLine( + \\ -l"advapi32" a# b#c d# + \\e\\\ + , &.{ "-ladvapi32", "a#", "b#c", "d#", "e\\\\\\" }); + + try testResponseFileCmdLine( + \\ 'foo' "bar" + , &.{ "foo", "bar" }); +} + +fn testResponseFileCmdLine(input_cmd_line: []const u8, expected_args: []const []const u8) !void { + var it = try ArgIteratorGeneral(.{ .comments = true, .single_quotes = true }) + .init(std.testing.allocator, input_cmd_line); + defer it.deinit(); + for (expected_args) |expected_arg| { + const arg = it.next().?; + try testing.expectEqualStrings(expected_arg, arg); + } + try testing.expect(it.next() == null); +} diff --git a/lib/std/process/Child.zig b/lib/std/process/Child.zig index 7c737c318b32d6f511aa435c95f79a7c08e9a0a8..f2e096bca27b970454ead0ed3f8dd518493ef1d1 100644 --- a/lib/std/process/Child.zig +++ b/lib/std/process/Child.zig @@ -13,12 +13,17 @@ const windows = std.os.windows; const linux = std.os.linux; const posix = std.posix; const mem = std.mem; -const EnvMap = std.process.EnvMap; const maxInt = std.math.maxInt; const assert = std.debug.assert; const Allocator = std.mem.Allocator; const ArrayList = std.ArrayList; +/// Tells whether spawning child processes is supported. +pub const can_spawn = switch (native_os) { + .wasi, .ios, .tvos, .visionos, .watchos => false, + else => true, +}; + pub const Id = switch (native_os) { .windows => windows.HANDLE, .wasi => void, @@ -54,9 +59,8 @@ term: ?(SpawnError!Term), argv: []const []const u8, -/// Leave as null to use the current env map using the supplied allocator. -/// Required if unable to access the current env map (e.g. building a library on -/// some platforms). +parent_environ: process.Environ, +/// `null` means to use `parent_environ` also for the spawned process. env_map: ?*const EnvMap, stdin_behavior: StdIo, @@ -229,15 +233,15 @@ pub const StdIo = enum { }; /// First argument in argv is the executable. -pub fn init(argv: []const []const u8, allocator: Allocator) Child { +pub fn init(gpa: Allocator, argv: []const []const u8, environ: Environ) Child { return .{ - .allocator = allocator, + .allocator = gpa, .argv = argv, + .environ = environ, .id = undefined, .thread_handle = undefined, .err_pipe = if (native_os == .windows) {} else null, .term = null, - .env_map = null, .cwd = null, .uid = if (native_os == .windows or native_os == .wasi) {} else null, .gid = if (native_os == .windows or native_os == .wasi) {} else null, @@ -435,41 +439,38 @@ pub const RunError = posix.GetCwdError || posix.ReadError || SpawnError || posix /// Spawns a child process, waits for it, collecting stdout and stderr, and then returns. /// If it succeeds, the caller owns result.stdout and result.stderr memory. -pub fn run(allocator: Allocator, io: Io, args: struct { +pub fn run(gpa: Allocator, io: Io, args: struct { argv: []const []const u8, + environ: Environ, cwd: ?[]const u8 = null, cwd_dir: ?Io.Dir = null, - /// Required if unable to access the current env map (e.g. building a - /// library on some platforms). - env_map: ?*const EnvMap = null, max_output_bytes: usize = 50 * 1024, expand_arg0: Arg0Expand = .no_expand, progress_node: std.Progress.Node = std.Progress.Node.none, }) RunError!RunResult { - var child = Child.init(args.argv, allocator); + var child = Child.init(gpa, args.argv, args.environ); child.stdin_behavior = .Ignore; child.stdout_behavior = .Pipe; child.stderr_behavior = .Pipe; child.cwd = args.cwd; child.cwd_dir = args.cwd_dir; - child.env_map = args.env_map; child.expand_arg0 = args.expand_arg0; child.progress_node = args.progress_node; var stdout: ArrayList(u8) = .empty; - defer stdout.deinit(allocator); + defer stdout.deinit(gpa); var stderr: ArrayList(u8) = .empty; - defer stderr.deinit(allocator); + defer stderr.deinit(gpa); try child.spawn(io); errdefer { _ = child.kill(io) catch {}; } - try child.collectOutput(allocator, &stdout, &stderr, args.max_output_bytes); + try child.collectOutput(gpa, &stdout, &stderr, args.max_output_bytes); return .{ - .stdout = try stdout.toOwnedSlice(allocator), - .stderr = try stderr.toOwnedSlice(allocator), + .stdout = try stdout.toOwnedSlice(gpa), + .stderr = try stderr.toOwnedSlice(gpa), .term = try child.wait(io), }; } @@ -643,23 +644,16 @@ fn spawnPosix(self: *Child, io: Io) SpawnError!void { const envp: [*:null]const ?[*:0]const u8 = m: { const prog_fd: i32 = if (prog_pipe[1] == -1) -1 else prog_fileno; - if (self.env_map) |env_map| { - break :m (try process.createEnvironFromMap(arena, env_map, .{ + switch (self.environ) { + .empty => break :m (try process.Environ.createBlock(.{ .block = &.{} }, arena, .{ .zig_progress_fd = prog_fd, - })).ptr; - } else if (builtin.link_libc) { - break :m (try process.createEnvironFromExisting(arena, std.c.environ, .{ + })).ptr, + .inherit => |b| break :m (try b.createBlock(arena, .{ .zig_progress_fd = prog_fd, - })).ptr; - } else if (builtin.output_mode == .Exe) { - // Then we have Zig start code and this works. - // TODO type-safety for null-termination of `os.environ`. - break :m (try process.createEnvironFromExisting(arena, @ptrCast(std.os.environ.ptr), .{ + })).ptr, + .map => |m| break :m (try m.createBlock(arena, .{ .zig_progress_fd = prog_fd, - })).ptr; - } else { - // TODO come up with a solution for this. - @panic("missing std lib enhancement: std.process.Child implementation has no way to collect the environment variables to forward to the child process"); + })).ptr, } }; @@ -701,9 +695,15 @@ fn spawnPosix(self: *Child, io: Io) SpawnError!void { posix.kill(posix.getpid(), .STOP) catch |err| forkChildErrReport(io, err_pipe[1], err); } + const parent_PATH: ?[]const u8 = switch(self.environ) { + .empty => null, + .inherit => + .map => |m| m.get("PATH"), + }; + const err = switch (self.expand_arg0) { - .expand => posix.execvpeZ_expandArg0(.expand, argv_buf.ptr[0].?, argv_buf.ptr, envp), - .no_expand => posix.execvpeZ_expandArg0(.no_expand, argv_buf.ptr[0].?, argv_buf.ptr, envp), + .expand => posix.execvpeZ_expandArg0(.expand, argv_buf.ptr[0].?, argv_buf.ptr, envp, parent_PATH), + .no_expand => posix.execvpeZ_expandArg0(.no_expand, argv_buf.ptr[0].?, argv_buf.ptr, envp, parent_PATH), }; forkChildErrReport(io, err_pipe[1], err); } diff --git a/lib/std/process/Environ.zig b/lib/std/process/Environ.zig new file mode 100644 index 0000000000000000000000000000000000000000..5f5386f02d539a29c41d5fd3fd98051dedb7853a --- /dev/null +++ b/lib/std/process/Environ.zig @@ -0,0 +1,764 @@ +const Environ = @This(); + +const builtin = @import("builtin"); +const native_os = builtin.os.tag; + +const std = @import("../std.zig"); +const Allocator = std.mem.Allocator; +const assert = std.debug.assert; +const testing = std.debug.testing; +const unicode = std.unicode; +const posix = std.posix; +const mem = std.mem; + +block: Block, + +pub const Block = switch (native_os) { + .windows => []const u16, + else => []const [*:0]const u8, +}; + +pub const Map = struct { + array_hash_map: ArrayHashMap, + allocator: Allocator, + + const ArrayHashMap = std.ArrayHashMapUnmanaged([]const u8, []const u8, EnvNameHashContext, false); + + pub const Size = usize; + + pub const EnvNameHashContext = struct { + fn upcase(c: u21) u21 { + if (c <= std.math.maxInt(u16)) + return std.os.windows.ntdll.RtlUpcaseUnicodeChar(@as(u16, @intCast(c))); + return c; + } + + pub fn hash(self: @This(), s: []const u8) u32 { + _ = self; + if (native_os == .windows) { + var h = std.hash.Wyhash.init(0); + var it = unicode.Wtf8View.initUnchecked(s).iterator(); + while (it.nextCodepoint()) |cp| { + const cp_upper = upcase(cp); + h.update(&[_]u8{ + @as(u8, @intCast((cp_upper >> 16) & 0xff)), + @as(u8, @intCast((cp_upper >> 8) & 0xff)), + @as(u8, @intCast((cp_upper >> 0) & 0xff)), + }); + } + return h.final(); + } + return std.array_hash_map.hashString(s); + } + + pub fn eql(self: @This(), a: []const u8, b: []const u8, b_index: usize) bool { + _ = self; + _ = b_index; + if (native_os == .windows) { + var it_a = unicode.Wtf8View.initUnchecked(a).iterator(); + var it_b = unicode.Wtf8View.initUnchecked(b).iterator(); + while (true) { + const c_a = it_a.nextCodepoint() orelse break; + const c_b = it_b.nextCodepoint() orelse return false; + if (upcase(c_a) != upcase(c_b)) + return false; + } + return if (it_b.nextCodepoint()) |_| false else true; + } + return std.array_hash_map.eqlString(a, b); + } + }; + + /// Create a Map backed by a specific allocator. + /// That allocator will be used for both backing allocations + /// and string deduplication. + pub fn init(allocator: Allocator) Map { + return .{ .array_hash_map = .empty, .allocator = allocator }; + } + + /// Free the backing storage of the map, as well as all + /// of the stored keys and values. + pub fn deinit(self: *Map) void { + const gpa = self.allocator; + var it = self.array_hash_map.iterator(); + while (it.next()) |entry| { + gpa.free(entry.key_ptr.*); + gpa.free(entry.value_ptr.*); + } + self.array_hash_map.deinit(gpa); + self.* = undefined; + } + + pub fn keys(m: *Map) [][]const u8 { + return m.array_hash_map.keys(); + } + + pub fn values(m: *Map) [][]const u8 { + return m.array_hash_map.values(); + } + + /// Same as `put` but the key and value become owned by the Map rather + /// than being copied. + /// If `putMove` fails, the ownership of key and value does not transfer. + /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string. + pub fn putMove(self: *Map, key: []u8, value: []u8) !void { + const gpa = self.allocator; + assert(unicode.wtf8ValidateSlice(key)); + const get_or_put = try self.array_hash_map.getOrPut(gpa, key); + if (get_or_put.found_existing) { + gpa.free(get_or_put.key_ptr.*); + gpa.free(get_or_put.value_ptr.*); + get_or_put.key_ptr.* = key; + } + get_or_put.value_ptr.* = value; + } + + /// `key` and `value` are copied into the Map. + /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string. + pub fn put(self: *Map, key: []const u8, value: []const u8) !void { + assert(unicode.wtf8ValidateSlice(key)); + const gpa = self.allocator; + const value_copy = try gpa.dupe(u8, value); + errdefer gpa.free(value_copy); + const get_or_put = try self.array_hash_map.getOrPut(gpa, key); + errdefer { + if (!get_or_put.found_existing) assert(self.array_hash_map.pop() != null); + } + if (get_or_put.found_existing) { + gpa.free(get_or_put.value_ptr.*); + } else { + get_or_put.key_ptr.* = try gpa.dupe(u8, key); + } + get_or_put.value_ptr.* = value_copy; + } + + /// Find the address of the value associated with a key. + /// The returned pointer is invalidated if the map resizes. + /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string. + pub fn getPtr(self: Map, key: []const u8) ?*[]const u8 { + assert(unicode.wtf8ValidateSlice(key)); + return self.array_hash_map.getPtr(key); + } + + /// Return the map's copy of the value associated with + /// a key. The returned string is invalidated if this + /// key is removed from the map. + /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string. + pub fn get(self: Map, key: []const u8) ?[]const u8 { + assert(unicode.wtf8ValidateSlice(key)); + return self.array_hash_map.get(key); + } + + pub fn contains(m: *const Map, key: []const u8) bool { + return m.contains(key); + } + + /// If there is an entry with a matching key, it is deleted from the hash + /// map. The entry is removed from the underlying array by swapping it with + /// the last element. + /// + /// Returns true if an entry was removed, false otherwise. + /// + /// This invalidates the value returned by get() for this key. + /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string. + pub fn swapRemove(self: *Map, key: []const u8) bool { + assert(unicode.wtf8ValidateSlice(key)); + const kv = self.array_hash_map.fetchSwapRemove(key) orelse return false; + const gpa = self.allocator; + gpa.free(kv.key); + gpa.free(kv.value); + return true; + } + + /// If there is an entry with a matching key, it is deleted from the map. + /// The entry is removed from the underlying array by shifting all elements + /// forward, thereby maintaining the current ordering. + /// + /// Returns true if an entry was removed, false otherwise. + /// + /// This invalidates the value returned by get() for this key. + /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string. + pub fn orderedRemove(self: *Map, key: []const u8) bool { + assert(unicode.wtf8ValidateSlice(key)); + const kv = self.array_hash_map.fetchOrderedRemove(key) orelse return false; + const gpa = self.allocator; + gpa.free(kv.key); + gpa.free(kv.value); + return true; + } + + /// Returns the number of KV pairs stored in the map. + pub fn count(self: Map) Size { + return self.array_hash_map.count(); + } + + /// Returns an iterator over entries in the map. + pub fn iterator(self: *const Map) ArrayHashMap.Iterator { + return self.array_hash_map.iterator(); + } + + /// Returns a full copy of `em` allocated with `gpa`, which is not necessarily + /// the same allocator used to allocate `em`. + pub fn clone(m: *const Map, gpa: Allocator) Allocator.Error!Map { + // Since we need to dupe the keys and values, the only way for error handling to not be a + // nightmare is to add keys to an empty map one-by-one. This could be avoided if this + // abstraction were a bit less... OOP-esque. + var new: Map = .init(gpa); + errdefer new.deinit(); + try new.array_hash_map.ensureUnusedCapacity(gpa, m.array_hash_map.count()); + for (m.array_hash_map.keys(), m.array_hash_map.values()) |key, value| { + try new.put(key, value); + } + return new; + } + + /// Creates a null-delimited environment variable block in the format + /// expected by POSIX, from a hash map plus options. + pub fn createBlock( + map: *const Map, + arena: Allocator, + options: CreateBlockOptions, + ) Allocator.Error![:null]?[*:0]u8 { + const ZigProgressAction = enum { nothing, edit, delete, add }; + const zig_progress_action: ZigProgressAction = a: { + const fd = options.zig_progress_fd orelse break :a .nothing; + const exists = map.get("ZIG_PROGRESS") != null; + if (fd >= 0) { + break :a if (exists) .edit else .add; + } else { + if (exists) break :a .delete; + } + break :a .nothing; + }; + + const envp_count: usize = c: { + var c: usize = map.count(); + switch (zig_progress_action) { + .add => c += 1, + .delete => c -= 1, + .nothing, .edit => {}, + } + break :c c; + }; + + const envp_buf = try arena.allocSentinel(?[*:0]u8, envp_count, null); + var i: usize = 0; + + if (zig_progress_action == .add) { + envp_buf[i] = try std.fmt.allocPrintSentinel(arena, "ZIG_PROGRESS={d}", .{options.zig_progress_fd.?}, 0); + i += 1; + } + + { + var it = map.iterator(); + while (it.next()) |pair| { + if (mem.eql(u8, pair.key_ptr.*, "ZIG_PROGRESS")) switch (zig_progress_action) { + .add => unreachable, + .delete => continue, + .edit => { + envp_buf[i] = try std.fmt.allocPrintSentinel(arena, "{s}={d}", .{ + pair.key_ptr.*, options.zig_progress_fd.?, + }, 0); + i += 1; + continue; + }, + .nothing => {}, + }; + + envp_buf[i] = try std.fmt.allocPrintSentinel(arena, "{s}={s}", .{ pair.key_ptr.*, pair.value_ptr.* }, 0); + i += 1; + } + } + + assert(i == envp_count); + return envp_buf; + } +}; + +pub const CreateMapError = error{ + OutOfMemory, + /// WASI-only. `environ_sizes_get` or `environ_get` failed for an + /// unexpected reason. + Unexpected, +}; + +/// Allocates a `Map` and copies environment block into it. +pub fn createMap(env: Environ, allocator: Allocator) CreateMapError!Map { + var result = Map.init(allocator); + errdefer result.deinit(); + + if (native_os == .windows) { + const ptr = env.block; + + var i: usize = 0; + while (ptr[i] != 0) { + const key_start = i; + + // There are some special environment variables that start with =, + // so we need a special case to not treat = as a key/value separator + // if it's the first character. + // https://devblogs.microsoft.com/oldnewthing/20100506-00/?p=14133 + if (ptr[key_start] == '=') i += 1; + + while (ptr[i] != 0 and ptr[i] != '=') : (i += 1) {} + const key_w = ptr[key_start..i]; + const key = try unicode.wtf16LeToWtf8Alloc(allocator, key_w); + errdefer allocator.free(key); + + if (ptr[i] == '=') i += 1; + + const value_start = i; + while (ptr[i] != 0) : (i += 1) {} + const value_w = ptr[value_start..i]; + const value = try unicode.wtf16LeToWtf8Alloc(allocator, value_w); + errdefer allocator.free(value); + + i += 1; // skip over null byte + + try result.putMove(key, value); + } + return result; + } else if (native_os == .wasi and !builtin.link_libc) { + var environ_count: usize = undefined; + var environ_buf_size: usize = undefined; + + const environ_sizes_get_ret = std.os.wasi.environ_sizes_get(&environ_count, &environ_buf_size); + if (environ_sizes_get_ret != .SUCCESS) { + return posix.unexpectedErrno(environ_sizes_get_ret); + } + + if (environ_count == 0) { + return result; + } + + const environ = try allocator.alloc([*:0]u8, environ_count); + defer allocator.free(environ); + const environ_buf = try allocator.alloc(u8, environ_buf_size); + defer allocator.free(environ_buf); + + const environ_get_ret = std.os.wasi.environ_get(environ.ptr, environ_buf.ptr); + if (environ_get_ret != .SUCCESS) { + return posix.unexpectedErrno(environ_get_ret); + } + + for (environ) |line| { + const pair = mem.sliceTo(line, 0); + var parts = mem.splitScalar(u8, pair, '='); + const key = parts.first(); + const value = parts.rest(); + try result.put(key, value); + } + return result; + } else if (builtin.link_libc) { + var ptr = env.block; + while (ptr[0]) |line| : (ptr += 1) { + var line_i: usize = 0; + while (line[line_i] != 0 and line[line_i] != '=') : (line_i += 1) {} + const key = line[0..line_i]; + + var end_i: usize = line_i; + while (line[end_i] != 0) : (end_i += 1) {} + const value = line[line_i + 1 .. end_i]; + + try result.put(key, value); + } + return result; + } else { + for (env.block) |line| { + var line_i: usize = 0; + while (line[line_i] != 0 and line[line_i] != '=') : (line_i += 1) {} + const key = line[0..line_i]; + + var end_i: usize = line_i; + while (line[end_i] != 0) : (end_i += 1) {} + const value = line[line_i + 1 .. end_i]; + + try result.put(key, value); + } + return result; + } +} + +test createMap { + var env = try createMap(testing.allocator); + defer env.deinit(); +} + +pub const GetEnvVarOwnedError = error{ + OutOfMemory, + EnvironmentVariableNotFound, + + /// On Windows, environment variable keys provided by the user must be valid WTF-8. + /// https://wtf-8.codeberg.page/ + InvalidWtf8, +}; + +/// Caller must free returned memory. +/// On Windows, if `key` is not valid [WTF-8](https://wtf-8.codeberg.page/), +/// then `error.InvalidWtf8` is returned. +/// On Windows, the value is encoded as [WTF-8](https://wtf-8.codeberg.page/). +/// On other platforms, the value is an opaque sequence of bytes with no particular encoding. +pub fn getEnvVarOwned(allocator: Allocator, key: []const u8) GetEnvVarOwnedError![]u8 { + if (native_os == .windows) { + const result_w = blk: { + var stack_alloc = std.heap.stackFallback(256 * @sizeOf(u16), allocator); + const stack_allocator = stack_alloc.get(); + const key_w = try unicode.wtf8ToWtf16LeAllocZ(stack_allocator, key); + defer stack_allocator.free(key_w); + + break :blk getenvW(key_w) orelse return error.EnvironmentVariableNotFound; + }; + // wtf16LeToWtf8Alloc can only fail with OutOfMemory + return unicode.wtf16LeToWtf8Alloc(allocator, result_w); + } else if (native_os == .wasi and !builtin.link_libc) { + var envmap = createMap(allocator) catch return error.OutOfMemory; + defer envmap.deinit(); + const val = envmap.get(key) orelse return error.EnvironmentVariableNotFound; + return allocator.dupe(u8, val); + } else { + const result = posix.getenv(key) orelse return error.EnvironmentVariableNotFound; + return allocator.dupe(u8, result); + } +} + +/// On Windows, `key` must be valid WTF-8. +pub inline fn hasEnvVarConstant(comptime key: []const u8) bool { + if (native_os == .windows) { + const key_w = comptime unicode.wtf8ToWtf16LeStringLiteral(key); + return getenvW(key_w) != null; + } else if (native_os == .wasi and !builtin.link_libc) { + return false; + } else { + return posix.getenv(key) != null; + } +} + +/// On Windows, `key` must be valid WTF-8. +pub inline fn hasNonEmptyEnvVarConstant(comptime key: []const u8) bool { + if (native_os == .windows) { + const key_w = comptime unicode.wtf8ToWtf16LeStringLiteral(key); + const value = getenvW(key_w) orelse return false; + return value.len != 0; + } else if (native_os == .wasi and !builtin.link_libc) { + return false; + } else { + const value = posix.getenv(key) orelse return false; + return value.len != 0; + } +} + +pub const ParseIntError = std.fmt.ParseIntError || error{EnvironmentVariableNotFound}; + +/// Parses an environment variable as an integer. +/// +/// On Windows, `key` must be valid WTF-8. +pub fn parseInt(io: std.Io, key: []const u8, comptime I: type, base: u8) ParseIntError!I { + const text = io.environ(key) orelse return error.EnvironmentVariableNotFound; + return std.fmt.parseInt(I, text, base); +} + +pub const HasEnvVarError = error{ + OutOfMemory, + + /// On Windows, environment variable keys provided by the user must be valid WTF-8. + /// https://wtf-8.codeberg.page/ + InvalidWtf8, +}; + +/// On Windows, if `key` is not valid [WTF-8](https://wtf-8.codeberg.page/), +/// then `error.InvalidWtf8` is returned. +pub fn hasEnvVar(allocator: Allocator, key: []const u8) HasEnvVarError!bool { + if (native_os == .windows) { + var stack_alloc = std.heap.stackFallback(256 * @sizeOf(u16), allocator); + const stack_allocator = stack_alloc.get(); + const key_w = try unicode.wtf8ToWtf16LeAllocZ(stack_allocator, key); + defer stack_allocator.free(key_w); + return getenvW(key_w) != null; + } else if (native_os == .wasi and !builtin.link_libc) { + var envmap = createMap(allocator) catch return error.OutOfMemory; + defer envmap.deinit(); + return envmap.getPtr(key) != null; + } else { + return posix.getenv(key) != null; + } +} + +/// On Windows, if `key` is not valid [WTF-8](https://wtf-8.codeberg.page/), +/// then `error.InvalidWtf8` is returned. +pub fn hasNonEmptyEnvVar(allocator: Allocator, key: []const u8) HasEnvVarError!bool { + if (native_os == .windows) { + var stack_alloc = std.heap.stackFallback(256 * @sizeOf(u16), allocator); + const stack_allocator = stack_alloc.get(); + const key_w = try unicode.wtf8ToWtf16LeAllocZ(stack_allocator, key); + defer stack_allocator.free(key_w); + const value = getenvW(key_w) orelse return false; + return value.len != 0; + } else if (native_os == .wasi and !builtin.link_libc) { + var envmap = createMap(allocator) catch return error.OutOfMemory; + defer envmap.deinit(); + const value = envmap.getPtr(key) orelse return false; + return value.len != 0; + } else { + const value = posix.getenv(key) orelse return false; + return value.len != 0; + } +} + +/// Windows-only. Get an environment variable with a null-terminated, WTF-16 encoded name. +/// The returned slice points to memory in the PEB. +/// +/// This function performs a Unicode-aware case-insensitive lookup using RtlEqualUnicodeString. +/// +/// See also: +/// * `std.posix.getenv` +/// * `createMap` +/// * `getEnvVarOwned` +/// * `hasEnvVarConstant` +/// * `hasEnvVar` +pub fn getenvW(key: [*:0]const u16) ?[:0]const u16 { + if (native_os != .windows) { + @compileError("Windows-only"); + } + const key_slice = mem.sliceTo(key, 0); + // '=' anywhere but the start makes this an invalid environment variable name + if (key_slice.len > 0 and std.mem.findScalar(u16, key_slice[1..], '=') != null) { + return null; + } + const ptr = std.os.windows.peb().ProcessParameters.Environment; + var i: usize = 0; + while (ptr[i] != 0) { + const key_value = mem.sliceTo(ptr[i..], 0); + + // There are some special environment variables that start with =, + // so we need a special case to not treat = as a key/value separator + // if it's the first character. + // https://devblogs.microsoft.com/oldnewthing/20100506-00/?p=14133 + const equal_search_start: usize = if (key_value[0] == '=') 1 else 0; + const equal_index = std.mem.findScalarPos(u16, key_value, equal_search_start, '=') orelse { + // This is enforced by CreateProcess. + // If violated, CreateProcess will fail with INVALID_PARAMETER. + unreachable; // must contain a = + }; + + const this_key = key_value[0..equal_index]; + if (std.os.windows.eqlIgnoreCaseWtf16(key_slice, this_key)) { + return key_value[equal_index + 1 ..]; + } + + // skip past the NUL terminator + i += key_value.len + 1; + } + return null; +} + +test getEnvVarOwned { + try testing.expectError( + error.EnvironmentVariableNotFound, + getEnvVarOwned(std.testing.allocator, "BADENV"), + ); +} + +test hasEnvVarConstant { + if (native_os == .wasi and !builtin.link_libc) return error.SkipZigTest; + + try testing.expect(!hasEnvVarConstant("BADENV")); +} + +test hasEnvVar { + const has_env = try hasEnvVar(std.testing.allocator, "BADENV"); + try testing.expect(!has_env); +} + +pub const CreateBlockOptions = struct { + /// `null` means to leave the `ZIG_PROGRESS` environment variable unmodified. + /// If non-null, negative means to remove the environment variable, and >= 0 + /// means to provide it with the given integer. + zig_progress_fd: ?i32 = null, +}; + +/// Creates a null-delimited environment variable block in the format expected +/// by POSIX, from a different one. +pub fn createBlock(existing: Environ, arena: Allocator, options: CreateBlockOptions) Allocator.Error![:null]?[*:0]u8 { + const existing_count, const contains_zig_progress = c: { + var count: usize = 0; + var contains = false; + while (existing.block[count]) |line| : (count += 1) { + contains = contains or mem.eql(u8, mem.sliceTo(line, '='), "ZIG_PROGRESS"); + } + break :c .{ count, contains }; + }; + const ZigProgressAction = enum { nothing, edit, delete, add }; + const zig_progress_action: ZigProgressAction = a: { + const fd = options.zig_progress_fd orelse break :a .nothing; + if (fd >= 0) { + break :a if (contains_zig_progress) .edit else .add; + } else { + if (contains_zig_progress) break :a .delete; + } + break :a .nothing; + }; + + const envp_count: usize = c: { + var count: usize = existing_count; + switch (zig_progress_action) { + .add => count += 1, + .delete => count -= 1, + .nothing, .edit => {}, + } + break :c count; + }; + + const envp_buf = try arena.allocSentinel(?[*:0]u8, envp_count, null); + var i: usize = 0; + var existing_index: usize = 0; + + if (zig_progress_action == .add) { + envp_buf[i] = try std.fmt.allocPrintSentinel(arena, "ZIG_PROGRESS={d}", .{options.zig_progress_fd.?}, 0); + i += 1; + } + + while (existing.block[existing_index]) |line| : (existing_index += 1) { + if (mem.eql(u8, mem.sliceTo(line, '='), "ZIG_PROGRESS")) switch (zig_progress_action) { + .add => unreachable, + .delete => continue, + .edit => { + envp_buf[i] = try std.fmt.allocPrintSentinel(arena, "ZIG_PROGRESS={d}", .{options.zig_progress_fd.?}, 0); + i += 1; + continue; + }, + .nothing => {}, + }; + envp_buf[i] = try arena.dupeZ(u8, mem.span(line)); + i += 1; + } + + assert(i == envp_count); + return envp_buf; +} + +test "Map.createBlock" { + const allocator = testing.allocator; + var envmap = Map.init(allocator); + defer envmap.deinit(); + + try envmap.put("HOME", "/home/ifreund"); + try envmap.put("WAYLAND_DISPLAY", "wayland-1"); + try envmap.put("DISPLAY", ":1"); + try envmap.put("DEBUGINFOD_URLS", " "); + try envmap.put("XCURSOR_SIZE", "24"); + + var arena = std.heap.ArenaAllocator.init(allocator); + defer arena.deinit(); + const environ = try envmap.createBlock(arena.allocator(), .{}); + + try testing.expectEqual(@as(usize, 5), environ.len); + + inline for (.{ + "HOME=/home/ifreund", + "WAYLAND_DISPLAY=wayland-1", + "DISPLAY=:1", + "DEBUGINFOD_URLS= ", + "XCURSOR_SIZE=24", + }) |target| { + for (environ) |variable| { + if (mem.eql(u8, mem.span(variable orelse continue), target)) break; + } else { + try testing.expect(false); // Environment variable not found + } + } +} + +/// Caller must free result. +pub fn createWindowsEnvBlock(allocator: mem.Allocator, env_map: *const Map) ![]u16 { + // count bytes needed + const max_chars_needed = x: { + // Only need 2 trailing NUL code units for an empty environment + var max_chars_needed: usize = if (env_map.count() == 0) 2 else 1; + var it = env_map.iterator(); + while (it.next()) |pair| { + // +1 for '=' + // +1 for null byte + max_chars_needed += pair.key_ptr.len + pair.value_ptr.len + 2; + } + break :x max_chars_needed; + }; + const result = try allocator.alloc(u16, max_chars_needed); + errdefer allocator.free(result); + + var it = env_map.iterator(); + var i: usize = 0; + while (it.next()) |pair| { + i += try unicode.wtf8ToWtf16Le(result[i..], pair.key_ptr.*); + result[i] = '='; + i += 1; + i += try unicode.wtf8ToWtf16Le(result[i..], pair.value_ptr.*); + result[i] = 0; + i += 1; + } + result[i] = 0; + i += 1; + // An empty environment is a special case that requires a redundant + // NUL terminator. CreateProcess will read the second code unit even + // though theoretically the first should be enough to recognize that the + // environment is empty (see https://nullprogram.com/blog/2023/08/23/) + if (env_map.count() == 0) { + result[i] = 0; + i += 1; + } + return try allocator.realloc(result, i); +} + +test Map { + var env = Map.init(testing.allocator); + defer env.deinit(); + + try env.put("SOMETHING_NEW", "hello"); + try testing.expectEqualStrings("hello", env.get("SOMETHING_NEW").?); + try testing.expectEqual(@as(Map.Size, 1), env.count()); + + // overwrite + try env.put("SOMETHING_NEW", "something"); + try testing.expectEqualStrings("something", env.get("SOMETHING_NEW").?); + try testing.expectEqual(@as(Map.Size, 1), env.count()); + + // a new longer name to test the Windows-specific conversion buffer + try env.put("SOMETHING_NEW_AND_LONGER", "1"); + try testing.expectEqualStrings("1", env.get("SOMETHING_NEW_AND_LONGER").?); + try testing.expectEqual(@as(Map.Size, 2), env.count()); + + // case insensitivity on Windows only + if (native_os == .windows) { + try testing.expectEqualStrings("1", env.get("something_New_aNd_LONGER").?); + } else { + try testing.expect(null == env.get("something_New_aNd_LONGER")); + } + + var it = env.iterator(); + var count: Map.Size = 0; + while (it.next()) |entry| { + const is_an_expected_name = std.mem.eql(u8, "SOMETHING_NEW", entry.key_ptr.*) or std.mem.eql(u8, "SOMETHING_NEW_AND_LONGER", entry.key_ptr.*); + try testing.expect(is_an_expected_name); + count += 1; + } + try testing.expectEqual(@as(Map.Size, 2), count); + + env.remove("SOMETHING_NEW"); + try testing.expect(env.get("SOMETHING_NEW") == null); + + try testing.expectEqual(@as(Map.Size, 1), env.count()); + + if (native_os == .windows) { + // test Unicode case-insensitivity on Windows + try env.put("КИРиллИЦА", "something else"); + try testing.expectEqualStrings("something else", env.get("кириллица").?); + + // and WTF-8 that's not valid UTF-8 + const wtf8_with_surrogate_pair = try unicode.wtf16LeToWtf8Alloc(testing.allocator, &[_]u16{ + std.mem.nativeToLittle(u16, 0xD83D), // unpaired high surrogate + }); + defer testing.allocator.free(wtf8_with_surrogate_pair); + + try env.put(wtf8_with_surrogate_pair, wtf8_with_surrogate_pair); + try testing.expectEqualSlices(u8, wtf8_with_surrogate_pair, env.get(wtf8_with_surrogate_pair).?); + } +} diff --git a/lib/std/start.zig b/lib/std/start.zig index c6a9f06724e965482cc67a8021aebff67f59d265..51a498c554261fb2e48fc80ce49bac9c07030fc9 100644 --- a/lib/std/start.zig +++ b/lib/std/start.zig @@ -524,7 +524,10 @@ fn WinStartup() callconv(.withStackAlign(.c, 1)) noreturn { std.debug.maybeEnableSegfaultHandler(); - std.os.windows.ntdll.RtlExitUserProcess(callMain()); + std.os.windows.ntdll.RtlExitUserProcess(callMain( + std.os.windows.peb().ProcessParameters.CommandLine, + std.os.windows.peb().ProcessParameters.Environment, + )); } fn wWinMainCRTStartup() callconv(.withStackAlign(.c, 1)) noreturn { @@ -666,17 +669,12 @@ fn expandStackSize(phdrs: []elf.Phdr) void { } inline fn callMainWithArgs(argc: usize, argv: [*][*:0]u8, envp: [][*:0]u8) u8 { - std.os.argv = argv[0..argc]; - std.os.environ = envp; - if (std.Options.debug_threaded_io) |t| { if (@sizeOf(std.Io.Threaded.Argv0) != 0) t.argv0.value = argv[0]; t.environ = .{ .block = envp }; } - std.debug.maybeEnableSegfaultHandler(); - - return callMain(); + return callMain(argv[0..argc], envp); } fn main(c_argc: c_int, c_argv: [*][*:0]c_char, c_envp: [*:null]?[*:0]c_char) callconv(.c) c_int { @@ -695,62 +693,94 @@ fn main(c_argc: c_int, c_argv: [*][*:0]c_char, c_envp: [*:null]?[*:0]c_char) cal } fn mainWithoutEnv(c_argc: c_int, c_argv: [*][*:0]c_char) callconv(.c) c_int { - std.os.argv = @as([*][*:0]u8, @ptrCast(c_argv))[0..@intCast(c_argc)]; - + const argv = @as([*][*:0]u8, @ptrCast(c_argv))[0..@intCast(c_argc)]; if (@sizeOf(std.Io.Threaded.Argv0) != 0) { - if (std.Options.debug_threaded_io) |t| t.argv0.value = std.os.argv[0]; + if (std.Options.debug_threaded_io) |t| t.argv0.value = argv[0]; } - - return callMain(); + return callMain(argv, &.{}); } -// General error message for a malformed return type +/// General error message for a malformed return type const bad_main_ret = "expected return type of main to be 'void', '!void', 'noreturn', 'u8', or '!u8'"; -pub inline fn callMain() u8 { - const ReturnType = @typeInfo(@TypeOf(root.main)).@"fn".return_type.?; +const use_debug_allocator = !builtin.link_libc and !native_arch.isWasm() and builtin.mode == .Debug; +var debug_allocator: std.heap.DebugAllocator(.{}) = .init; +inline fn callMain(args: std.process.Args.Vector, environ: std.process.Environ.Block) u8 { + const fn_info = @typeInfo(@TypeOf(root.main)).@"fn"; + if (fn_info.params.len == 0) return wrapMain(root.main()); + if (fn_info.params[0].type.? == std.process.Init.Minimal) return wrapMain(root.main(.{ + .args = .{ .vector = args }, + .environ = .{ .block = environ }, + })); + + const gpa = if (builtin.link_libc) + std.heap.c_allocator + else if (native_arch.isWasm()) + std.heap.wasm_allocator + else if (use_debug_allocator) + debug_allocator.allocator() + else + std.heap.smp_allocator; + + defer if (use_debug_allocator) switch (debug_allocator.deinit()) { + .leak => std.process.exit(1), + .ok => {}, + }; + + var arena_allocator = std.heap.ArenaAllocator.init(std.heap.page_allocator); + defer arena_allocator.deinit(); + + var threaded: std.Io.Threaded = .init(gpa, .{ + .argv0 = if (@sizeOf(std.Io.Threaded.Argv0) != 0) .{ .value = args[0] } else .{}, + .environ = environ, + }); + defer threaded.deinit(); + + var env_map = environ.getEnvMap(gpa) catch |err| + std.process.fatal("failed to parse environment variables: {t}", .{err}); + defer env_map.deinit(); + + return wrapMain(root.main(.{ + .minimal = .{ + .args = .{ .vector = args }, + .environ = .{ .block = environ }, + }, + .arena = &arena_allocator, + .gpa = gpa, + .io = threaded.io(), + .env_map = env_map, + })); +} + +inline fn wrapMain(result: anytype) u8 { + const ReturnType = @TypeOf(result); switch (ReturnType) { - void => { - root.main(); - return 0; - }, - noreturn, u8 => { - return root.main(); - }, - else => { - if (@typeInfo(ReturnType) != .error_union) @compileError(bad_main_ret); - - const result = root.main() catch |err| { - switch (builtin.zig_backend) { - .stage2_powerpc, - .stage2_riscv64, - => { - _ = std.posix.write(std.posix.STDERR_FILENO, "error: failed with error\n") catch {}; - return 1; - }, - else => {}, - } - std.log.err("{s}", .{@errorName(err)}); - switch (native_os) { - .freestanding, .other => {}, - else => if (@errorReturnTrace()) |trace| { - std.debug.dumpStackTrace(trace); - }, - } - return 1; - }; - - return switch (@TypeOf(result)) { - void => 0, - u8 => result, - else => @compileError(bad_main_ret), - }; - }, + void => return 0, + noreturn => unreachable, + u8 => return result, + else => {}, } + if (@typeInfo(ReturnType) != .error_union) @compileError(bad_main_ret); + + const unwrapped_result = result catch |err| { + std.log.err("{t}", .{err}); + switch (native_os) { + .freestanding, .other => {}, + else => if (@errorReturnTrace()) |trace| std.debug.dumpStackTrace(trace), + } + return 1; + }; + + return switch (@TypeOf(unwrapped_result)) { + noreturn => unreachable, + void => 0, + u8 => unwrapped_result, + else => @compileError(bad_main_ret), + }; } -pub fn call_wWinMain() std.os.windows.INT { +fn call_wWinMain() std.os.windows.INT { const peb = std.os.windows.peb(); const MAIN_HINSTANCE = @typeInfo(@TypeOf(root.wWinMain)).@"fn".params[0].type.?; const hInstance: MAIN_HINSTANCE = @ptrCast(peb.ImageBaseAddress); diff --git a/lib/std/std.zig b/lib/std/std.zig index 6ec39306ea949d257a572f90fcc917724979f97d..c4cb3b153701cf883b29a31eb969bed3edf1b841 100644 --- a/lib/std/std.zig +++ b/lib/std/std.zig @@ -114,9 +114,6 @@ pub const options: Options = if (@hasDecl(root, "std_options")) root.std_options pub const Options = struct { enable_segfault_handler: bool = debug.default_enable_segfault_handler, - /// Function used to implement `std.Io.Dir.cwd` for WASI. - wasiCwd: fn () os.wasi.fd_t = os.defaultWasiCwd, - /// The current log level. log_level: log.Level = log.default_level, @@ -193,6 +190,9 @@ pub const Options = struct { /// Overrides `std.Io.File.Permissions`. pub const FilePermissions: ?type = if (@hasDecl(root, "std_options_FilePermissions")) root.std_options_FilePermissions else null; + + /// Overrides `std.Io.Dir.cwd`. + pub const cwd: ?fn () Io.Dir = if (@hasDecl(root, "std_options_cwd")) root.std_options_cwd else null; }; // This forces the start.zig file to be imported, and the comptime logic inside that diff --git a/lib/std/zig.zig b/lib/std/zig.zig index abc213ba27a93920df72ed6d77e74ea08abcc011..5524cac22d3e4e5a0adaba9fd50f8a09d7c9b8b1 100644 --- a/lib/std/zig.zig +++ b/lib/std/zig.zig @@ -746,21 +746,12 @@ pub const EnvVar = enum { LOCALAPPDATA, HOME, - pub fn isSet(comptime ev: EnvVar) bool { - return std.process.hasNonEmptyEnvVarConstant(@tagName(ev)); + pub fn isSet(ev: EnvVar, map: *const std.process.Environ.Map) bool { + return map.contains(@tagName(ev)); } - pub fn get(ev: EnvVar, arena: std.mem.Allocator) !?[]u8 { - if (std.process.getEnvVarOwned(arena, @tagName(ev))) |value| { - return value; - } else |err| switch (err) { - error.EnvironmentVariableNotFound => return null, - else => |e| return e, - } - } - - pub fn getPosix(comptime ev: EnvVar) ?[:0]const u8 { - return std.posix.getenvZ(@tagName(ev)); + pub fn get(ev: EnvVar, map: *const std.process.Environ.Map) ?[]const u8 { + return map.get(@tagName(ev)); } }; diff --git a/lib/std/zig/system/darwin.zig b/lib/std/zig/system/darwin.zig index b493ccf0ec378bd24fd33fbea534b00f07c53fb0..e69de48d262ec9daed3bdf17e41a65b458f9e6e4 100644 --- a/lib/std/zig/system/darwin.zig +++ b/lib/std/zig/system/darwin.zig @@ -35,7 +35,7 @@ pub fn isSdkInstalled(gpa: Allocator, io: Io) bool { /// Caller owns the memory. /// stderr from xcrun is ignored. /// If error.OutOfMemory occurs in Allocator, this function returns null. -pub fn getSdk(gpa: Allocator, io: Io, target: *const Target) ?[]const u8 { +pub fn getSdk(gpa: Allocator, io: Io, environ: std.process.Child.Environ, target: *const Target) ?[]const u8 { const is_simulator_abi = target.abi == .simulator; const sdk = switch (target.os.tag) { .driverkit => "driverkit", @@ -47,7 +47,10 @@ pub fn getSdk(gpa: Allocator, io: Io, target: *const Target) ?[]const u8 { else => return null, }; const argv = &[_][]const u8{ "xcrun", "--sdk", sdk, "--show-sdk-path" }; - const result = std.process.Child.run(gpa, io, .{ .argv = argv }) catch return null; + const result = std.process.Child.run(gpa, io, .{ + .argv = argv, + .environ = environ, + }) catch return null; defer { gpa.free(result.stderr); gpa.free(result.stdout); diff --git a/test/link/macho.zig b/test/link/macho.zig index ccfecefa4402e235a7bcfb525e93444f6e047861..844273b8e546840507d7c8ca5a02f77d725abf04 100644 --- a/test/link/macho.zig +++ b/test/link/macho.zig @@ -871,7 +871,7 @@ fn testLinkDirectlyCppTbd(b: *Build, opts: Options) *Step { const io = b.graph.io; const test_step = addTestStep(b, "link-directly-cpp-tbd", opts); - const sdk = std.zig.system.darwin.getSdk(b.allocator, io, &opts.target.result) orelse + const sdk = std.zig.system.darwin.getSdk(b.allocator, io, .{ .map = &b.graph.env_map }, &opts.target.result) orelse @panic("macOS SDK is required to run the test"); const exe = addExecutable(b, opts, .{ diff --git a/test/standalone/ios/build.zig b/test/standalone/ios/build.zig index b87d55993b6615c60700cb05d39cd8148be717fb..d9bd93875b11afc1351474d1ce3e91935b214d75 100644 --- a/test/standalone/ios/build.zig +++ b/test/standalone/ios/build.zig @@ -25,7 +25,7 @@ pub fn build(b: *std.Build) void { const io = b.graph.io; - if (std.zig.system.darwin.getSdk(b.allocator, io, &target.result)) |sdk| { + if (std.zig.system.darwin.getSdk(b.allocator, io, .{ .map = &b.graph.env_map }, &target.result)) |sdk| { b.sysroot = sdk; exe.root_module.addSystemIncludePath(.{ .cwd_relative = b.pathJoin(&.{ sdk, "/usr/include" }) }); exe.root_module.addSystemFrameworkPath(.{ .cwd_relative = b.pathJoin(&.{ sdk, "/System/Library/Frameworks" }) }); diff --git a/test/standalone/windows_bat_args/fuzz.zig b/test/standalone/windows_bat_args/fuzz.zig index 28749259f7d832ed5de43ed2ed552adb7ac1a8ad..f0da2321b13870302594fb1446142ce86188f479 100644 --- a/test/standalone/windows_bat_args/fuzz.zig +++ b/test/standalone/windows_bat_args/fuzz.zig @@ -84,13 +84,13 @@ pub fn main() anyerror!void { } } -fn testExec(gpa: Allocator, io: Io, args: []const []const u8, env: ?*std.process.EnvMap) !void { +fn testExec(gpa: Allocator, io: Io, args: []const []const u8, env: ?*std.process.Environ.Map) !void { try testExecBat(gpa, io, "args1.bat", args, env); try testExecBat(gpa, io, "args2.bat", args, env); try testExecBat(gpa, io, "args3.bat", args, env); } -fn testExecBat(gpa: Allocator, io: Io, bat: []const u8, args: []const []const u8, env: ?*std.process.EnvMap) !void { +fn testExecBat(gpa: Allocator, io: Io, bat: []const u8, args: []const []const u8, env: ?*std.process.Environ.Map) !void { const argv = try gpa.alloc([]const u8, 1 + args.len); defer gpa.free(argv); argv[0] = bat; diff --git a/test/standalone/windows_bat_args/test.zig b/test/standalone/windows_bat_args/test.zig index e0d1abe80640bcab15592c416ca973efc5584231..ac851cf8f6cd58f01025ef5a7d8aa2653452cbe5 100644 --- a/test/standalone/windows_bat_args/test.zig +++ b/test/standalone/windows_bat_args/test.zig @@ -130,13 +130,13 @@ fn testExecError(err: anyerror, gpa: Allocator, io: Io, args: []const []const u8 return std.testing.expectError(err, testExec(gpa, io, args, null)); } -fn testExec(gpa: Allocator, io: Io, args: []const []const u8, env: ?*std.process.EnvMap) !void { +fn testExec(gpa: Allocator, io: Io, args: []const []const u8, env: ?*std.process.Environ.Map) !void { try testExecBat(gpa, io, "args1.bat", args, env); try testExecBat(gpa, io, "args2.bat", args, env); try testExecBat(gpa, io, "args3.bat", args, env); } -fn testExecBat(gpa: Allocator, io: Io, bat: []const u8, args: []const []const u8, env: ?*std.process.EnvMap) !void { +fn testExecBat(gpa: Allocator, io: Io, bat: []const u8, args: []const []const u8, env: ?*std.process.Environ.Map) !void { const argv = try gpa.alloc([]const u8, 1 + args.len); defer gpa.free(argv); argv[0] = bat; diff --git a/test/standalone/windows_paths/test.zig b/test/standalone/windows_paths/test.zig index ed4069dc61e6a5121936b34e9628fd21831da3bc..1fbdca211526fcc61639b8c518d8221f3febf3ab 100644 --- a/test/standalone/windows_paths/test.zig +++ b/test/standalone/windows_paths/test.zig @@ -25,10 +25,10 @@ pub fn main() anyerror!void { const alt_drive_letter = try getAltDriveLetter(cwd_path); const alt_drive_cwd_key = try std.fmt.allocPrint(arena, "={c}:", .{alt_drive_letter}); const alt_drive_cwd = try std.fmt.allocPrint(arena, "{c}:\\baz", .{alt_drive_letter}); - var alt_drive_env_map = std.process.EnvMap.init(arena); + var alt_drive_env_map = std.process.Environ.Map.init(arena); try alt_drive_env_map.put(alt_drive_cwd_key, alt_drive_cwd); - const empty_env = std.process.EnvMap.init(arena); + const empty_env = std.process.Environ.Map.init(arena); { const drive_rel = try std.fmt.allocPrint(arena, "{c}:foo", .{alt_drive_letter}); @@ -96,7 +96,7 @@ fn checkRelative( expected_stdout: []const u8, argv: []const []const u8, cwd: ?[]const u8, - env_map: ?*const std.process.EnvMap, + env_map: ?*const std.process.Environ.Map, ) !void { const result = try std.process.Child.run(allocator, io, .{ .argv = argv, diff --git a/tools/doctest.zig b/tools/doctest.zig index 3a67210a592b0daaf471f1853897d3635602b61e..cd836c624e6663ee888073bd847306bf6b64856a 100644 --- a/tools/doctest.zig +++ b/tools/doctest.zig @@ -1128,7 +1128,7 @@ fn in(slice: []const u8, number: u8) bool { fn run( allocator: Allocator, io: Io, - env_map: *process.EnvMap, + env_map: *process.Environ.Map, cwd: []const u8, args: []const []const u8, ) !process.Child.RunResult { -- 2.54.0 From 32af0f6154edb1c1434d0bb489d2abfea7da1685 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 30 Dec 2025 21:37:08 -0800 Subject: [PATCH 02/60] std: move child process APIs to std.Io this gets the build runner compiling again on linux this work is incomplete; it only moves code around so that environment variables can be wrangled properly. a future commit will need to audit the cancelation and error handling of this moved logic. --- build.zig | 2 +- lib/compiler/aro/aro/Driver.zig | 12 +- lib/compiler/reduce.zig | 2 +- lib/compiler/std-docs.zig | 6 +- lib/std/Build.zig | 33 +- lib/std/Build/Step.zig | 38 +- lib/std/Build/Step/Compile.zig | 4 +- lib/std/Build/Step/Fmt.zig | 2 +- lib/std/Build/Step/Run.zig | 140 +- lib/std/Build/WebServer.zig | 24 +- lib/std/Io.zig | 6 + lib/std/Io/Threaded.zig | 1698 ++++++++++++++++- lib/std/c.zig | 44 +- lib/std/os/emscripten.zig | 6 +- lib/std/os/linux.zig | 6 +- lib/std/posix.zig | 97 - lib/std/process.zig | 305 ++- lib/std/process/Child.zig | 1799 +----------------- lib/std/zig/LibCInstallation.zig | 40 +- lib/std/zig/system/darwin.zig | 15 +- src/Compilation.zig | 28 +- src/link/Lld.zig | 26 +- src/main.zig | 24 +- test/link/macho.zig | 2 +- test/src/Debugger.zig | 2 +- test/src/StackTrace.zig | 6 +- test/standalone/child_process/main.zig | 6 +- test/standalone/ios/build.zig | 2 +- test/standalone/simple/hello_world/hello.zig | 14 +- test/tests.zig | 2 +- 30 files changed, 2169 insertions(+), 2222 deletions(-) diff --git a/build.zig b/build.zig index 338709c7c4193ad3c3c4874104fdcf15205eda45..1cef23d2066f7b5e3a05dc8f08361daf80be99d8 100644 --- a/build.zig +++ b/build.zig @@ -261,7 +261,7 @@ pub fn build(b: *std.Build) !void { "--git-dir", ".git", // affected by the -C argument "describe", "--match", "*.*.*", // "--tags", "--abbrev=9", - }, &code, .Ignore) catch { + }, &code, .ignore) catch { break :v version_string; }; const git_describe = mem.trim(u8, git_describe_untrimmed, " \n\r"); diff --git a/lib/compiler/aro/aro/Driver.zig b/lib/compiler/aro/aro/Driver.zig index 340a35bdde97b4995461bc139866a146246663db..6ffaedc2e715577065dec636fb38db01d0e4546a 100644 --- a/lib/compiler/aro/aro/Driver.zig +++ b/lib/compiler/aro/aro/Driver.zig @@ -1256,9 +1256,9 @@ fn invokeAssembler(d: *Driver, tc: *Toolchain, input_path: []const u8, output_pa var child = std.process.Child.init(&argv, d.comp.gpa); // TODO handle better - child.stdin_behavior = .Inherit; - child.stdout_behavior = .Inherit; - child.stderr_behavior = .Inherit; + child.stdin_behavior = .inherit; + child.stdout_behavior = .inherit; + child.stderr_behavior = .inherit; const term = child.spawnAndWait() catch |er| { return d.fatal("unable to spawn linker: {s}", .{errorDescription(er)}); @@ -1508,9 +1508,9 @@ pub fn invokeLinker(d: *Driver, tc: *Toolchain, comptime fast_exit: bool) Compil } var child = std.process.Child.init(argv.items, d.comp.gpa); // TODO handle better - child.stdin_behavior = .Inherit; - child.stdout_behavior = .Inherit; - child.stderr_behavior = .Inherit; + child.stdin_behavior = .inherit; + child.stdout_behavior = .inherit; + child.stderr_behavior = .inherit; const term = child.spawnAndWait() catch |er| { return d.fatal("unable to spawn linker: {s}", .{errorDescription(er)}); diff --git a/lib/compiler/reduce.zig b/lib/compiler/reduce.zig index 922031d00873a8a18cbe2d766c1b5f3b35baa98e..b0fa94cb06cf39b068e5d806af2d81e4efd3b5f2 100644 --- a/lib/compiler/reduce.zig +++ b/lib/compiler/reduce.zig @@ -306,7 +306,7 @@ fn termToInteresting(term: std.process.Child.Term) Interestingness { } fn runCheck(arena: Allocator, io: Io, argv: []const []const u8) !Interestingness { - const result = try std.process.Child.run(arena, io, .{ .argv = argv }); + const result = try std.process.run(arena, io, .{ .spawn_options = .{ .argv = argv } }); if (result.stderr.len != 0) std.debug.print("{s}", .{result.stderr}); return termToInteresting(result.term); diff --git a/lib/compiler/std-docs.zig b/lib/compiler/std-docs.zig index e2b58b86683f2018cf7f6d3638a231690f515da6..a9280b6fd91a5a55d4a3a216fe6188d5ee63daea 100644 --- a/lib/compiler/std-docs.zig +++ b/lib/compiler/std-docs.zig @@ -447,9 +447,9 @@ fn openBrowserTabThread(gpa: Allocator, io: Io, url: []const u8) !void { else => "xdg-open", }; var child = std.process.Child.init(&.{ main_exe, url }, gpa); - child.stdin_behavior = .Ignore; - child.stdout_behavior = .Ignore; - child.stderr_behavior = .Ignore; + child.stdin_behavior = .ignore; + child.stdout_behavior = .ignore; + child.stderr_behavior = .ignore; try child.spawn(io); _ = try child.wait(io); } diff --git a/lib/std/Build.zig b/lib/std/Build.zig index 8867ac05df4a77b8cf5b88d7b13b0bd6440e6f30..3ade95ae060d691bb1ab9084250aab3d14e309ab 100644 --- a/lib/std/Build.zig +++ b/lib/std/Build.zig @@ -190,7 +190,7 @@ pub const RunError = error{ ExitCodeFailure, ProcessTerminated, ExecNotSupported, -} || std.process.Child.SpawnError; +} || std.process.SpawnError; pub const PkgConfigError = error{ PkgConfigCrashed, @@ -1755,7 +1755,7 @@ pub fn fmt(b: *Build, comptime format: []const u8, args: anytype) []u8 { } fn supportedWindowsProgramExtension(ext: []const u8) bool { - inline for (@typeInfo(std.process.Child.WindowsExtension).@"enum".fields) |field| { + inline for (@typeInfo(std.process.WindowsExtension).@"enum".fields) |field| { if (std.ascii.eqlIgnoreCase(ext, "." ++ field.name)) return true; } return false; @@ -1830,23 +1830,26 @@ pub fn runAllowFail( b: *Build, argv: []const []const u8, out_code: *u8, - stderr_behavior: std.process.Child.StdIo, + stderr_behavior: std.process.SpawnOptions.StdIo, ) RunError![]u8 { assert(argv.len != 0); if (!process.can_spawn) return error.ExecNotSupported; - const io = b.graph.io; + const graph = b.graph; + const io = graph.io; const max_output_size = 400 * 1024; - var child = std.process.Child.init(b.allocator, argv, .{ .map = &b.graph.env_map }); - child.stdin_behavior = .Ignore; - child.stdout_behavior = .Pipe; - child.stderr_behavior = stderr_behavior; + try Step.handleVerbose2(b, null, &graph.env_map, argv); - try Step.handleVerbose2(b, null, child.environ.map, argv); - try child.spawn(io); + var child = try std.process.spawn(io, .{ + .argv = argv, + .env_map = &graph.env_map, + .stdin = .ignore, + .stdout = .pipe, + .stderr = stderr_behavior, + }); var stdout_reader = child.stdout.?.readerStreaming(io, &.{}); const stdout = stdout_reader.interface.allocRemaining(b.allocator, .limited(max_output_size)) catch { @@ -1856,14 +1859,18 @@ pub fn runAllowFail( const term = try child.wait(io); switch (term) { - .Exited => |code| { + .exited => |code| { if (code != 0) { out_code.* = @as(u8, @truncate(code)); return error.ExitCodeFailure; } return stdout; }, - .Signal, .Stopped, .Unknown => |code| { + .signal => |sig| { + out_code.* = @as(u8, @truncate(@intFromEnum(sig))); + return error.ProcessTerminated; + }, + .stopped, .unknown => |code| { out_code.* = @as(u8, @truncate(code)); return error.ProcessTerminated; }, @@ -1882,7 +1889,7 @@ pub fn run(b: *Build, argv: []const []const u8) []u8 { } var code: u8 = undefined; - return b.runAllowFail(argv, &code, .Inherit) catch |err| { + return b.runAllowFail(argv, &code, .inherit) catch |err| { const printed_cmd = Step.allocPrintCmd(b.allocator, null, null, argv) catch @panic("OOM"); std.debug.print("unable to spawn the following command: {t}\n{s}\n", .{ err, printed_cmd }); process.exit(1); diff --git a/lib/std/Build/Step.zig b/lib/std/Build/Step.zig index 5d6daf9a89b711113880fdc517adae3b529e3a9e..2699e3f01b2e9c6efdcf1398ea8ff39b1f26a621 100644 --- a/lib/std/Build/Step.zig +++ b/lib/std/Build/Step.zig @@ -348,7 +348,7 @@ pub fn captureChildProcess( gpa: Allocator, progress_node: std.Progress.Node, argv: []const []const u8, -) !std.process.Child.RunResult { +) !std.process.RunResult { const graph = s.owner.graph; const arena = graph.arena; const io = graph.io; @@ -360,11 +360,11 @@ pub fn captureChildProcess( try handleChildProcUnsupported(s); try handleVerbose(s.owner, null, argv); - const result = std.process.Child.run(arena, io, .{ + const result = std.process.run(arena, io, .{ .spawn_options = .{ .argv = argv, - .environ = .{ .map = &graph.env_map }, + .env_map = &graph.env_map, .progress_node = progress_node, - }) catch |err| return s.fail("failed to run {s}: {t}", .{ argv[0], err }); + } }) catch |err| return s.fail("failed to run {s}: {t}", .{ argv[0], err }); if (result.stderr.len > 0) { try s.result_error_msgs.append(arena, result.stderr); @@ -444,19 +444,20 @@ pub fn evalZigProcess( return result; } assert(argv.len != 0); - const arena = b.allocator; try handleChildProcUnsupported(s); try handleVerbose(s.owner, null, argv); - var child = std.process.Child.init(arena, argv, .{ .map = &b.graph.env_map }); - child.stdin_behavior = .Pipe; - child.stdout_behavior = .Pipe; - child.stderr_behavior = .Pipe; - child.request_resource_usage_statistics = true; - child.progress_node = prog_node; - - child.spawn(io) catch |err| return s.fail("failed to spawn zig compiler {s}: {t}", .{ argv[0], err }); + var child = std.process.spawn(io, .{ + .argv = argv, + .env_map = &b.graph.env_map, + .stdin = .pipe, + .stdout = .pipe, + .stderr = .pipe, + .request_resource_usage_statistics = true, + .progress_node = prog_node, + }) catch |err| return s.fail("failed to spawn zig compiler {s}: {t}", .{ argv[0], err }); + defer if (!watch) child.kill(io); const zp = try gpa.create(ZigProcess); zp.* = .{ @@ -465,7 +466,7 @@ pub fn evalZigProcess( .stdout = child.stdout.?, .stderr = child.stderr.?, }), - .progress_ipc_fd = if (std.Progress.have_ipc) child.progress_node.getIpcFd() else {}, + .progress_ipc_fd = if (std.Progress.have_ipc) prog_node.getIpcFd() else {}, }; if (watch) s.setZigProcess(zp); defer if (!watch) { @@ -487,7 +488,7 @@ pub fn evalZigProcess( // Special handling for Compile step that is expecting compile errors. if (s.cast(Compile)) |compile| switch (term) { - .Exited => { + .exited => { // Note that the exit code may be 0 in this case due to the // compiler server protocol. if (compile.expect_errors != null) { @@ -719,12 +720,15 @@ pub inline fn handleChildProcUnsupported(s: *Step) error{ OutOfMemory, MakeFaile pub fn handleChildProcessTerm(s: *Step, term: std.process.Child.Term) error{ MakeFailed, OutOfMemory }!void { assert(s.result_failed_command != null); switch (term) { - .Exited => |code| { + .exited => |code| { if (code != 0) { return s.fail("process exited with error code {d}", .{code}); } }, - .Signal, .Stopped, .Unknown => { + .signal => |sig| { + return s.fail("process terminated with signal {t}", .{sig}); + }, + .stopped, .unknown => { return s.fail("process terminated unexpectedly", .{}); }, } diff --git a/lib/std/Build/Step/Compile.zig b/lib/std/Build/Step/Compile.zig index 0454e5b79dbc2582cac7188b9a2593decb37dfa2..915f7343865d950c3b7cc020cc131ac570f95236 100644 --- a/lib/std/Build/Step/Compile.zig +++ b/lib/std/Build/Step/Compile.zig @@ -747,7 +747,7 @@ fn runPkgConfig(compile: *Compile, lib_name: []const u8) !PkgConfigResult { pkg_name, "--cflags", "--libs", - }, &code, .Ignore)) |stdout| stdout else |err| switch (err) { + }, &code, .ignore)) |stdout| stdout else |err| switch (err) { error.ProcessTerminated => return error.PkgConfigCrashed, error.ExecNotSupported => return error.PkgConfigFailed, error.ExitCodeFailure => return error.PkgConfigFailed, @@ -1847,7 +1847,7 @@ pub fn doAtomicSymLinks( fn execPkgConfigList(b: *std.Build, out_code: *u8) (PkgConfigError || RunError)![]const PkgConfigPkg { const pkg_config_exe = b.graph.env_map.get("PKG_CONFIG") orelse "pkg-config"; - const stdout = try b.runAllowFail(&[_][]const u8{ pkg_config_exe, "--list-all" }, out_code, .Ignore); + const stdout = try b.runAllowFail(&[_][]const u8{ pkg_config_exe, "--list-all" }, out_code, .ignore); var list = std.array_list.Managed(PkgConfigPkg).init(b.allocator); errdefer list.deinit(); var line_it = mem.tokenizeAny(u8, stdout, "\r\n"); diff --git a/lib/std/Build/Step/Fmt.zig b/lib/std/Build/Step/Fmt.zig index ff748af97973a8723cd0a3344d305736de0b7eb2..2da07b63bb422176c8674ff6ccf71a0e00749c4e 100644 --- a/lib/std/Build/Step/Fmt.zig +++ b/lib/std/Build/Step/Fmt.zig @@ -69,7 +69,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void { const run_result = try step.captureChildProcess(options.gpa, prog_node, argv.items); if (fmt.check) switch (run_result.term) { - .Exited => |code| if (code != 0 and run_result.stdout.len != 0) { + .exited => |code| if (code != 0 and run_result.stdout.len != 0) { var it = std.mem.tokenizeScalar(u8, run_result.stdout, '\n'); while (it.next()) |bad_file_name| { try step.addError("{s}: non-conforming formatting", .{bad_file_name}); diff --git a/lib/std/Build/Step/Run.zig b/lib/std/Build/Step/Run.zig index 4a5386f8cdc9b8702bfac62dbe7320d2a46afc22..5500d16ecad1612f2c60c5f07815b2bc277cfd82 100644 --- a/lib/std/Build/Step/Run.zig +++ b/lib/std/Build/Step/Run.zig @@ -149,7 +149,7 @@ pub const StdIo = union(enum) { expect_stderr_match: []const u8, expect_stdout_exact: []const u8, expect_stdout_match: []const u8, - expect_term: std.process.Child.Term, + expect_term: process.Child.Term, }; }; @@ -618,7 +618,7 @@ pub fn expectStdOutEqual(run: *Run, bytes: []const u8) void { } pub fn expectExitCode(run: *Run, code: u8) void { - const new_check: StdIo.Check = .{ .expect_term = .{ .Exited = code } }; + const new_check: StdIo.Check = .{ .expect_term = .{ .exited = code } }; run.addCheck(new_check); } @@ -1182,40 +1182,40 @@ fn populateGeneratedPaths( } } -fn formatTerm(term: ?std.process.Child.Term, w: *std.Io.Writer) std.Io.Writer.Error!void { +fn formatTerm(term: ?process.Child.Term, w: *std.Io.Writer) std.Io.Writer.Error!void { if (term) |t| switch (t) { - .Exited => |code| try w.print("exited with code {d}", .{code}), - .Signal => |sig| try w.print("terminated with signal {d}", .{sig}), - .Stopped => |sig| try w.print("stopped with signal {d}", .{sig}), - .Unknown => |code| try w.print("terminated for unknown reason with code {d}", .{code}), + .exited => |code| try w.print("exited with code {d}", .{code}), + .signal => |sig| try w.print("terminated with signal {t}", .{sig}), + .stopped => |sig| try w.print("stopped with signal {d}", .{sig}), + .unknown => |code| try w.print("terminated for unknown reason with code {d}", .{code}), } else { try w.writeAll("exited with any code"); } } -fn fmtTerm(term: ?std.process.Child.Term) std.fmt.Alt(?std.process.Child.Term, formatTerm) { +fn fmtTerm(term: ?process.Child.Term) std.fmt.Alt(?process.Child.Term, formatTerm) { return .{ .data = term }; } -fn termMatches(expected: ?std.process.Child.Term, actual: std.process.Child.Term) bool { +fn termMatches(expected: ?process.Child.Term, actual: process.Child.Term) bool { return if (expected) |e| switch (e) { - .Exited => |expected_code| switch (actual) { - .Exited => |actual_code| expected_code == actual_code, + .exited => |expected_code| switch (actual) { + .exited => |actual_code| expected_code == actual_code, else => false, }, - .Signal => |expected_sig| switch (actual) { - .Signal => |actual_sig| expected_sig == actual_sig, + .signal => |expected_sig| switch (actual) { + .signal => |actual_sig| expected_sig == actual_sig, else => false, }, - .Stopped => |expected_sig| switch (actual) { - .Stopped => |actual_sig| expected_sig == actual_sig, + .stopped => |expected_sig| switch (actual) { + .stopped => |actual_sig| expected_sig == actual_sig, else => false, }, - .Unknown => |expected_code| switch (actual) { - .Unknown => |actual_code| expected_code == actual_code, + .unknown => |expected_code| switch (actual) { + .unknown => |actual_code| expected_code == actual_code, else => false, }, } else switch (actual) { - .Exited => true, + .exited => true, else => false, }; } @@ -1526,8 +1526,8 @@ fn runCommand( else => { // On failure, report captured stderr like normal standard error output. const bad_exit = switch (generic_result.term) { - .Exited => |code| code != 0, - .Signal, .Stopped, .Unknown => true, + .exited => |code| code != 0, + .signal, .stopped, .unknown => true, }; if (bad_exit) { if (generic_result.stderr) |bytes| { @@ -1541,7 +1541,7 @@ fn runCommand( } const EvalGenericResult = struct { - term: std.process.Child.Term, + term: process.Child.Term, stdout: ?[]const u8, stderr: ?[]const u8, }; @@ -1555,7 +1555,6 @@ fn spawnChildAndCollect( fuzz_context: ?FuzzContext, ) !?EvalGenericResult { const b = run.step.owner; - const arena = b.allocator; const graph = b.graph; const io = graph.io; @@ -1564,53 +1563,52 @@ fn spawnChildAndCollect( assert(run.stdio == .zig_test); } - var child = std.process.Child.init(arena, argv, .{ .map = env_map }); - if (run.cwd) |lazy_cwd| { - child.cwd = lazy_cwd.getPath2(b, &run.step); - } - child.request_resource_usage_statistics = true; - - child.stdin_behavior = switch (run.stdio) { - .infer_from_args => if (has_side_effects) .Inherit else .Ignore, - .inherit => .Inherit, - .check => .Ignore, - .zig_test => .Pipe, - }; - child.stdout_behavior = switch (run.stdio) { - .infer_from_args => if (has_side_effects) .Inherit else .Ignore, - .inherit => .Inherit, - .check => |checks| if (checksContainStdout(checks.items)) .Pipe else .Ignore, - .zig_test => .Pipe, - }; - child.stderr_behavior = switch (run.stdio) { - .infer_from_args => if (has_side_effects) .Inherit else .Pipe, - .inherit => .Inherit, - .check => .Pipe, - .zig_test => .Pipe, - }; - if (run.captured_stdout != null) child.stdout_behavior = .Pipe; - if (run.captured_stderr != null) child.stderr_behavior = .Pipe; - if (run.stdin != .none) { - assert(run.stdio != .inherit); - child.stdin_behavior = .Pipe; - } + const child_cwd = if (run.cwd) |lazy_cwd| lazy_cwd.getPath2(b, &run.step) else null; // If an error occurs, it's caused by this command: assert(run.step.result_failed_command == null); - run.step.result_failed_command = try Step.allocPrintCmd(options.gpa, child.cwd, .{ + run.step.result_failed_command = try Step.allocPrintCmd(options.gpa, child_cwd, .{ .child = env_map, .parent = &graph.env_map, }, argv); + var spawn_options: process.SpawnOptions = .{ + .argv = argv, + .cwd = child_cwd, + .env_map = &graph.env_map, + .request_resource_usage_statistics = true, + .stdin = if (run.stdin != .none) s: { + assert(run.stdio != .inherit); + break :s .pipe; + } else switch (run.stdio) { + .infer_from_args => if (has_side_effects) .inherit else .ignore, + .inherit => .inherit, + .check => .ignore, + .zig_test => .pipe, + }, + .stdout = if (run.captured_stdout != null) .pipe else switch (run.stdio) { + .infer_from_args => if (has_side_effects) .inherit else .ignore, + .inherit => .inherit, + .check => |checks| if (checksContainStdout(checks.items)) .pipe else .ignore, + .zig_test => .pipe, + }, + .stderr = if (run.captured_stderr != null) .pipe else switch (run.stdio) { + .infer_from_args => if (has_side_effects) .inherit else .pipe, + .inherit => .inherit, + .check => .pipe, + .zig_test => .pipe, + }, + }; + if (run.stdio == .zig_test) { var timer = try std.time.Timer.start(); defer run.step.result_duration_ns = timer.read(); - try evalZigTest(run, &child, options, fuzz_context); + try evalZigTest(run, spawn_options, options, fuzz_context); return null; } else { - const inherit = child.stdout_behavior == .Inherit or child.stderr_behavior == .Inherit; + const inherit = spawn_options.stdout == .inherit or spawn_options.stderr == .inherit; if (!run.disable_zig_progress and !inherit) { - child.progress_node = options.progress_node; + spawn_options.progress_node = options.progress_node; } const terminal_mode: Io.Terminal.Mode = if (inherit) m: { const stderr = try io.lockStderr(&.{}, graph.stderr_mode); @@ -1619,7 +1617,7 @@ fn spawnChildAndCollect( defer if (inherit) io.unlockStderr(); try setColorEnvironmentVariables(run, env_map, terminal_mode); var timer = try std.time.Timer.start(); - const res = try evalGeneric(run, &child); + const res = try evalGeneric(run, spawn_options); run.step.result_duration_ns = timer.read(); return .{ .term = res.term, .stdout = res.stdout, .stderr = res.stderr }; } @@ -1658,7 +1656,7 @@ const StdioPollEnum = enum { stdout, stderr }; fn evalZigTest( run: *Run, - child: *std.process.Child, + spawn_options: process.SpawnOptions, options: Step.MakeOptions, fuzz_context: ?FuzzContext, ) !void { @@ -1682,14 +1680,14 @@ fn evalZigTest( var test_metadata: ?TestMetadata = null; while (true) { - try child.spawn(io); + var child = try process.spawn(io, spawn_options); var poller = std.Io.poll(gpa, StdioPollEnum, .{ .stdout = child.stdout.?, .stderr = child.stderr.?, }); var child_killed = false; defer if (!child_killed) { - _ = child.kill(io) catch {}; + child.kill(io); poller.deinit(); run.step.result_peak_rss = @max( run.step.result_peak_rss, @@ -1697,11 +1695,9 @@ fn evalZigTest( ); }; - try child.waitForSpawn(); - switch (try pollZigTest( run, - child, + &child, options, fuzz_context, &poller, @@ -1763,7 +1759,7 @@ fn evalZigTest( // Report an error if the child terminated uncleanly or if we were still trying to run more tests. run.step.result_stderr = stderr_owned; const tests_done = test_metadata != null and test_metadata.?.next_index == std.math.maxInt(u32); - if (!tests_done or !termMatches(.{ .Exited = 0 }, term)) { + if (!tests_done or !termMatches(.{ .exited = 0 }, term)) { // The individual unit test results are irrelevant: the test runner itself broke! // Fail immediately without populating `s.test_results`. return run.step.fail("test process unexpectedly {f}", .{fmtTerm(term)}); @@ -1818,7 +1814,7 @@ fn evalZigTest( /// * `poll` fails, indicating the child closed stdout and stderr fn pollZigTest( run: *Run, - child: *std.process.Child, + child: *process.Child, options: Step.MakeOptions, fuzz_context: ?FuzzContext, poller: *std.Io.Poller(StdioPollEnum), @@ -2176,15 +2172,13 @@ fn sendRunFuzzTestMessage( }; } -fn evalGeneric(run: *Run, child: *std.process.Child) !EvalGenericResult { +fn evalGeneric(run: *Run, spawn_options: process.SpawnOptions) !EvalGenericResult { const b = run.step.owner; const io = b.graph.io; const arena = b.allocator; - try child.spawn(io); - errdefer _ = child.kill(io) catch {}; - - try child.waitForSpawn(); + var child = try process.spawn(io, spawn_options); + defer child.kill(io); switch (run.stdin) { .bytes => |bytes| { @@ -2334,10 +2328,10 @@ fn hashStdIo(hh: *std.Build.Cache.HashHelper, stdio: StdIo) void { => |s| hh.addBytes(s), .expect_term => |term| { - hh.add(@as(std.meta.Tag(std.process.Child.Term), term)); + hh.add(@as(std.meta.Tag(process.Child.Term), term)); switch (term) { - .Exited => |x| hh.add(x), - .Signal, .Stopped, .Unknown => |x| hh.add(x), + inline .exited, .signal => |x| hh.add(x), + .stopped, .unknown => |x| hh.add(x), } }, } diff --git a/lib/std/Build/WebServer.zig b/lib/std/Build/WebServer.zig index 2c53e103cc161b5cb1d67c9724f027df8462267c..ae9a200f24772851584d7bd694886f9029f58a93 100644 --- a/lib/std/Build/WebServer.zig +++ b/lib/std/Build/WebServer.zig @@ -572,11 +572,14 @@ fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.Optim "--listen=-", }); - var child: std.process.Child = .init(gpa, argv.items, .{ .map = &graph.env_map }); - child.stdin_behavior = .Pipe; - child.stdout_behavior = .Pipe; - child.stderr_behavior = .Pipe; - try child.spawn(io); + var child = try std.process.spawn(io, .{ + .argv = argv.items, + .env_map = &graph.env_map, + .stdin = .pipe, + .stdout = .pipe, + .stderr = .pipe, + }); + defer child.kill(io); var poller = Io.poll(gpa, enum { stdout, stderr }, .{ .stdout = child.stdout.?, @@ -636,7 +639,7 @@ fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.Optim child.stdin = null; switch (try child.wait(io)) { - .Exited => |code| { + .exited => |code| { if (code != 0) { log.err( "the following command exited with error code {d}:\n{s}", @@ -645,7 +648,14 @@ fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.Optim return error.WasmCompilationFailed; } }, - .Signal, .Stopped, .Unknown => { + .signal => |sig| { + log.err( + "the following command terminated with signal {t}:\n{s}", + .{ sig, try Build.Step.allocPrintCmd(arena, null, null, argv.items) }, + ); + return error.WasmCompilationFailed; + }, + .stopped, .unknown => { log.err( "the following command terminated unexpectedly:\n{s}", .{try Build.Step.allocPrintCmd(arena, null, null, argv.items)}, diff --git a/lib/std/Io.zig b/lib/std/Io.zig index 31dd500df76b5628bf10b5c6ce2b7f70b73d95bf..9670ac3bd77c3b7a26bae7515b2d00a9e858ed36 100644 --- a/lib/std/Io.zig +++ b/lib/std/Io.zig @@ -717,6 +717,12 @@ pub const VTable = struct { tryLockStderr: *const fn (?*anyopaque, ?Terminal.Mode) Cancelable!?LockedStderr, unlockStderr: *const fn (?*anyopaque) void, processSetCurrentDir: *const fn (?*anyopaque, Dir) std.process.SetCurrentDirError!void, + processReplace: *const fn (?*anyopaque, std.process.ReplaceOptions) std.process.ReplaceError, + processReplacePath: *const fn (?*anyopaque, Dir, std.process.ReplaceOptions) std.process.ReplaceError, + processSpawn: *const fn (?*anyopaque, std.process.SpawnOptions) std.process.SpawnError!std.process.Child, + processSpawnPath: *const fn (?*anyopaque, Dir, std.process.SpawnOptions) std.process.SpawnError!std.process.Child, + childWait: *const fn (?*anyopaque, *std.process.Child) std.process.Child.WaitError!std.process.Child.Term, + childKill: *const fn (?*anyopaque, *std.process.Child) void, now: *const fn (?*anyopaque, Clock) Clock.Error!Timestamp, sleep: *const fn (?*anyopaque, Timeout) SleepError!void, diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 50e9f4cf150bb3715fffce73083c32e5b3a2dd6f..df1ce77fe57df7303fda1c4b68d6f8dce630227c 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -13,6 +13,7 @@ const File = std.Io.File; const Dir = std.Io.Dir; const HostName = std.Io.net.HostName; const IpAddress = std.Io.net.IpAddress; +const process = std.process; const Allocator = std.mem.Allocator; const Alignment = std.mem.Alignment; const assert = std.debug.assert; @@ -72,7 +73,7 @@ pub const Argv0 = switch (native_os) { pub const Environ = struct { /// Unmodified data directly from the OS. - block: std.process.Environ.Block = &.{}, + block: process.Environ.Block = &.{}, /// Protected by `mutex`. Determines whether the other fields have been /// memoized based on `block`. initialized: bool = false, @@ -95,10 +96,10 @@ pub const Environ = struct { }; pub const String = switch (native_os) { - .openbsd, .haiku => struct { + .windows, .wasi => struct {}, + else => struct { PATH: ?[:0]const u8 = null, }, - else => struct {}, }; }; @@ -1400,6 +1401,12 @@ pub fn io(t: *Threaded) Io { .tryLockStderr = tryLockStderr, .unlockStderr = unlockStderr, .processSetCurrentDir = processSetCurrentDir, + .processReplace = processReplace, // TODO audit for cancelation and unreachable + .processReplacePath = processReplacePath, // TODO audit for cancelation and unreachable + .processSpawn = processSpawn, // TODO audit for cancelation and unreachable + .processSpawnPath = processSpawnPath, // TODO audit for cancelation and unreachable + .childWait = childWait, // TODO audit for cancelation and unreachable + .childKill = childKill, // TODO audit for cancelation and unreachable .now = now, .sleep = sleep, @@ -1538,6 +1545,12 @@ pub fn ioBasic(t: *Threaded) Io { .tryLockStderr = tryLockStderr, .unlockStderr = unlockStderr, .processSetCurrentDir = processSetCurrentDir, + .processReplace = processReplace, + .processReplacePath = processReplacePath, + .processSpawn = processSpawn, + .processSpawnPath = processSpawnPath, + .childWait = childWait, + .childKill = childKill, .now = now, .sleep = sleep, @@ -1601,6 +1614,11 @@ const have_fchmod = switch (native_os) { else => true, }; +const have_wait4 = switch (native_os) { + .dragonfly, .freebsd, .netbsd, .openbsd, .illumos, .linux, .serenity, .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => true, + else => false, +}; + const openat_sym = if (posix.lfs64_abi) posix.system.openat64 else posix.system.openat; const fstat_sym = if (posix.lfs64_abi) posix.system.fstat64 else posix.system.fstat; const fstatat_sym = if (posix.lfs64_abi) posix.system.fstatat64 else posix.system.fstatat; @@ -2247,7 +2265,7 @@ fn dirCreateDirPath( ) Dir.CreateDirPathError!Dir.CreatePathStatus { const t: *Threaded = @ptrCast(@alignCast(userdata)); - var it = std.fs.path.componentIterator(sub_path); + var it = Dir.path.componentIterator(sub_path); var status: Dir.CreatePathStatus = .existed; var component = it.last() orelse return error.BadPathName; while (true) { @@ -2307,9 +2325,9 @@ fn dirCreateDirPathOpenWindows( _ = permissions; // TODO apply these permissions - var it = std.fs.path.componentIterator(sub_path); + var it = Dir.path.componentIterator(sub_path); // If there are no components in the path, then create a dummy component with the full path. - var component: std.fs.path.NativeComponentIterator.Component = it.last() orelse .{ + var component: Dir.path.NativeComponentIterator.Component = it.last() orelse .{ .name = "", .path = sub_path, }; @@ -2347,7 +2365,7 @@ fn dirCreateDirPathOpenWindows( }, &.{ .Length = @sizeOf(w.OBJECT_ATTRIBUTES), - .RootDirectory = if (std.fs.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle, + .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle, .Attributes = .{}, .ObjectName = &nt_name, .SecurityDescriptor = null, @@ -2986,7 +3004,7 @@ fn dirAccessWindows( }; var attr: windows.OBJECT_ATTRIBUTES = .{ .Length = @sizeOf(windows.OBJECT_ATTRIBUTES), - .RootDirectory = if (std.fs.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle, + .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle, .Attributes = .{}, .ObjectName = &nt_name, .SecurityDescriptor = null, @@ -3535,7 +3553,7 @@ fn dirOpenFileWindows( _ = t; const sub_path_w_array = try windows.sliceToPrefixedFileW(dir.handle, sub_path); const sub_path_w = sub_path_w_array.span(); - const dir_handle = if (std.fs.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle; + const dir_handle = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle; return dirOpenFileWtf16(dir_handle, sub_path_w, flags); } @@ -3933,7 +3951,7 @@ pub fn dirOpenDirWindows( }, &.{ .Length = @sizeOf(w.OBJECT_ATTRIBUTES), - .RootDirectory = if (std.fs.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle, + .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle, .Attributes = .{}, .ObjectName = &nt_name, .SecurityDescriptor = null, @@ -5081,7 +5099,7 @@ fn dirDeleteWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, remov } }, &.{ .Length = @sizeOf(w.OBJECT_ATTRIBUTES), - .RootDirectory = if (std.fs.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle, + .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle, .Attributes = .{}, .ObjectName = &nt_name, .SecurityDescriptor = null, @@ -5358,7 +5376,7 @@ fn dirRenameWindows( .POSIX_SEMANTICS = true, .IGNORE_READONLY_ATTRIBUTE = true, }, - .RootDirectory = if (std.fs.path.isAbsoluteWindowsWtf16(new_path_w)) null else new_dir.handle, + .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(new_path_w)) null else new_dir.handle, .FileName = new_path_w, }); var io_status_block: w.IO_STATUS_BLOCK = undefined; @@ -5387,7 +5405,7 @@ fn dirRenameWindows( if (need_fallback) { const rename_info: w.FILE.RENAME_INFORMATION = .init(.{ .Flags = .{ .REPLACE_IF_EXISTS = replace_if_exists }, - .RootDirectory = if (std.fs.path.isAbsoluteWindowsWtf16(new_path_w)) null else new_dir.handle, + .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(new_path_w)) null else new_dir.handle, .FileName = new_path_w, }); var io_status_block: w.IO_STATUS_BLOCK = undefined; @@ -5622,13 +5640,13 @@ fn dirSymLinkWindows( // the C:\ drive. .rooted => break :target_path target_path_w.span(), // Keep relative paths relative, but anything else needs to get NT-prefixed. - else => if (!std.fs.path.isAbsoluteWindowsWtf16(target_path_w.span())) + else => if (!Dir.path.isAbsoluteWindowsWtf16(target_path_w.span())) break :target_path target_path_w.span(), } } var prefixed_target_path = try w.wToPrefixedFileW(dir.handle, target_path_w.span()); // We do this after prefixing to ensure that drive-relative paths are treated as absolute - is_target_absolute = std.fs.path.isAbsoluteWindowsWtf16(prefixed_target_path.span()); + is_target_absolute = Dir.path.isAbsoluteWindowsWtf16(prefixed_target_path.span()); break :target_path prefixed_target_path.span(); }; @@ -5636,8 +5654,8 @@ fn dirSymLinkWindows( var buffer: [w.MAXIMUM_REPARSE_DATA_BUFFER_SIZE]u8 = undefined; const buf_len = @sizeOf(SYMLINK_DATA) + final_target_path.len * 4; const header_len = @sizeOf(w.ULONG) + @sizeOf(w.USHORT) * 2; - const target_is_absolute = std.fs.path.isAbsoluteWindowsWtf16(final_target_path); - const symlink_data = SYMLINK_DATA{ + const target_is_absolute = Dir.path.isAbsoluteWindowsWtf16(final_target_path); + const symlink_data: SYMLINK_DATA = .{ .ReparseTag = .SYMLINK, .ReparseDataLength = @intCast(buf_len - header_len), .Reserved = 0, @@ -7890,7 +7908,7 @@ fn posixSeekTo(fd: posix.fd_t, offset: u64) File.SeekError!void { } } -fn processExecutableOpen(userdata: ?*anyopaque, flags: File.OpenFlags) std.process.OpenExecutableError!File { +fn processExecutableOpen(userdata: ?*anyopaque, flags: File.OpenFlags) process.OpenExecutableError!File { const t: *Threaded = @ptrCast(@alignCast(userdata)); switch (native_os) { .wasi => return error.OperationUnsupported, @@ -7931,7 +7949,7 @@ fn processExecutableOpen(userdata: ?*anyopaque, flags: File.OpenFlags) std.proce } } -fn processExecutablePath(userdata: ?*anyopaque, out_buffer: []u8) std.process.ExecutablePathError!usize { +fn processExecutablePath(userdata: ?*anyopaque, out_buffer: []u8) process.ExecutablePathError!usize { const t: *Threaded = @ptrCast(@alignCast(userdata)); switch (native_os) { @@ -11691,14 +11709,14 @@ fn netLookupFallible( fn lockStderr(userdata: ?*anyopaque, terminal_mode: ?Io.Terminal.Mode) Io.Cancelable!Io.LockedStderr { const t: *Threaded = @ptrCast(@alignCast(userdata)); // Only global mutex since this is Threaded. - std.process.stderr_thread_mutex.lock(); + process.stderr_thread_mutex.lock(); return initLockedStderr(t, terminal_mode); } fn tryLockStderr(userdata: ?*anyopaque, terminal_mode: ?Io.Terminal.Mode) Io.Cancelable!?Io.LockedStderr { const t: *Threaded = @ptrCast(@alignCast(userdata)); // Only global mutex since this is Threaded. - if (!std.process.stderr_thread_mutex.tryLock()) return null; + if (!process.stderr_thread_mutex.tryLock()) return null; return try initLockedStderr(t, terminal_mode); } @@ -11729,10 +11747,10 @@ fn unlockStderr(userdata: ?*anyopaque) void { }; t.stderr_writer.interface.end = 0; t.stderr_writer.interface.buffer = &.{}; - std.process.stderr_thread_mutex.unlock(); + process.stderr_thread_mutex.unlock(); } -fn processSetCurrentDir(userdata: ?*anyopaque, dir: Dir) std.process.SetCurrentDirError!void { +fn processSetCurrentDir(userdata: ?*anyopaque, dir: Dir) process.SetCurrentDirError!void { if (native_os == .wasi) return error.OperationUnsupported; const t: *Threaded = @ptrCast(@alignCast(userdata)); _ = t; @@ -12690,6 +12708,1640 @@ fn scanEnviron(t: *Threaded) void { } } +fn processReplace(userdata: ?*anyopaque, options: std.process.ReplaceOptions) std.process.ReplaceError { + _ = userdata; + _ = options; + @panic("TODO"); +} + +fn processReplacePath(userdata: ?*anyopaque, dir: Dir, options: std.process.ReplaceOptions) std.process.ReplaceError { + _ = userdata; + _ = dir; + _ = options; + @panic("TODO"); +} + +fn processSpawnPath(userdata: ?*anyopaque, dir: Dir, options: process.SpawnOptions) process.SpawnError!process.Child { + _ = userdata; + _ = dir; + _ = options; + @panic("TODO"); +} + +const processSpawn = switch (native_os) { + .wasi, .ios, .tvos, .visionos, .watchos => processSpawnUnsupported, + .windows => processSpawnWindows, + else => processSpawnPosix, +}; + +fn processSpawnUnsupported(userdata: ?*anyopaque, options: process.SpawnOptions) process.SpawnError!process.Child { + _ = userdata; + _ = options; + return error.OperationUnsupported; +} + +fn processSpawnPosix(userdata: ?*anyopaque, options: process.SpawnOptions) process.SpawnError!process.Child { + const t: *Threaded = @ptrCast(@alignCast(userdata)); + + // The child process does need to access (one end of) these pipes. However, + // we must initially set CLOEXEC to avoid a race condition. If another thread + // is racing to spawn a different child process, we don't want it to inherit + // these FDs in any scenario; that would mean that, for instance, calls to + // `poll` from the parent would not report the child's stdout as closing when + // expected, since the other child may retain a reference to the write end of + // the pipe. So, we create the pipes with CLOEXEC initially. After fork, we + // need to do something in the new child to make sure we preserve the reference + // we want. We could use `fcntl` to remove CLOEXEC from the FD, but as it + // turns out, we `dup2` everything anyway, so there's no need! + const pipe_flags: posix.O = .{ .CLOEXEC = true }; + + const stdin_pipe = if (options.stdin == .pipe) try posix.pipe2(pipe_flags) else undefined; + errdefer if (options.stdin == .pipe) { + destroyPipe(stdin_pipe); + }; + + const stdout_pipe = if (options.stdout == .pipe) try posix.pipe2(pipe_flags) else undefined; + errdefer if (options.stdout == .pipe) { + destroyPipe(stdout_pipe); + }; + + const stderr_pipe = if (options.stderr == .pipe) try posix.pipe2(pipe_flags) else undefined; + errdefer if (options.stderr == .pipe) { + destroyPipe(stderr_pipe); + }; + + const any_ignore = (options.stdin == .ignore or options.stdout == .ignore or options.stderr == .ignore); + const dev_null_fd = if (any_ignore) + posix.openZ("/dev/null", .{ .ACCMODE = .RDWR }, 0) catch |err| switch (err) { + error.PathAlreadyExists => unreachable, + error.NoSpaceLeft => unreachable, + error.FileTooBig => unreachable, + error.DeviceBusy => unreachable, + error.FileLocksUnsupported => unreachable, + error.BadPathName => unreachable, // Windows-only + error.WouldBlock => unreachable, + error.NetworkNotFound => unreachable, // Windows-only + error.Canceled => unreachable, // temporarily in the posix error set + error.SharingViolation => unreachable, // Windows-only + error.PipeBusy => unreachable, // not a pipe + error.AntivirusInterference => unreachable, // Windows-only + else => |e| return e, + } + else + undefined; + defer { + if (any_ignore) posix.close(dev_null_fd); + } + + const prog_pipe: [2]posix.fd_t = p: { + if (options.progress_node.index == .none) { + break :p .{ -1, -1 }; + } else { + // We use CLOEXEC for the same reason as in `pipe_flags`. + break :p try posix.pipe2(.{ .NONBLOCK = true, .CLOEXEC = true }); + } + }; + errdefer destroyPipe(prog_pipe); + + var arena_allocator = std.heap.ArenaAllocator.init(t.allocator); + defer arena_allocator.deinit(); + const arena = arena_allocator.allocator(); + + // The POSIX standard does not allow malloc() between fork() and execve(), + // and this allocator may be a libc allocator. + // I have personally observed the child process deadlocking when it tries + // to call malloc() due to a heap allocation between fork() and execve(), + // in musl v1.1.24. + // Additionally, we want to reduce the number of possible ways things + // can fail between fork() and execve(). + // Therefore, we do all the allocation for the execve() before the fork(). + // This means we must do the null-termination of argv and env vars here. + const argv_buf = try arena.allocSentinel(?[*:0]const u8, options.argv.len, null); + for (options.argv, 0..) |arg, i| argv_buf[i] = (try arena.dupeZ(u8, arg)).ptr; + + const prog_fileno = 3; + comptime assert(@max(posix.STDIN_FILENO, posix.STDOUT_FILENO, posix.STDERR_FILENO) + 1 == prog_fileno); + + const envp: [*:null]const ?[*:0]const u8 = m: { + const prog_fd: i32 = if (prog_pipe[1] == -1) -1 else prog_fileno; + if (options.env_map) |env_map| { + break :m (try env_map.createBlock(arena, .{ + .zig_progress_fd = prog_fd, + })).ptr; + } + break :m (try process.Environ.createBlock(.{ .block = t.environ.block }, arena, .{ + .zig_progress_fd = prog_fd, + })).ptr; + }; + + // This pipe communicates to the parent errors in the child between `fork` and `execvpe`. + // It is closed by the child (via CLOEXEC) without writing if `execvpe` succeeds. + const err_pipe: [2]posix.fd_t = try posix.pipe2(.{ .CLOEXEC = true }); + errdefer destroyPipe(err_pipe); + + t.scanEnviron(); // for PATH + const PATH = t.environ.string.PATH orelse "/usr/local/bin:/bin/:/usr/bin"; + + const pid_result = try posix.fork(); + if (pid_result == 0) { + // we are the child + setUpChildIo(options.stdin, stdin_pipe[0], posix.STDIN_FILENO, dev_null_fd) catch |err| forkBail(err_pipe[1], err); + setUpChildIo(options.stdout, stdout_pipe[1], posix.STDOUT_FILENO, dev_null_fd) catch |err| forkBail(err_pipe[1], err); + setUpChildIo(options.stderr, stderr_pipe[1], posix.STDERR_FILENO, dev_null_fd) catch |err| forkBail(err_pipe[1], err); + + if (options.cwd_dir) |cwd| { + posix.fchdir(cwd.handle) catch |err| forkBail(err_pipe[1], err); + } else if (options.cwd) |cwd| { + posix.chdir(cwd) catch |err| forkBail(err_pipe[1], err); + } + + // Must happen after fchdir above, the cwd file descriptor might be + // equal to prog_fileno and be clobbered by this dup2 call. + if (prog_pipe[1] != -1) posix.dup2(prog_pipe[1], prog_fileno) catch |err| forkBail(err_pipe[1], err); + + if (options.gid) |gid| { + posix.setregid(gid, gid) catch |err| forkBail(err_pipe[1], err); + } + + if (options.uid) |uid| { + switch (posix.errno(posix.system.setreuid(uid, uid))) { + .SUCCESS => {}, + .AGAIN => forkBail(err_pipe[1], error.ResourceLimitReached), + .INVAL => forkBail(err_pipe[1], error.InvalidUserId), + .PERM => forkBail(err_pipe[1], error.PermissionDenied), + else => forkBail(err_pipe[1], error.Unexpected), + } + } + + if (options.pgid) |pid| { + switch (posix.errno(posix.system.setpgid(0, pid))) { + .SUCCESS => {}, + .ACCES => forkBail(err_pipe[1], error.ProcessAlreadyExec), + .INVAL => forkBail(err_pipe[1], error.InvalidProcessGroupId), + .PERM => forkBail(err_pipe[1], error.PermissionDenied), + else => forkBail(err_pipe[1], error.Unexpected), + } + } + + if (options.start_suspended) { + switch (posix.errno(posix.system.kill(posix.system.getpid(), .STOP))) { + .SUCCESS => {}, + .PERM => forkBail(err_pipe[1], error.PermissionDenied), + else => forkBail(err_pipe[1], error.Unexpected), + } + } + + const err = execvpeZ_expandArg0(options.expand_arg0, argv_buf.ptr[0].?, argv_buf.ptr, envp, PATH); + forkBail(err_pipe[1], err); + } + + const pid: posix.pid_t = @intCast(pid_result); // We are the parent. + + posix.close(err_pipe[1]); // make sure only the child holds the write end open + defer posix.close(err_pipe[0]); + + if (options.stdin == .pipe) posix.close(stdin_pipe[0]); + if (options.stdout == .pipe) posix.close(stdout_pipe[1]); + if (options.stderr == .pipe) posix.close(stderr_pipe[1]); + + if (prog_pipe[1] != -1) posix.close(prog_pipe[1]); + + options.progress_node.setIpcFd(prog_pipe[0]); + + // Wait for the child to report any errors in or before `execvpe`. + if (readIntFd(t, err_pipe[0])) |child_err_int| { + const child_err: process.SpawnError = @errorCast(@errorFromInt(child_err_int)); + return child_err; + } else |read_err| switch (read_err) { + error.EndOfStream => { + // Write end closed by CLOEXEC at the time of the `execvpe` call, + // indicating success. + }, + else => { + // Problem reading the error from the error reporting pipe. We + // don't know if the child is alive or dead. Better to assume it is + // alive so the resource does not risk being leaked. + }, + } + + return .{ + .id = pid, + .stdin = switch (options.stdin) { + .pipe => .{ .handle = stdin_pipe[1] }, + else => null, + }, + .stdout = switch (options.stdout) { + .pipe => .{ .handle = stdout_pipe[0] }, + else => null, + }, + .stderr = switch (options.stderr) { + .pipe => .{ .handle = stderr_pipe[0] }, + else => null, + }, + .request_resource_usage_statistics = options.request_resource_usage_statistics, + }; +} + +fn childWait(userdata: ?*anyopaque, child: *std.process.Child) process.Child.WaitError!process.Child.Term { + const t: *Threaded = @ptrCast(@alignCast(userdata)); + switch (native_os) { + .windows => return childWaitWindows(t, child), + else => return childWaitPosix(t, child), + } +} + +fn childKill(userdata: ?*anyopaque, child: *std.process.Child) void { + const t: *Threaded = @ptrCast(@alignCast(userdata)); + if (is_windows) { + childKillWindows(t, child, 1) catch { + childCleanupStreams(child); + child.id = null; + }; + } else { + childKillPosix(t, child) catch { + childCleanupStreams(child); + child.id = null; + }; + } +} + +fn childKillWindows(t: *Threaded, child: *process.Child, exit_code: windows.UINT) !void { + windows.TerminateProcess(child.id, exit_code) catch |err| switch (err) { + error.AccessDenied => { + // Usually when TerminateProcess triggers a ACCESS_DENIED error, it + // indicates that the process has already exited, but there may be + // some rare edge cases where our process handle no longer has the + // PROCESS_TERMINATE access right, so let's do another check to make + // sure the process is really no longer running: + windows.WaitForSingleObjectEx(child.id, 0, false) catch return err; + return error.AlreadyTerminated; + }, + else => return err, + }; + try childWaitWindows(t, child); +} + +fn childWaitWindows(t: *Threaded, child: *process.Child) process.Child.WaitError!process.Child.Term { + _ = t; // TODO cancelation + windows.WaitForSingleObjectEx(child.id, windows.INFINITE, false); + + const term: process.Child.Term = x: { + var exit_code: windows.DWORD = undefined; + if (windows.kernel32.GetExitCodeProcess(child.id, &exit_code) == 0) { + break :x .{ .unknown = 0 }; + } else { + break :x .{ .exited = @as(u8, @truncate(exit_code)) }; + } + }; + + if (child.request_resource_usage_statistics) { + child.resource_usage_statistics.rusage = try windows.GetProcessMemoryInfo(child.id); + } + + posix.close(child.id); + posix.close(child.thread_handle); + childCleanupStreams(child); + child.id = null; + return term; +} + +fn childWaitPosix(t: *Threaded, child: *process.Child) process.Child.WaitError!process.Child.Term { + _ = t; // TODO cancelation + const pid = child.id.?; + const res: posix.WaitPidResult = res: { + if (child.request_resource_usage_statistics and have_wait4) { + var ru: posix.rusage = undefined; + const res = posix.wait4(pid, 0, &ru); + child.resource_usage_statistics.rusage = ru; + break :res res; + } + break :res posix.waitpid(pid, 0); + }; + const status = res.status; + childCleanupStreams(child); + child.id = null; + return statusToTerm(status); +} + +fn statusToTerm(status: u32) process.Child.Term { + return if (posix.W.IFEXITED(status)) + .{ .exited = posix.W.EXITSTATUS(status) } + else if (posix.W.IFSIGNALED(status)) + .{ .signal = posix.W.TERMSIG(status) } + else if (posix.W.IFSTOPPED(status)) + .{ .stopped = posix.W.STOPSIG(status) } + else + .{ .unknown = status }; +} + +fn childKillPosix(t: *Threaded, child: *process.Child) !void { + try posix.kill(child.id.?, posix.SIG.TERM); + _ = try childWaitPosix(t, child); +} + +fn childCleanupStreams(child: *process.Child) void { + if (child.stdin) |*stdin| { + posix.close(stdin.handle); + child.stdin = null; + } + if (child.stdout) |*stdout| { + posix.close(stdout.handle); + child.stdout = null; + } + if (child.stderr) |*stderr| { + posix.close(stderr.handle); + child.stderr = null; + } +} + +/// Errors that can occur between fork() and execv() +const ForkBailError = process.SpawnError || process.ReplaceError; + +/// Child of fork calls this to report an error to the fork parent. Then the +/// child exits. +fn forkBail(fd: posix.fd_t, err: ForkBailError) noreturn { + writeIntFd(fd, @as(ErrInt, @intFromError(err))) catch {}; + // If we're linking libc, some naughty applications may have registered atexit handlers + // which we really do not want to run in the fork child. I caught LLVM doing this and + // it caused a deadlock instead of doing an exit syscall. In the words of Avril Lavigne, + // "Why'd you have to go and make things so complicated?" + if (builtin.link_libc) { + // The _exit(2) function does nothing but make the exit syscall, unlike exit(3) + std.c._exit(1); + } + posix.system.exit(1); +} + +fn writeIntFd(fd: posix.fd_t, value: ErrInt) !void { + var buffer: [8]u8 = undefined; + std.mem.writeInt(u64, &buffer, value, .little); + // Skip the cancel mechanism. + var i: usize = 0; + while (true) { + const rc = posix.system.write(fd, buffer[i..].ptr, buffer.len - i); + switch (posix.errno(rc)) { + .SUCCESS => { + const n: usize = @intCast(rc); + i += n; + if (buffer.len - i == 0) return; + }, + .INTR => continue, + else => return error.SystemResources, + } + } +} + +fn readIntFd(t: *Threaded, fd: posix.fd_t) !ErrInt { + _ = t; // TODO cancelation + var buffer: [8]u8 = undefined; + var i: usize = 0; + while (true) { + const rc = posix.system.read(fd, buffer[i..].ptr, buffer.len - i); + switch (posix.errno(rc)) { + .SUCCESS => { + const n: usize = @intCast(rc); + if (n == 0) return error.EndOfStream; + i += n; + continue; + }, + .INTR => continue, + else => |err| return posix.unexpectedErrno(err), + } + } + return @intCast(std.mem.readInt(u64, &buffer, .little)); +} + +const ErrInt = std.meta.Int(.unsigned, @sizeOf(anyerror) * 8); + +fn destroyPipe(pipe: [2]posix.fd_t) void { + if (pipe[0] != -1) posix.close(pipe[0]); + if (pipe[0] != pipe[1]) posix.close(pipe[1]); +} + +fn setUpChildIo(stdio: process.SpawnOptions.StdIo, pipe_fd: i32, std_fileno: i32, dev_null_fd: i32) !void { + switch (stdio) { + .pipe => try posix.dup2(pipe_fd, std_fileno), + .close => posix.close(std_fileno), + .inherit => {}, + .ignore => try posix.dup2(dev_null_fd, std_fileno), + .file => @panic("TODO implement setUpChildIo when file is used"), + } +} + +fn processSpawnWindows(userdata: ?*anyopaque, child: *process.Child) process.SpawnError!void { + const t: *Threaded = @ptrCast(@alignCast(userdata)); + _ = t; + + var saAttr: windows.SECURITY_ATTRIBUTES = .{ + .nLength = @sizeOf(windows.SECURITY_ATTRIBUTES), + .bInheritHandle = windows.TRUE, + .lpSecurityDescriptor = null, + }; + + const any_ignore = + child.stdin_behavior == .ignore or + child.stdout_behavior == .ignore or + child.stderr_behavior == .ignore; + + const nul_handle = if (any_ignore) + // "\Device\Null" or "\??\NUL" + windows.OpenFile(&[_]u16{ '\\', 'D', 'e', 'v', 'i', 'c', 'e', '\\', 'N', 'u', 'l', 'l' }, .{ + .access_mask = .{ + .STANDARD = .{ .SYNCHRONIZE = true }, + .GENERIC = .{ .WRITE = true, .READ = true }, + }, + .sa = &saAttr, + .creation = .OPEN, + }) catch |err| switch (err) { + error.PathAlreadyExists => return error.Unexpected, // not possible for "NUL" + error.PipeBusy => return error.Unexpected, // not possible for "NUL" + error.NoDevice => return error.Unexpected, // not possible for "NUL" + error.FileNotFound => return error.Unexpected, // not possible for "NUL" + error.AccessDenied => return error.Unexpected, // not possible for "NUL" + error.NameTooLong => return error.Unexpected, // not possible for "NUL" + error.WouldBlock => return error.Unexpected, // not possible for "NUL" + error.NetworkNotFound => return error.Unexpected, // not possible for "NUL" + error.AntivirusInterference => return error.Unexpected, // not possible for "NUL" + error.OperationCanceled => return error.Unexpected, // we're not canceling the operation + else => |e| return e, + } + else + undefined; + defer { + if (any_ignore) posix.close(nul_handle); + } + + var g_hChildStd_IN_Rd: ?windows.HANDLE = null; + var g_hChildStd_IN_Wr: ?windows.HANDLE = null; + switch (child.stdin_behavior) { + .pipe => { + try windowsMakePipeIn(&g_hChildStd_IN_Rd, &g_hChildStd_IN_Wr, &saAttr); + }, + .ignore => { + g_hChildStd_IN_Rd = nul_handle; + }, + .inherit => { + g_hChildStd_IN_Rd = windows.GetStdHandle(windows.STD_INPUT_HANDLE) catch null; + }, + .close => { + g_hChildStd_IN_Rd = null; + }, + } + errdefer if (child.stdin_behavior == .pipe) { + windowsDestroyPipe(g_hChildStd_IN_Rd, g_hChildStd_IN_Wr); + }; + + var g_hChildStd_OUT_Rd: ?windows.HANDLE = null; + var g_hChildStd_OUT_Wr: ?windows.HANDLE = null; + switch (child.stdout_behavior) { + .pipe => { + try windowsMakeAsyncPipe(&g_hChildStd_OUT_Rd, &g_hChildStd_OUT_Wr, &saAttr); + }, + .ignore => { + g_hChildStd_OUT_Wr = nul_handle; + }, + .inherit => { + g_hChildStd_OUT_Wr = windows.GetStdHandle(windows.STD_OUTPUT_HANDLE) catch null; + }, + .close => { + g_hChildStd_OUT_Wr = null; + }, + } + errdefer if (child.stdout_behavior == .pipe) { + windowsDestroyPipe(g_hChildStd_OUT_Rd, g_hChildStd_OUT_Wr); + }; + + var g_hChildStd_ERR_Rd: ?windows.HANDLE = null; + var g_hChildStd_ERR_Wr: ?windows.HANDLE = null; + switch (child.stderr_behavior) { + .pipe => { + try windowsMakeAsyncPipe(&g_hChildStd_ERR_Rd, &g_hChildStd_ERR_Wr, &saAttr); + }, + .ignore => { + g_hChildStd_ERR_Wr = nul_handle; + }, + .inherit => { + g_hChildStd_ERR_Wr = windows.GetStdHandle(windows.STD_ERROR_HANDLE) catch null; + }, + .close => { + g_hChildStd_ERR_Wr = null; + }, + } + errdefer if (child.stderr_behavior == .pipe) { + windowsDestroyPipe(g_hChildStd_ERR_Rd, g_hChildStd_ERR_Wr); + }; + + var siStartInfo = windows.STARTUPINFOW{ + .cb = @sizeOf(windows.STARTUPINFOW), + .hStdError = g_hChildStd_ERR_Wr, + .hStdOutput = g_hChildStd_OUT_Wr, + .hStdInput = g_hChildStd_IN_Rd, + .dwFlags = windows.STARTF_USESTDHANDLES, + + .lpReserved = null, + .lpDesktop = null, + .lpTitle = null, + .dwX = 0, + .dwY = 0, + .dwXSize = 0, + .dwYSize = 0, + .dwXCountChars = 0, + .dwYCountChars = 0, + .dwFillAttribute = 0, + .wShowWindow = 0, + .cbReserved2 = 0, + .lpReserved2 = null, + }; + var piProcInfo: windows.PROCESS_INFORMATION = undefined; + + const cwd_w = if (child.cwd) |cwd| try std.unicode.wtf8ToWtf16LeAllocZ(child.allocator, cwd) else null; + defer if (cwd_w) |cwd| child.allocator.free(cwd); + const cwd_w_ptr = if (cwd_w) |cwd| cwd.ptr else null; + + const maybe_envp_buf = if (child.env_map) |env_map| try process.createWindowsEnvBlock(child.allocator, env_map) else null; + defer if (maybe_envp_buf) |envp_buf| child.allocator.free(envp_buf); + const envp_ptr = if (maybe_envp_buf) |envp_buf| envp_buf.ptr else null; + + const app_name_wtf8 = child.argv[0]; + const app_name_is_absolute = Dir.path.isAbsolute(app_name_wtf8); + + // the cwd set in Child is in effect when choosing the executable path + // to match posix semantics + var cwd_path_w_needs_free = false; + const cwd_path_w = x: { + // If the app name is absolute, then we need to use its dirname as the cwd + if (app_name_is_absolute) { + cwd_path_w_needs_free = true; + const dir = Dir.path.dirname(app_name_wtf8).?; + break :x try std.unicode.wtf8ToWtf16LeAllocZ(child.allocator, dir); + } else if (child.cwd) |cwd| { + cwd_path_w_needs_free = true; + break :x try std.unicode.wtf8ToWtf16LeAllocZ(child.allocator, cwd); + } else { + break :x &[_:0]u16{}; // empty for cwd + } + }; + defer if (cwd_path_w_needs_free) child.allocator.free(cwd_path_w); + + // If the app name has more than just a filename, then we need to separate that + // into the basename and dirname and use the dirname as an addition to the cwd + // path. This is because NtQueryDirectoryFile cannot accept FileName params with + // path separators. + const app_basename_wtf8 = Dir.path.basename(app_name_wtf8); + // If the app name is absolute, then the cwd will already have the app's dirname in it, + // so only populate app_dirname if app name is a relative path with > 0 path separators. + const maybe_app_dirname_wtf8 = if (!app_name_is_absolute) Dir.path.dirname(app_name_wtf8) else null; + const app_dirname_w: ?[:0]u16 = x: { + if (maybe_app_dirname_wtf8) |app_dirname_wtf8| { + break :x try std.unicode.wtf8ToWtf16LeAllocZ(child.allocator, app_dirname_wtf8); + } + break :x null; + }; + defer if (app_dirname_w != null) child.allocator.free(app_dirname_w.?); + + const app_name_w = try std.unicode.wtf8ToWtf16LeAllocZ(child.allocator, app_basename_wtf8); + defer child.allocator.free(app_name_w); + + const flags: windows.CreateProcessFlags = .{ + .create_suspended = child.start_suspended, + .create_unicode_environment = true, + .create_no_window = child.create_no_window, + }; + + run: { + const PATH: [:0]const u16 = process.getenvW(std.unicode.utf8ToUtf16LeStringLiteral("PATH")) orelse &[_:0]u16{}; + const PATHEXT: [:0]const u16 = process.getenvW(std.unicode.utf8ToUtf16LeStringLiteral("PATHEXT")) orelse &[_:0]u16{}; + + // In case the command ends up being a .bat/.cmd script, we need to escape things using the cmd.exe rules + // and invoke cmd.exe ourselves in order to mitigate arbitrary command execution from maliciously + // constructed arguments. + // + // We'll need to wait until we're actually trying to run the command to know for sure + // if the resolved command has the `.bat` or `.cmd` extension, so we defer actually + // serializing the command line until we determine how it should be serialized. + var cmd_line_cache = WindowsCommandLineCache.init(child.allocator, child.argv); + defer cmd_line_cache.deinit(); + + var app_buf: std.ArrayList(u16) = .empty; + defer app_buf.deinit(child.allocator); + + try app_buf.appendSlice(child.allocator, app_name_w); + + var dir_buf: std.ArrayList(u16) = .empty; + defer dir_buf.deinit(child.allocator); + + if (cwd_path_w.len > 0) { + try dir_buf.appendSlice(child.allocator, cwd_path_w); + } + if (app_dirname_w) |app_dir| { + if (dir_buf.items.len > 0) try dir_buf.append(child.allocator, Dir.path.sep); + try dir_buf.appendSlice(child.allocator, app_dir); + } + + windowsCreateProcessPathExt(child.allocator, io, &dir_buf, &app_buf, PATHEXT, &cmd_line_cache, envp_ptr, cwd_w_ptr, flags, &siStartInfo, &piProcInfo) catch |no_path_err| { + const original_err = switch (no_path_err) { + // argv[0] contains unsupported characters that will never resolve to a valid exe. + error.InvalidArg0 => return error.FileNotFound, + error.FileNotFound, error.InvalidExe, error.AccessDenied => |e| e, + error.UnrecoverableInvalidExe => return error.InvalidExe, + else => |e| return e, + }; + + // If the app name had path separators, that disallows PATH searching, + // and there's no need to search the PATH if the app name is absolute. + // We still search the path if the cwd is absolute because of the + // "cwd set in Child is in effect when choosing the executable path + // to match posix semantics" behavior--we don't want to skip searching + // the PATH just because we were trying to set the cwd of the child process. + if (app_dirname_w != null or app_name_is_absolute) { + return original_err; + } + + var it = std.mem.tokenizeScalar(u16, PATH, ';'); + while (it.next()) |search_path| { + dir_buf.clearRetainingCapacity(); + try dir_buf.appendSlice(child.allocator, search_path); + + if (windowsCreateProcessPathExt(child.allocator, io, &dir_buf, &app_buf, PATHEXT, &cmd_line_cache, envp_ptr, cwd_w_ptr, flags, &siStartInfo, &piProcInfo)) { + break :run; + } else |err| switch (err) { + // argv[0] contains unsupported characters that will never resolve to a valid exe. + error.InvalidArg0 => return error.FileNotFound, + error.FileNotFound, error.AccessDenied, error.InvalidExe => continue, + error.UnrecoverableInvalidExe => return error.InvalidExe, + else => |e| return e, + } + } else { + return original_err; + } + }; + } + + if (g_hChildStd_IN_Wr) |h| { + child.stdin = File{ .handle = h }; + } else { + child.stdin = null; + } + if (g_hChildStd_OUT_Rd) |h| { + child.stdout = File{ .handle = h }; + } else { + child.stdout = null; + } + if (g_hChildStd_ERR_Rd) |h| { + child.stderr = File{ .handle = h }; + } else { + child.stderr = null; + } + + child.id = piProcInfo.hProcess; + child.thread_handle = piProcInfo.hThread; + child.term = null; + + if (child.stdin_behavior == .pipe) { + posix.close(g_hChildStd_IN_Rd.?); + } + if (child.stderr_behavior == .pipe) { + posix.close(g_hChildStd_ERR_Wr.?); + } + if (child.stdout_behavior == .pipe) { + posix.close(g_hChildStd_OUT_Wr.?); + } +} + +/// Expects `app_buf` to contain exactly the app name, and `dir_buf` to contain exactly the dir path. +/// After return, `app_buf` will always contain exactly the app name and `dir_buf` will always contain exactly the dir path. +/// Note: `app_buf` should not contain any leading path separators. +/// Note: If the dir is the cwd, dir_buf should be empty (len = 0). +fn windowsCreateProcessPathExt( + allocator: Allocator, + dir_buf: *std.ArrayList(u16), + app_buf: *std.ArrayList(u16), + pathext: [:0]const u16, + cmd_line_cache: *WindowsCommandLineCache, + envp_ptr: ?[*]u16, + cwd_ptr: ?[*:0]u16, + flags: windows.CreateProcessFlags, + lpStartupInfo: *windows.STARTUPINFOW, + lpProcessInformation: *windows.PROCESS_INFORMATION, +) !void { + const app_name_len = app_buf.items.len; + const dir_path_len = dir_buf.items.len; + + if (app_name_len == 0) return error.FileNotFound; + + defer app_buf.shrinkRetainingCapacity(app_name_len); + defer dir_buf.shrinkRetainingCapacity(dir_path_len); + + // The name of the game here is to avoid CreateProcessW calls at all costs, + // and only ever try calling it when we have a real candidate for execution. + // Secondarily, we want to minimize the number of syscalls used when checking + // for each PATHEXT-appended version of the app name. + // + // An overview of the technique used: + // - Open the search directory for iteration (either cwd or a path from PATH) + // - Use NtQueryDirectoryFile with a wildcard filename of `*` to + // check if anything that could possibly match either the unappended version + // of the app name or any of the versions with a PATHEXT value appended exists. + // - If the wildcard NtQueryDirectoryFile call found nothing, we can exit early + // without needing to use PATHEXT at all. + // + // This allows us to use a sequence + // for any directory that doesn't contain any possible matches, instead of having + // to use a separate look up for each individual filename combination (unappended + + // each PATHEXT appended). For directories where the wildcard *does* match something, + // we iterate the matches and take note of any that are either the unappended version, + // or a version with a supported PATHEXT appended. We then try calling CreateProcessW + // with the found versions in the appropriate order. + + // In the future, child process execution needs to move to Io implementation. + // Under those conditions, here we will have access to lower level directory + // opening function knowing which implementation we are in. Here, we imitate + // that scenario. + var dir = dir: { + // needs to be null-terminated + try dir_buf.append(allocator, 0); + defer dir_buf.shrinkRetainingCapacity(dir_path_len); + const dir_path_z = dir_buf.items[0 .. dir_buf.items.len - 1 :0]; + const prefixed_path = try windows.wToPrefixedFileW(null, dir_path_z); + break :dir dirOpenDirWindows(.cwd(), prefixed_path.span(), .{ + .iterate = true, + }) catch return error.FileNotFound; + }; + defer windows.CloseHandle(dir.handle); + + // Add wildcard and null-terminator + try app_buf.append(allocator, '*'); + try app_buf.append(allocator, 0); + const app_name_wildcard = app_buf.items[0 .. app_buf.items.len - 1 :0]; + + // This 2048 is arbitrary, we just want it to be large enough to get multiple FILE_DIRECTORY_INFORMATION entries + // returned per NtQueryDirectoryFile call. + var file_information_buf: [2048]u8 align(@alignOf(windows.FILE_DIRECTORY_INFORMATION)) = undefined; + const file_info_maximum_single_entry_size = @sizeOf(windows.FILE_DIRECTORY_INFORMATION) + (windows.NAME_MAX * 2); + if (file_information_buf.len < file_info_maximum_single_entry_size) { + @compileError("file_information_buf must be large enough to contain at least one maximum size FILE_DIRECTORY_INFORMATION entry"); + } + var io_status: windows.IO_STATUS_BLOCK = undefined; + + const num_supported_pathext = @typeInfo(process.WindowsExtension).@"enum".fields.len; + var pathext_seen = [_]bool{false} ** num_supported_pathext; + var any_pathext_seen = false; + var unappended_exists = false; + + // Fully iterate the wildcard matches via NtQueryDirectoryFile and take note of all versions + // of the app_name we should try to spawn. + // Note: This is necessary because the order of the files returned is filesystem-dependent: + // On NTFS, `blah.exe*` will always return `blah.exe` first if it exists. + // On FAT32, it's possible for something like `blah.exe.obj` to be returned first. + while (true) { + const app_name_len_bytes = std.math.cast(u16, app_name_wildcard.len * 2) orelse return error.NameTooLong; + var app_name_unicode_string = windows.UNICODE_STRING{ + .Length = app_name_len_bytes, + .MaximumLength = app_name_len_bytes, + .Buffer = @constCast(app_name_wildcard.ptr), + }; + const rc = windows.ntdll.NtQueryDirectoryFile( + dir.handle, + null, + null, + null, + &io_status, + &file_information_buf, + file_information_buf.len, + .Directory, + windows.FALSE, // single result + &app_name_unicode_string, + windows.FALSE, // restart iteration + ); + + // If we get nothing with the wildcard, then we can just bail out + // as we know appending PATHEXT will not yield anything. + switch (rc) { + .SUCCESS => {}, + .NO_SUCH_FILE => return error.FileNotFound, + .NO_MORE_FILES => break, + .ACCESS_DENIED => return error.AccessDenied, + else => return windows.unexpectedStatus(rc), + } + + // According to the docs, this can only happen if there is not enough room in the + // buffer to write at least one complete FILE_DIRECTORY_INFORMATION entry. + // Therefore, this condition should not be possible to hit with the buffer size we use. + std.debug.assert(io_status.Information != 0); + + var it = windows.FileInformationIterator(windows.FILE_DIRECTORY_INFORMATION){ .buf = &file_information_buf }; + while (it.next()) |info| { + // Skip directories + if (info.FileAttributes.DIRECTORY) continue; + const filename = @as([*]u16, @ptrCast(&info.FileName))[0 .. info.FileNameLength / 2]; + // Because all results start with the app_name since we're using the wildcard `app_name*`, + // if the length is equal to app_name then this is an exact match + if (filename.len == app_name_len) { + // Note: We can't break early here because it's possible that the unappended version + // fails to spawn, in which case we still want to try the PATHEXT appended versions. + unappended_exists = true; + } else if (windowsCreateProcessSupportsExtension(filename[app_name_len..])) |pathext_ext| { + pathext_seen[@intFromEnum(pathext_ext)] = true; + any_pathext_seen = true; + } + } + } + + const unappended_err = unappended: { + if (unappended_exists) { + if (dir_path_len != 0) switch (dir_buf.items[dir_buf.items.len - 1]) { + '/', '\\' => {}, + else => try dir_buf.append(allocator, Dir.path.sep), + }; + try dir_buf.appendSlice(allocator, app_buf.items[0..app_name_len]); + try dir_buf.append(allocator, 0); + const full_app_name = dir_buf.items[0 .. dir_buf.items.len - 1 :0]; + + const is_bat_or_cmd = bat_or_cmd: { + const app_name = app_buf.items[0..app_name_len]; + const ext_start = std.mem.lastIndexOfScalar(u16, app_name, '.') orelse break :bat_or_cmd false; + const ext = app_name[ext_start..]; + const ext_enum = windowsCreateProcessSupportsExtension(ext) orelse break :bat_or_cmd false; + switch (ext_enum) { + .cmd, .bat => break :bat_or_cmd true, + else => break :bat_or_cmd false, + } + }; + const cmd_line_w = if (is_bat_or_cmd) + try cmd_line_cache.scriptCommandLine(full_app_name) + else + try cmd_line_cache.commandLine(); + const app_name_w = if (is_bat_or_cmd) + try cmd_line_cache.cmdExePath() + else + full_app_name; + + if (windowsCreateProcess(app_name_w.ptr, cmd_line_w.ptr, envp_ptr, cwd_ptr, flags, lpStartupInfo, lpProcessInformation)) |_| { + return; + } else |err| switch (err) { + error.FileNotFound, + error.AccessDenied, + => break :unappended err, + error.InvalidExe => { + // On InvalidExe, if the extension of the app name is .exe then + // it's treated as an unrecoverable error. Otherwise, it'll be + // skipped as normal. + const app_name = app_buf.items[0..app_name_len]; + const ext_start = std.mem.lastIndexOfScalar(u16, app_name, '.') orelse break :unappended err; + const ext = app_name[ext_start..]; + if (windows.eqlIgnoreCaseWtf16(ext, std.unicode.utf8ToUtf16LeStringLiteral(".EXE"))) { + return error.UnrecoverableInvalidExe; + } + break :unappended err; + }, + else => return err, + } + } + break :unappended error.FileNotFound; + }; + + if (!any_pathext_seen) return unappended_err; + + // Now try any PATHEXT appended versions that we've seen + var ext_it = std.mem.tokenizeScalar(u16, pathext, ';'); + while (ext_it.next()) |ext| { + const ext_enum = windowsCreateProcessSupportsExtension(ext) orelse continue; + if (!pathext_seen[@intFromEnum(ext_enum)]) continue; + + dir_buf.shrinkRetainingCapacity(dir_path_len); + if (dir_path_len != 0) switch (dir_buf.items[dir_buf.items.len - 1]) { + '/', '\\' => {}, + else => try dir_buf.append(allocator, Dir.path.sep), + }; + try dir_buf.appendSlice(allocator, app_buf.items[0..app_name_len]); + try dir_buf.appendSlice(allocator, ext); + try dir_buf.append(allocator, 0); + const full_app_name = dir_buf.items[0 .. dir_buf.items.len - 1 :0]; + + const is_bat_or_cmd = switch (ext_enum) { + .cmd, .bat => true, + else => false, + }; + const cmd_line_w = if (is_bat_or_cmd) + try cmd_line_cache.scriptCommandLine(full_app_name) + else + try cmd_line_cache.commandLine(); + const app_name_w = if (is_bat_or_cmd) + try cmd_line_cache.cmdExePath() + else + full_app_name; + + if (windowsCreateProcess(app_name_w.ptr, cmd_line_w.ptr, envp_ptr, cwd_ptr, flags, lpStartupInfo, lpProcessInformation)) |_| { + return; + } else |err| switch (err) { + error.FileNotFound => continue, + error.AccessDenied => continue, + error.InvalidExe => { + // On InvalidExe, if the extension of the app name is .exe then + // it's treated as an unrecoverable error. Otherwise, it'll be + // skipped as normal. + if (windows.eqlIgnoreCaseWtf16(ext, std.unicode.utf8ToUtf16LeStringLiteral(".EXE"))) { + return error.UnrecoverableInvalidExe; + } + continue; + }, + else => return err, + } + } + + return unappended_err; +} + +fn windowsCreateProcess( + app_name: [*:0]u16, + cmd_line: [*:0]u16, + envp_ptr: ?[*]u16, + cwd_ptr: ?[*:0]u16, + flags: windows.CreateProcessFlags, + lpStartupInfo: *windows.STARTUPINFOW, + lpProcessInformation: *windows.PROCESS_INFORMATION, +) !void { + // TODO the docs for environment pointer say: + // > A pointer to the environment block for the new process. If this parameter + // > is NULL, the new process uses the environment of the calling process. + // > ... + // > An environment block can contain either Unicode or ANSI characters. If + // > the environment block pointed to by lpEnvironment contains Unicode + // > characters, be sure that dwCreationFlags includes CREATE_UNICODE_ENVIRONMENT. + // > If this parameter is NULL and the environment block of the parent process + // > contains Unicode characters, you must also ensure that dwCreationFlags + // > includes CREATE_UNICODE_ENVIRONMENT. + // This seems to imply that we have to somehow know whether our process parent passed + // CREATE_UNICODE_ENVIRONMENT if we want to pass NULL for the environment parameter. + // Since we do not know this information that would imply that we must not pass NULL + // for the parameter. + // However this would imply that programs compiled with -DUNICODE could not pass + // environment variables to programs that were not, which seems unlikely. + // More investigation is needed. + return windows.CreateProcessW( + app_name, + cmd_line, + null, + null, + windows.TRUE, + flags, + @as(?*anyopaque, @ptrCast(envp_ptr)), + cwd_ptr, + lpStartupInfo, + lpProcessInformation, + ); +} + +/// Case-insensitive WTF-16 lookup +fn windowsCreateProcessSupportsExtension(ext: []const u16) ?process.WindowsExtension { + comptime { + // Ensures keeping this function in sync with the enum. + const fields = @typeInfo(process.WindowsExtension).@"enum".fields; + assert(fields.len == 4); + assert(@intFromEnum(process.WindowsExtension.bat) == 0); + assert(@intFromEnum(process.WindowsExtension.cmd) == 1); + assert(@intFromEnum(process.WindowsExtension.com) == 2); + assert(@intFromEnum(process.WindowsExtension.exe) == 3); + } + + if (ext.len != 4) return null; + const State = enum { + start, + dot, + b, + ba, + c, + cm, + co, + e, + ex, + }; + var state: State = .start; + for (ext) |c| switch (state) { + .start => switch (c) { + '.' => state = .dot, + else => return null, + }, + .dot => switch (c) { + 'b', 'B' => state = .b, + 'c', 'C' => state = .c, + 'e', 'E' => state = .e, + else => return null, + }, + .b => switch (c) { + 'a', 'A' => state = .ba, + else => return null, + }, + .c => switch (c) { + 'm', 'M' => state = .cm, + 'o', 'O' => state = .co, + else => return null, + }, + .e => switch (c) { + 'x', 'X' => state = .ex, + else => return null, + }, + .ba => switch (c) { + 't', 'T' => return .bat, + else => return null, + }, + .cm => switch (c) { + 'd', 'D' => return .cmd, + else => return null, + }, + .co => switch (c) { + 'm', 'M' => return .com, + else => return null, + }, + .ex => switch (c) { + 'e', 'E' => return .exe, + else => return null, + }, + }; + return null; +} + +test windowsCreateProcessSupportsExtension { + try std.testing.expectEqual(process.WindowsExtension.exe, windowsCreateProcessSupportsExtension(&[_]u16{ '.', 'e', 'X', 'e' }).?); + try std.testing.expect(windowsCreateProcessSupportsExtension(&[_]u16{ '.', 'e', 'X', 'e', 'c' }) == null); +} + +/// Serializes argv into a WTF-16 encoded command-line string for use with CreateProcessW. +/// +/// Serialization is done on-demand and the result is cached in order to allow for: +/// - Only serializing the particular type of command line needed (`.bat`/`.cmd` +/// command line serialization is different from `.exe`/etc) +/// - Reusing the serialized command lines if necessary (i.e. if the execution +/// of a command fails and the PATH is going to be continued to be searched +/// for more candidates) +const WindowsCommandLineCache = struct { + cmd_line: ?[:0]u16 = null, + script_cmd_line: ?[:0]u16 = null, + cmd_exe_path: ?[:0]u16 = null, + argv: []const []const u8, + allocator: Allocator, + + fn init(allocator: Allocator, argv: []const []const u8) WindowsCommandLineCache { + return .{ + .allocator = allocator, + .argv = argv, + }; + } + + fn deinit(self: *WindowsCommandLineCache) void { + if (self.cmd_line) |cmd_line| self.allocator.free(cmd_line); + if (self.script_cmd_line) |script_cmd_line| self.allocator.free(script_cmd_line); + if (self.cmd_exe_path) |cmd_exe_path| self.allocator.free(cmd_exe_path); + } + + fn commandLine(self: *WindowsCommandLineCache) ![:0]u16 { + if (self.cmd_line == null) { + self.cmd_line = try argvToCommandLineWindows(self.allocator, self.argv); + } + return self.cmd_line.?; + } + + /// Not cached, since the path to the batch script will change during PATH searching. + /// `script_path` should be as qualified as possible, e.g. if the PATH is being searched, + /// then script_path should include both the search path and the script filename + /// (this allows avoiding cmd.exe having to search the PATH again). + fn scriptCommandLine(self: *WindowsCommandLineCache, script_path: []const u16) ![:0]u16 { + if (self.script_cmd_line) |v| self.allocator.free(v); + self.script_cmd_line = try argvToScriptCommandLineWindows( + self.allocator, + script_path, + self.argv[1..], + ); + return self.script_cmd_line.?; + } + + fn cmdExePath(self: *WindowsCommandLineCache) ![:0]u16 { + if (self.cmd_exe_path == null) { + self.cmd_exe_path = try windowsCmdExePath(self.allocator); + } + return self.cmd_exe_path.?; + } +}; + +/// Returns the absolute path of `cmd.exe` within the Windows system directory. +/// The caller owns the returned slice. +fn windowsCmdExePath(allocator: Allocator) error{ OutOfMemory, Unexpected }![:0]u16 { + var buf = try std.ArrayList(u16).initCapacity(allocator, 128); + errdefer buf.deinit(allocator); + while (true) { + const unused_slice = buf.unusedCapacitySlice(); + // TODO: Get the system directory from PEB.ReadOnlyStaticServerData + const len = windows.kernel32.GetSystemDirectoryW(@ptrCast(unused_slice), @intCast(unused_slice.len)); + if (len == 0) { + switch (windows.GetLastError()) { + else => |err| return windows.unexpectedError(err), + } + } + if (len > unused_slice.len) { + try buf.ensureUnusedCapacity(allocator, len); + } else { + buf.items.len = len; + break; + } + } + switch (buf.items[buf.items.len - 1]) { + '/', '\\' => {}, + else => try buf.append(allocator, Dir.path.sep), + } + try buf.appendSlice(allocator, std.unicode.utf8ToUtf16LeStringLiteral("cmd.exe")); + return try buf.toOwnedSliceSentinel(allocator, 0); +} + +const ArgvToScriptCommandLineError = error{ + OutOfMemory, + InvalidWtf8, + /// NUL (U+0000), LF (U+000A), CR (U+000D) are not allowed + /// within arguments when executing a `.bat`/`.cmd` script. + /// - NUL/LF signifiies end of arguments, so anything afterwards + /// would be lost after execution. + /// - CR is stripped by `cmd.exe`, so any CR codepoints + /// would be lost after execution. + InvalidBatchScriptArg, +}; + +/// Serializes `argv` to a Windows command-line string that uses `cmd.exe /c` and `cmd.exe`-specific +/// escaping rules. The caller owns the returned slice. +/// +/// Escapes `argv` using the suggested mitigation against arbitrary command execution from: +/// https://flatt.tech/research/posts/batbadbut-you-cant-securely-execute-commands-on-windows/ +/// +/// The return of this function will look like +/// `cmd.exe /d /e:ON /v:OFF /c ""` +/// and should be used as the `lpCommandLine` of `CreateProcessW`, while the +/// return of `windowsCmdExePath` should be used as `lpApplicationName`. +/// +/// Should only be used when spawning `.bat`/`.cmd` scripts, see `argvToCommandLineWindows` otherwise. +/// The `.bat`/`.cmd` file must be known to both have the `.bat`/`.cmd` extension and exist on the filesystem. +fn argvToScriptCommandLineWindows( + allocator: Allocator, + /// Path to the `.bat`/`.cmd` script. If this path is relative, it is assumed to be relative to the CWD. + /// The script must have been verified to exist at this path before calling this function. + script_path: []const u16, + /// Arguments, not including the script name itself. Expected to be encoded as WTF-8. + script_args: []const []const u8, +) ArgvToScriptCommandLineError![:0]u16 { + var buf = try std.array_list.Managed(u8).initCapacity(allocator, 64); + defer buf.deinit(); + + // `/d` disables execution of AutoRun commands. + // `/e:ON` and `/v:OFF` are needed for BatBadBut mitigation: + // > If delayed expansion is enabled via the registry value DelayedExpansion, + // > it must be disabled by explicitly calling cmd.exe with the /V:OFF option. + // > Escaping for % requires the command extension to be enabled. + // > If it’s disabled via the registry value EnableExtensions, it must be enabled with the /E:ON option. + // https://flatt.tech/research/posts/batbadbut-you-cant-securely-execute-commands-on-windows/ + buf.appendSliceAssumeCapacity("cmd.exe /d /e:ON /v:OFF /c \""); + + // Always quote the path to the script arg + buf.appendAssumeCapacity('"'); + // We always want the path to the batch script to include a path separator in order to + // avoid cmd.exe searching the PATH for the script. This is not part of the arbitrary + // command execution mitigation, we just know exactly what script we want to execute + // at this point, and potentially making cmd.exe re-find it is unnecessary. + // + // If the script path does not have a path separator, then we know its relative to CWD and + // we can just put `.\` in the front. + if (std.mem.findAny(u16, script_path, &[_]u16{ + std.mem.nativeToLittle(u16, '\\'), std.mem.nativeToLittle(u16, '/'), + }) == null) { + try buf.appendSlice(".\\"); + } + // Note that we don't do any escaping/mitigations for this argument, since the relevant + // characters (", %, etc) are illegal in file paths and this function should only be called + // with script paths that have been verified to exist. + try std.unicode.wtf16LeToWtf8ArrayList(&buf, script_path); + buf.appendAssumeCapacity('"'); + + for (script_args) |arg| { + // Literal carriage returns get stripped when run through cmd.exe + // and NUL/newlines act as 'end of command.' Because of this, it's basically + // always a mistake to include these characters in argv, so it's + // an error condition in order to ensure that the return of this + // function can always roundtrip through cmd.exe. + if (std.mem.findAny(u8, arg, "\x00\r\n") != null) { + return error.InvalidBatchScriptArg; + } + + // Separate args with a space. + try buf.append(' '); + + // Need to quote if the argument is empty (otherwise the arg would just be lost) + // or if the last character is a `\`, since then something like "%~2" in a .bat + // script would cause the closing " to be escaped which we don't want. + var needs_quotes = arg.len == 0 or arg[arg.len - 1] == '\\'; + if (!needs_quotes) { + for (arg) |c| { + switch (c) { + // Known good characters that don't need to be quoted + 'A'...'Z', 'a'...'z', '0'...'9', '#', '$', '*', '+', '-', '.', '/', ':', '?', '@', '\\', '_' => {}, + // When in doubt, quote + else => { + needs_quotes = true; + break; + }, + } + } + } + if (needs_quotes) { + try buf.append('"'); + } + var backslashes: usize = 0; + for (arg) |c| { + switch (c) { + '\\' => { + backslashes += 1; + }, + '"' => { + try buf.appendNTimes('\\', backslashes); + try buf.append('"'); + backslashes = 0; + }, + // Replace `%` with `%%cd:~,%`. + // + // cmd.exe allows extracting a substring from an environment + // variable with the syntax: `%foo:~,%`. + // Therefore, `%cd:~,%` will always expand to an empty string + // since both the start and end index are blank, and it is assumed + // that `%cd%` is always available since it is a built-in variable + // that corresponds to the current directory. + // + // This means that replacing `%foo%` with `%%cd:~,%foo%%cd:~,%` + // will stop `%foo%` from being expanded and *after* expansion + // we'll still be left with `%foo%` (the literal string). + '%' => { + // the trailing `%` is appended outside the switch + try buf.appendSlice("%%cd:~,"); + backslashes = 0; + }, + else => { + backslashes = 0; + }, + } + try buf.append(c); + } + if (needs_quotes) { + try buf.appendNTimes('\\', backslashes); + try buf.append('"'); + } + } + + try buf.append('"'); + + return try std.unicode.wtf8ToWtf16LeAllocZ(allocator, buf.items); +} + +const ArgvToCommandLineError = error{ OutOfMemory, InvalidWtf8, InvalidArg0 }; + +/// Serializes `argv` to a Windows command-line string suitable for passing to a child process and +/// parsing by the `CommandLineToArgvW` algorithm. The caller owns the returned slice. +/// +/// To avoid arbitrary command execution, this function should not be used when spawning `.bat`/`.cmd` scripts. +/// https://flatt.tech/research/posts/batbadbut-you-cant-securely-execute-commands-on-windows/ +/// +/// When executing `.bat`/`.cmd` scripts, use `argvToScriptCommandLineWindows` instead. +fn argvToCommandLineWindows( + allocator: Allocator, + argv: []const []const u8, +) ArgvToCommandLineError![:0]u16 { + var buf = std.array_list.Managed(u8).init(allocator); + defer buf.deinit(); + + if (argv.len != 0) { + const arg0 = argv[0]; + + // The first argument must be quoted if it contains spaces or ASCII control characters + // (excluding DEL). It also follows special quoting rules where backslashes have no special + // interpretation, which makes it impossible to pass certain first arguments containing + // double quotes to a child process without characters from the first argument leaking into + // subsequent ones (which could have security implications). + // + // Empty arguments technically don't need quotes, but we quote them anyway for maximum + // compatibility with different implementations of the 'CommandLineToArgvW' algorithm. + // + // Double quotes are illegal in paths on Windows, so for the sake of simplicity we reject + // all first arguments containing double quotes, even ones that we could theoretically + // serialize in unquoted form. + var needs_quotes = arg0.len == 0; + for (arg0) |c| { + if (c <= ' ') { + needs_quotes = true; + } else if (c == '"') { + return error.InvalidArg0; + } + } + if (needs_quotes) { + try buf.append('"'); + try buf.appendSlice(arg0); + try buf.append('"'); + } else { + try buf.appendSlice(arg0); + } + + for (argv[1..]) |arg| { + try buf.append(' '); + + // Subsequent arguments must be quoted if they contain spaces, tabs or double quotes, + // or if they are empty. For simplicity and for maximum compatibility with different + // implementations of the 'CommandLineToArgvW' algorithm, we also quote all ASCII + // control characters (again, excluding DEL). + needs_quotes = for (arg) |c| { + if (c <= ' ' or c == '"') { + break true; + } + } else arg.len == 0; + if (!needs_quotes) { + try buf.appendSlice(arg); + continue; + } + + try buf.append('"'); + var backslash_count: usize = 0; + for (arg) |byte| { + switch (byte) { + '\\' => { + backslash_count += 1; + }, + '"' => { + try buf.appendNTimes('\\', backslash_count * 2 + 1); + try buf.append('"'); + backslash_count = 0; + }, + else => { + try buf.appendNTimes('\\', backslash_count); + try buf.append(byte); + backslash_count = 0; + }, + } + } + try buf.appendNTimes('\\', backslash_count * 2); + try buf.append('"'); + } + } + + return try std.unicode.wtf8ToWtf16LeAllocZ(allocator, buf.items); +} + +test argvToCommandLineWindows { + const t = testArgvToCommandLineWindows; + + try t(&.{ + \\C:\Program Files\zig\zig.exe + , + \\run + , + \\.\src\main.zig + , + \\-target + , + \\x86_64-windows-gnu + , + \\-O + , + \\ReleaseSafe + , + \\-- + , + \\--emoji=🗿 + , + \\--eval=new Regex("Dwayne \"The Rock\" Johnson") + , + }, + \\"C:\Program Files\zig\zig.exe" run .\src\main.zig -target x86_64-windows-gnu -O ReleaseSafe -- --emoji=🗿 "--eval=new Regex(\"Dwayne \\\"The Rock\\\" Johnson\")" + ); + + try t(&.{}, ""); + try t(&.{""}, "\"\""); + try t(&.{" "}, "\" \""); + try t(&.{"\t"}, "\"\t\""); + try t(&.{"\x07"}, "\"\x07\""); + try t(&.{"🦎"}, "🦎"); + + try t( + &.{ "zig", "aa aa", "bb\tbb", "cc\ncc", "dd\r\ndd", "ee\x7Fee" }, + "zig \"aa aa\" \"bb\tbb\" \"cc\ncc\" \"dd\r\ndd\" ee\x7Fee", + ); + + try t( + &.{ "\\\\foo bar\\foo bar\\", "\\\\zig zag\\zig zag\\" }, + "\"\\\\foo bar\\foo bar\\\" \"\\\\zig zag\\zig zag\\\\\"", + ); + + try std.testing.expectError( + error.InvalidArg0, + argvToCommandLineWindows(std.testing.allocator, &.{"\"quotes\"quotes\""}), + ); + try std.testing.expectError( + error.InvalidArg0, + argvToCommandLineWindows(std.testing.allocator, &.{"quotes\"quotes"}), + ); + try std.testing.expectError( + error.InvalidArg0, + argvToCommandLineWindows(std.testing.allocator, &.{"q u o t e s \" q u o t e s"}), + ); +} + +fn testArgvToCommandLineWindows(argv: []const []const u8, expected_cmd_line: []const u8) !void { + const cmd_line_w = try argvToCommandLineWindows(std.testing.allocator, argv); + defer std.testing.allocator.free(cmd_line_w); + + const cmd_line = try std.unicode.wtf16LeToWtf8Alloc(std.testing.allocator, cmd_line_w); + defer std.testing.allocator.free(cmd_line); + + try std.testing.expectEqualStrings(expected_cmd_line, cmd_line); +} + +/// Replaces the current process image with the executed process. If this +/// function succeeds, it does not return. +/// +/// This operation is not available on all targets. `can_execv` +/// +/// This function also uses the PATH environment variable to get the full path to the executable. +/// If `file` is an absolute path, this is the same as `execveZ`. +/// +/// Like `execvpeZ` except if `arg0_expand` is `.expand`, then `argv` is mutable, +/// and `argv[0]` is expanded to be the same absolute path that is passed to the execve syscall. +/// If this function returns with an error, `argv[0]` will be restored to the value it was when it was passed in. +fn execvpeZ_expandArg0( + arg0_expand: process.ArgExpansion, + file: [*:0]const u8, + child_argv: [*:null]?[*:0]const u8, + envp: [*:null]const ?[*:0]const u8, + PATH: []const u8, +) process.ReplaceError { + const file_slice = std.mem.sliceTo(file, 0); + if (std.mem.findScalar(u8, file_slice, '/') != null) return execveZ(file, child_argv, envp); + + // Use of PATH_MAX here is valid as the path_buf will be passed + // directly to the operating system in execveZ. + var path_buf: [posix.PATH_MAX]u8 = undefined; + var it = std.mem.tokenizeScalar(u8, PATH, ':'); + var seen_eacces = false; + var err: process.ReplaceError = error.FileNotFound; + + // In case of expanding arg0 we must put it back if we return with an error. + const prev_arg0 = child_argv[0]; + defer switch (arg0_expand) { + .expand => child_argv[0] = prev_arg0, + .no_expand => {}, + }; + + while (it.next()) |search_path| { + const path_len = search_path.len + file_slice.len + 1; + if (path_buf.len < path_len + 1) return error.NameTooLong; + @memcpy(path_buf[0..search_path.len], search_path); + path_buf[search_path.len] = '/'; + @memcpy(path_buf[search_path.len + 1 ..][0..file_slice.len], file_slice); + path_buf[path_len] = 0; + const full_path = path_buf[0..path_len :0].ptr; + switch (arg0_expand) { + .expand => child_argv[0] = full_path, + .no_expand => {}, + } + err = execveZ(full_path, child_argv, envp); + switch (err) { + error.AccessDenied => seen_eacces = true, + error.FileNotFound, error.NotDir => {}, + else => |e| return e, + } + } + if (seen_eacces) return error.AccessDenied; + return err; +} + +/// This function ignores PATH environment variable. See `execvpeZ` for that. +pub fn execveZ( + path: [*:0]const u8, + child_argv: [*:null]const ?[*:0]const u8, + envp: [*:null]const ?[*:0]const u8, +) process.ReplaceError { + switch (posix.errno(posix.system.execve(path, child_argv, envp))) { + .SUCCESS => unreachable, + .FAULT => |err| return errnoBug(err), // Bad pointer parameter. + .@"2BIG" => return error.SystemResources, + .MFILE => return error.ProcessFdQuotaExceeded, + .NAMETOOLONG => return error.NameTooLong, + .NFILE => return error.SystemFdQuotaExceeded, + .NOMEM => return error.SystemResources, + .ACCES => return error.AccessDenied, + .PERM => return error.PermissionDenied, + .INVAL => return error.InvalidExe, + .NOEXEC => return error.InvalidExe, + .IO => return error.FileSystem, + .LOOP => return error.FileSystem, + .ISDIR => return error.IsDir, + .NOENT => return error.FileNotFound, + .NOTDIR => return error.NotDir, + .TXTBSY => return error.FileBusy, + else => |err| switch (native_os) { + .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => switch (err) { + .BADEXEC => return error.InvalidExe, + .BADARCH => return error.InvalidExe, + else => return posix.unexpectedErrno(err), + }, + .linux => switch (err) { + .LIBBAD => return error.InvalidExe, + else => return posix.unexpectedErrno(err), + }, + else => return posix.unexpectedErrno(err), + }, + } +} + +/// This function also uses the PATH environment variable to get the full path to the executable. +/// If `file` is an absolute path, this is the same as `execveZ`. +pub fn execvpeZ( + file: [*:0]const u8, + argv_ptr: [*:null]const ?[*:0]const u8, + envp: [*:null]const ?[*:0]const u8, + optional_PATH: ?[]const u8, +) process.ReplaceError { + return execvpeZ_expandArg0(.no_expand, file, argv_ptr, envp, optional_PATH); +} + +fn windowsMakePipeIn(rd: *?windows.HANDLE, wr: *?windows.HANDLE, sattr: *const windows.SECURITY_ATTRIBUTES) !void { + var rd_h: windows.HANDLE = undefined; + var wr_h: windows.HANDLE = undefined; + try windows.CreatePipe(&rd_h, &wr_h, sattr); + errdefer windowsDestroyPipe(rd_h, wr_h); + try windows.SetHandleInformation(wr_h, windows.HANDLE_FLAG_INHERIT, 0); + rd.* = rd_h; + wr.* = wr_h; +} + +fn windowsDestroyPipe(rd: ?windows.HANDLE, wr: ?windows.HANDLE) void { + if (rd) |h| posix.close(h); + if (wr) |h| posix.close(h); +} + +fn windowsMakeAsyncPipe(rd: *?windows.HANDLE, wr: *?windows.HANDLE, sattr: *const windows.SECURITY_ATTRIBUTES) !void { + var tmp_bufw: [128]u16 = undefined; + + // Anonymous pipes are built upon Named pipes. + // https://docs.microsoft.com/en-us/windows/win32/api/namedpipeapi/nf-namedpipeapi-createpipe + // Asynchronous (overlapped) read and write operations are not supported by anonymous pipes. + // https://docs.microsoft.com/en-us/windows/win32/ipc/anonymous-pipe-operations + const pipe_path = blk: { + var tmp_buf: [128]u8 = undefined; + // Forge a random path for the pipe. + const pipe_path = std.fmt.bufPrintSentinel( + &tmp_buf, + "\\\\.\\pipe\\zig-childprocess-{d}-{d}", + .{ windows.GetCurrentProcessId(), pipe_name_counter.fetchAdd(1, .monotonic) }, + 0, + ) catch unreachable; + const len = std.unicode.wtf8ToWtf16Le(&tmp_bufw, pipe_path) catch unreachable; + tmp_bufw[len] = 0; + break :blk tmp_bufw[0..len :0]; + }; + + // Create the read handle that can be used with overlapped IO ops. + const read_handle = windows.kernel32.CreateNamedPipeW( + pipe_path.ptr, + windows.PIPE_ACCESS_INBOUND | windows.FILE_FLAG_OVERLAPPED, + windows.PIPE_TYPE_BYTE, + 1, + 4096, + 4096, + 0, + sattr, + ); + if (read_handle == windows.INVALID_HANDLE_VALUE) { + switch (windows.GetLastError()) { + else => |err| return windows.unexpectedError(err), + } + } + errdefer posix.close(read_handle); + + var sattr_copy = sattr.*; + const write_handle = windows.kernel32.CreateFileW( + pipe_path.ptr, + .{ .GENERIC = .{ .WRITE = true } }, + 0, + &sattr_copy, + windows.OPEN_EXISTING, + @bitCast(windows.FILE.ATTRIBUTE{ .NORMAL = true }), + null, + ); + if (write_handle == windows.INVALID_HANDLE_VALUE) { + switch (windows.GetLastError()) { + else => |err| return windows.unexpectedError(err), + } + } + errdefer posix.close(write_handle); + + try windows.SetHandleInformation(read_handle, windows.HANDLE_FLAG_INHERIT, 0); + + rd.* = read_handle; + wr.* = write_handle; +} + +var pipe_name_counter = std.atomic.Value(u32).init(1); + test { _ = @import("Threaded/test.zig"); } diff --git a/lib/std/c.zig b/lib/std/c.zig index 9bf4bef5d240a311ffd301aa32ece126d271e12e..41c1cd52c0cd03a45586be2bc58e1a29d39b38ff 100644 --- a/lib/std/c.zig +++ b/lib/std/c.zig @@ -3764,8 +3764,8 @@ pub const W = switch (native_os) { pub fn EXITSTATUS(x: u32) u8 { return @as(u8, @intCast(x >> 8)); } - pub fn TERMSIG(x: u32) u32 { - return status(x); + pub fn TERMSIG(x: u32) SIG { + return @enumFromInt(status(x)); } pub fn STOPSIG(x: u32) u32 { return x >> 8; @@ -3797,14 +3797,14 @@ pub const W = switch (native_os) { pub fn EXITSTATUS(s: u32) u8 { return @as(u8, @intCast((s & 0xff00) >> 8)); } - pub fn TERMSIG(s: u32) u32 { - return s & 0x7f; + pub fn TERMSIG(s: u32) SIG { + return @enumFromInt(s & 0x7f); } pub fn STOPSIG(s: u32) u32 { return EXITSTATUS(s); } pub fn IFEXITED(s: u32) bool { - return TERMSIG(s) == 0; + return (s & 0x7f) == 0; } pub fn IFSTOPPED(s: u32) bool { return @as(u16, @truncate((((s & 0xffff) *% 0x10001) >> 8))) > 0x7f00; @@ -3825,14 +3825,14 @@ pub const W = switch (native_os) { pub fn EXITSTATUS(s: u32) u8 { return @as(u8, @intCast((s >> 8) & 0xff)); } - pub fn TERMSIG(s: u32) u32 { - return s & 0x7f; + pub fn TERMSIG(s: u32) SIG { + return @enumFromInt(s & 0x7f); } pub fn STOPSIG(s: u32) u32 { return EXITSTATUS(s); } pub fn IFEXITED(s: u32) bool { - return TERMSIG(s) == 0; + return (s & 0x7f) == 0; } pub fn IFCONTINUED(s: u32) bool { @@ -3859,14 +3859,14 @@ pub const W = switch (native_os) { pub fn EXITSTATUS(s: u32) u8 { return @as(u8, @intCast((s >> 8) & 0xff)); } - pub fn TERMSIG(s: u32) u32 { - return s & 0x7f; + pub fn TERMSIG(s: u32) SIG { + return @enumFromInt(s & 0x7f); } pub fn STOPSIG(s: u32) u32 { return EXITSTATUS(s); } pub fn IFEXITED(s: u32) bool { - return TERMSIG(s) == 0; + return (s & 0x7f) == 0; } pub fn IFCONTINUED(s: u32) bool { @@ -3893,14 +3893,14 @@ pub const W = switch (native_os) { pub fn EXITSTATUS(s: u32) u8 { return @as(u8, @intCast((s & 0xff00) >> 8)); } - pub fn TERMSIG(s: u32) u32 { - return s & 0x7f; + pub fn TERMSIG(s: u32) SIG { + return @enumFromInt(s & 0x7f); } pub fn STOPSIG(s: u32) u32 { return EXITSTATUS(s); } pub fn IFEXITED(s: u32) bool { - return TERMSIG(s) == 0; + return (s & 0x7f) == 0; } pub fn IFSTOPPED(s: u32) bool { return @as(u16, @truncate((((s & 0xffff) *% 0x10001) >> 8))) > 0x7f00; @@ -3921,8 +3921,8 @@ pub const W = switch (native_os) { return @as(u8, @intCast(s & 0xff)); } - pub fn TERMSIG(s: u32) u32 { - return (s >> 8) & 0xff; + pub fn TERMSIG(s: u32) SIG { + return @enumFromInt((s >> 8) & 0xff); } pub fn STOPSIG(s: u32) u32 { @@ -3949,14 +3949,14 @@ pub const W = switch (native_os) { pub fn EXITSTATUS(s: u32) u8 { return @as(u8, @intCast((s >> 8) & 0xff)); } - pub fn TERMSIG(s: u32) u32 { - return (s & 0x7f); + pub fn TERMSIG(s: u32) SIG { + return @enumFromInt(s & 0x7f); } pub fn STOPSIG(s: u32) u32 { return EXITSTATUS(s); } pub fn IFEXITED(s: u32) bool { - return TERMSIG(s) == 0; + return (s & 0x7f) == 0; } pub fn IFCONTINUED(s: u32) bool { @@ -3988,12 +3988,12 @@ pub const W = switch (native_os) { return EXITSTATUS(s); } - pub fn TERMSIG(s: u32) u32 { - return s & 0x7f; + pub fn TERMSIG(s: u32) SIG { + return @enumFromInt(s & 0x7f); } pub fn IFEXITED(s: u32) bool { - return TERMSIG(s) == 0; + return (s & 0x7f) == 0; } pub fn IFSTOPPED(s: u32) bool { diff --git a/lib/std/os/emscripten.zig b/lib/std/os/emscripten.zig index cb444b360dd00e0375a6e02e0d95936fce90b2b5..a7fb141ed41b8353e72dd3032fd6486ce9e22ead 100644 --- a/lib/std/os/emscripten.zig +++ b/lib/std/os/emscripten.zig @@ -224,14 +224,14 @@ pub const W = struct { pub fn EXITSTATUS(s: u32) u8 { return @as(u8, @intCast((s & 0xff00) >> 8)); } - pub fn TERMSIG(s: u32) u32 { - return s & 0x7f; + pub fn TERMSIG(s: u32) SIG { + return @enumFromInt(s & 0x7f); } pub fn STOPSIG(s: u32) u32 { return EXITSTATUS(s); } pub fn IFEXITED(s: u32) bool { - return TERMSIG(s) == 0; + return (s & 0x7f) == 0; } pub fn IFSTOPPED(s: u32) bool { return @as(u16, @truncate(((s & 0xffff) *% 0x10001) >> 8)) > 0x7f00; diff --git a/lib/std/os/linux.zig b/lib/std/os/linux.zig index 174ef64d95f8f2e79c27aebba1055ac4457e74a0..68baa9c626e638fbe4c214a1bfaf3b700e2a259e 100644 --- a/lib/std/os/linux.zig +++ b/lib/std/os/linux.zig @@ -3616,14 +3616,14 @@ pub const W = struct { pub fn EXITSTATUS(s: u32) u8 { return @as(u8, @intCast((s & 0xff00) >> 8)); } - pub fn TERMSIG(s: u32) u32 { - return s & 0x7f; + pub fn TERMSIG(s: u32) SIG { + return @enumFromInt(s & 0x7f); } pub fn STOPSIG(s: u32) u32 { return EXITSTATUS(s); } pub fn IFEXITED(s: u32) bool { - return TERMSIG(s) == 0; + return (s & 0x7f) == 0; } pub fn IFSTOPPED(s: u32) bool { return @as(u16, @truncate(((s & 0xffff) *% 0x10001) >> 8)) > 0x7f00; diff --git a/lib/std/posix.zig b/lib/std/posix.zig index 55b19df995e1db0930690c9be9767cebc17db5e6..72ddd276763ae3d81ccf0d10ce0542465d831a49 100644 --- a/lib/std/posix.zig +++ b/lib/std/posix.zig @@ -795,79 +795,10 @@ pub fn dup2(old_fd: fd_t, new_fd: fd_t) !void { } } -pub fn getpid() pid_t { - return system.getpid(); -} - pub fn getppid() pid_t { return system.getppid(); } -pub const ExecveError = error{ - SystemResources, - AccessDenied, - PermissionDenied, - InvalidExe, - FileSystem, - IsDir, - FileNotFound, - NotDir, - FileBusy, - ProcessFdQuotaExceeded, - SystemFdQuotaExceeded, - NameTooLong, -} || UnexpectedError; - -/// This function ignores PATH environment variable. See `execvpeZ` for that. -pub fn execveZ( - path: [*:0]const u8, - child_argv: [*:null]const ?[*:0]const u8, - envp: [*:null]const ?[*:0]const u8, -) ExecveError { - switch (errno(system.execve(path, child_argv, envp))) { - .SUCCESS => unreachable, - .FAULT => unreachable, - .@"2BIG" => return error.SystemResources, - .MFILE => return error.ProcessFdQuotaExceeded, - .NAMETOOLONG => return error.NameTooLong, - .NFILE => return error.SystemFdQuotaExceeded, - .NOMEM => return error.SystemResources, - .ACCES => return error.AccessDenied, - .PERM => return error.PermissionDenied, - .INVAL => return error.InvalidExe, - .NOEXEC => return error.InvalidExe, - .IO => return error.FileSystem, - .LOOP => return error.FileSystem, - .ISDIR => return error.IsDir, - .NOENT => return error.FileNotFound, - .NOTDIR => return error.NotDir, - .TXTBSY => return error.FileBusy, - else => |err| switch (native_os) { - .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => switch (err) { - .BADEXEC => return error.InvalidExe, - .BADARCH => return error.InvalidExe, - else => return unexpectedErrno(err), - }, - .linux => switch (err) { - .LIBBAD => return error.InvalidExe, - else => return unexpectedErrno(err), - }, - else => return unexpectedErrno(err), - }, - } -} - -/// This function also uses the PATH environment variable to get the full path to the executable. -/// If `file` is an absolute path, this is the same as `execveZ`. -pub fn execvpeZ( - file: [*:0]const u8, - argv_ptr: [*:null]const ?[*:0]const u8, - envp: [*:null]const ?[*:0]const u8, - optional_PATH: ?[]const u8, -) ExecveError { - return execvpeZ_expandArg0(.no_expand, file, argv_ptr, envp, optional_PATH); -} - pub const GetCwdError = error{ NameTooLong, CurrentWorkingDirectoryUnlinked, @@ -1119,16 +1050,6 @@ pub fn seteuid(uid: uid_t) SetEidError!void { } } -pub fn setreuid(ruid: uid_t, euid: uid_t) SetIdError!void { - switch (errno(system.setreuid(ruid, euid))) { - .SUCCESS => return, - .AGAIN => return error.ResourceLimitReached, - .INVAL => return error.InvalidUserId, - .PERM => return error.PermissionDenied, - else => |err| return unexpectedErrno(err), - } -} - pub fn setgid(gid: gid_t) SetIdError!void { switch (errno(system.setgid(gid))) { .SUCCESS => return, @@ -1158,24 +1079,6 @@ pub fn setregid(rgid: gid_t, egid: gid_t) SetIdError!void { } } -pub const SetPgidError = error{ - ProcessAlreadyExec, - InvalidProcessGroupId, - PermissionDenied, - ProcessNotFound, -} || UnexpectedError; - -pub fn setpgid(pid: pid_t, pgid: pid_t) SetPgidError!void { - switch (errno(system.setpgid(pid, pgid))) { - .SUCCESS => return, - .ACCES => return error.ProcessAlreadyExec, - .INVAL => return error.InvalidProcessGroupId, - .PERM => return error.PermissionDenied, - .SRCH => return error.ProcessNotFound, - else => |err| return unexpectedErrno(err), - } -} - pub fn getuid() uid_t { return system.getuid(); } diff --git a/lib/std/process.zig b/lib/std/process.zig index 4221356bddbc8a9d419966a374873721a9f6a36b..1fd31d263494db052a90b45ad82ac0dce561fb57 100644 --- a/lib/std/process.zig +++ b/lib/std/process.zig @@ -259,20 +259,44 @@ pub fn getBaseAddress() usize { } } -/// Deprecated in favor of `Child.can_spawn`. -pub const can_spawn = Child.can_spawn; -/// Deprecated in favor of `can_replace`. -pub const can_execv = can_replace; - /// Tells whether the target operating system supports replacing the current -/// process image. If this is `false` then calling `execv` or `replace` -/// functions will cause compilation to fail. +/// process image. If this is `false` then calling `replace` or `replaceFile` +/// functions will return `error.OperationUnsupported`. pub const can_replace = switch (native_os) { .windows, .haiku, .wasi => false, else => true, }; -pub const ReplaceError = std.posix.ExecveError || error{OutOfMemory}; +/// Tells whether spawning child processes is supported. +pub const can_spawn = switch (native_os) { + .wasi, .ios, .tvos, .visionos, .watchos => false, + else => true, +}; + +pub const ReplaceError = error{ + /// The target operating system cannot replace the process image with a new + /// one. + OperationUnsupported, + SystemResources, + AccessDenied, + PermissionDenied, + InvalidExe, + FileSystem, + IsDir, + FileNotFound, + NotDir, + FileBusy, + ProcessFdQuotaExceeded, + SystemFdQuotaExceeded, +} || Io.Dir.PathNameError || Io.Cancelable || Io.UnexpectedError; + +pub const ReplaceOptions = struct { + argv: []const []const u8, + arg0_expand: ArgExpansion = .no_expand, + /// Replaces the environment when provided. The PATH value from here is + /// never used to resolve `argv[0]`. + env_map: ?*const Environ.Map = null, +}; /// Replaces the current process image with the executed process. If this /// function succeeds, it does not return. @@ -281,25 +305,9 @@ pub const ReplaceError = std.posix.ExecveError || error{OutOfMemory}; /// is not already a file path (i.e. it contains '/'), it is resolved into a /// file path based on PATH from the parent environment. /// -/// This operation is not available on targets for which `can_replace` is -/// `false`. -/// -/// This function must allocate memory to add a null terminating bytes on path -/// and each arg. -/// -/// Due to the heap allocation, it is illegal to call this function in a fork() -/// child. -pub fn replace(io: Io, gpa: Allocator, argv: []const []const u8, env: Environ.Block) ReplaceError { - if (!can_replace) @compileError("unsupported operation: replace"); - - var arena_allocator = std.heap.ArenaAllocator.init(gpa); - defer arena_allocator.deinit(); - const arena = arena_allocator.allocator(); - - const argv_buf = try arena.allocSentinel(?[*:0]const u8, argv.len, null); - for (argv, 0..) |arg, i| argv_buf[i] = (try arena.dupeZ(u8, arg)).ptr; - - return posix.execvpeZ_expandArg0(.no_expand, argv_buf.ptr[0].?, argv_buf.ptr, env); +/// It is illegal to call this function in a fork() child. +pub fn replace(io: Io, options: ReplaceOptions) ReplaceError { + return io.vtable.processReplace(io.userdata, options); } /// Replaces the current process image with the executed process. If this @@ -309,92 +317,181 @@ pub fn replace(io: Io, gpa: Allocator, argv: []const []const u8, env: Environ.Bl /// relative to `dir`. It is *always* treated as a file path, even if it does /// not contain '/'. /// -/// This operation is not available on targets for which `can_replace` is -/// `false`. -/// -/// This function must allocate memory to add a null terminating bytes on path -/// and each arg. -/// -/// Due to the heap allocation, it is illegal to call this -/// function in a fork() child. For that use case, use the `std.posix` -/// functions directly. -pub fn replaceFile(io: Io, gpa: Allocator, argv: []const []const u8, env: Environ.Block) ReplaceError { - if (!can_replace) @compileError("unsupported operation: replaceFile"); +/// It is illegal to call this function in a fork() child. +pub fn replacePath(io: Io, dir: Io.Dir, options: ReplaceOptions) ReplaceError { + return io.vtable.processReplacePath(io.userdata, dir, options); +} - var arena_allocator = std.heap.ArenaAllocator.init(gpa); - defer arena_allocator.deinit(); - const arena = arena_allocator.allocator(); +pub const ArgExpansion = enum { expand, no_expand }; - const argv_buf = try arena.allocSentinel(?[*:0]const u8, argv.len, null); - for (argv, 0..) |arg, i| argv_buf[i] = (try arena.dupeZ(u8, arg)).ptr; +/// File name extensions supported natively by `CreateProcess()` on Windows. +pub const WindowsExtension = enum { bat, cmd, com, exe }; - return posix.execvpeZ_expandArg0(.no_expand, argv_buf.ptr[0].?, argv_buf.ptr, env); -} +pub const SpawnError = error{ + OutOfMemory, + /// POSIX-only. `StdIo.ignore` was selected and opening `/dev/null` returned ENODEV. + NoDevice, + /// Windows-only. `cwd` or `argv` was provided and it was invalid WTF-8. + /// https://wtf-8.codeberg.page/ + InvalidWtf8, + /// Windows-only. `cwd` was provided, but the path did not exist when spawning the child process. + CurrentWorkingDirectoryUnlinked, + /// Windows-only. NUL (U+0000), LF (U+000A), CR (U+000D) are not allowed + /// within arguments when executing a `.bat`/`.cmd` script. + /// - NUL/LF signifiies end of arguments, so anything afterwards + /// would be lost after execution. + /// - CR is stripped by `cmd.exe`, so any CR codepoints + /// would be lost after execution. + InvalidBatchScriptArg, + SystemResources, + AccessDenied, + PermissionDenied, + InvalidExe, + FileSystem, + IsDir, + FileNotFound, + NotDir, + FileBusy, + ProcessFdQuotaExceeded, + SystemFdQuotaExceeded, + ResourceLimitReached, + InvalidUserId, + InvalidProcessGroupId, + SymLinkLoop, + InvalidName, + /// An attempt was made to change the process group ID of one of the + /// children of the calling process and the child had already performed an + /// image replacement. + ProcessAlreadyExec, +} || Io.Dir.PathNameError || Io.Cancelable || Io.UnexpectedError; -pub const Arg0Expand = enum { expand, no_expand }; +pub const SpawnOptions = struct { + argv: []const []const u8, -/// Replaces the current process image with the executed process. If this -/// function succeeds, it does not return. -/// -/// This operation is not available on all targets. `can_execv` -/// -/// This function also uses the PATH environment variable to get the full path to the executable. -/// If `file` is an absolute path, this is the same as `execveZ`. -/// -/// Like `execvpeZ` except if `arg0_expand` is `.expand`, then `argv` is mutable, -/// and `argv[0]` is expanded to be the same absolute path that is passed to the execve syscall. -/// If this function returns with an error, `argv[0]` will be restored to the value it was when it was passed in. -pub fn replace( - comptime arg0_expand: Arg0Expand, - file: [*:0]const u8, - child_argv: switch (arg0_expand) { - .expand => [*:null]?[*:0]const u8, - .no_expand => [*:null]const ?[*:0]const u8, - }, - envp: [*:null]const ?[*:0]const u8, - optional_PATH: ?[]const u8, -) ExecveError { - const file_slice = mem.sliceTo(file, 0); - if (mem.findScalar(u8, file_slice, '/') != null) return execveZ(file, child_argv, envp); - - const PATH = optional_PATH orelse "/usr/local/bin:/bin/:/usr/bin"; - // Use of PATH_MAX here is valid as the path_buf will be passed - // directly to the operating system in execveZ. - var path_buf: [PATH_MAX]u8 = undefined; - var it = mem.tokenizeScalar(u8, PATH, ':'); - var seen_eacces = false; - var err: ExecveError = error.FileNotFound; - - // In case of expanding arg0 we must put it back if we return with an error. - const prev_arg0 = child_argv[0]; - defer switch (arg0_expand) { - .expand => child_argv[0] = prev_arg0, - .no_expand => {}, + /// Set to change the current working directory when spawning the child process. + cwd: ?[]const u8 = null, + /// Set to change the current working directory when spawning the child process. + /// This is not yet implemented for Windows. See https://github.com/ziglang/zig/issues/5190 + /// Once that is done, `cwd` will be deprecated in favor of this field. + cwd_dir: ?Io.Dir = null, + /// Replaces the child environment when provided. The PATH value from here + /// is not used to resolve `argv[0]`; that resolution always uses parent + /// environment. + env_map: ?*const Environ.Map = null, + expand_arg0: ArgExpansion = .no_expand, + /// When populated, a pipe will be created for the child process to + /// communicate progress back to the parent. The file descriptor of the + /// write end of the pipe will be specified in the `ZIG_PROGRESS` + /// environment variable inside the child process. The progress reported by + /// the child will be attached to this progress node in the parent process. + /// + /// The child's progress tree will be grafted into the parent's progress tree, + /// by substituting this node with the child's root node. + progress_node: std.Progress.Node = std.Progress.Node.none, + + stdin: StdIo = .inherit, + stdout: StdIo = .inherit, + stderr: StdIo = .inherit, + + /// Set to true to obtain rusage information for the child process. + /// Depending on the target platform and implementation status, the + /// requested statistics may or may not be available. If they are + /// available, then the `resource_usage_statistics` field will be populated + /// after calling `wait`. + /// On Linux and Darwin, this obtains rusage statistics from wait4(). + request_resource_usage_statistics: bool = false, + + /// Set to change the user id when spawning the child process. + uid: ?posix.uid_t = null, + /// Set to change the group id when spawning the child process. + gid: ?posix.gid_t = null, + /// Set to change the process group id when spawning the child process. + pgid: ?posix.pid_t = null, + + /// Start child process in suspended state. + /// For Posix systems it's started as if SIGSTOP was sent. + start_suspended: bool = false, + /// Windows-only. Sets the CREATE_NO_WINDOW flag in CreateProcess. + create_no_window: bool = false, + /// Darwin-only. Disable ASLR for the child process. + disable_aslr: bool = false, + + /// Behavior of the child process's standard input, output, and error streams. + pub const StdIo = union(enum) { + /// Inherit the corresponding stream from the parent process. + inherit, + /// Pass an already open file from the parent to the child. + file: File, + /// Pass a null stream to the child process by opening "/dev/null" on POSIX + /// and "NUL" on Windows. + ignore, + /// Create a new pipe for the stream. + /// + /// The corresponding field (`stdout`, `stderr`, or `stdin`) will be + /// assigned a `File` object that can be used to read from or write to the + /// pipe. + pipe, + /// Spawn the child process with the corresponding stream missing. This + /// will likely result in the child encountering EBADF if it tries to use + /// stdin, stdout, or stderr, or if only one stream is closed, it will + /// result in them getting mixed up. Generally, this option is for advanced + /// use cases only. + close, }; +}; - while (it.next()) |search_path| { - const path_len = search_path.len + file_slice.len + 1; - if (path_buf.len < path_len + 1) return error.NameTooLong; - @memcpy(path_buf[0..search_path.len], search_path); - path_buf[search_path.len] = '/'; - @memcpy(path_buf[search_path.len + 1 ..][0..file_slice.len], file_slice); - path_buf[path_len] = 0; - const full_path = path_buf[0..path_len :0].ptr; - switch (arg0_expand) { - .expand => child_argv[0] = full_path, - .no_expand => {}, - } - err = execveZ(full_path, child_argv, envp); - switch (err) { - error.AccessDenied => seen_eacces = true, - error.FileNotFound, error.NotDir => {}, - else => |e| return e, - } - } - if (seen_eacces) return error.AccessDenied; - return err; +/// Creates a child process. +/// +/// `argv[0]` is the name of the program to execute. If it is not already a +/// file path (i.e. it contains '/'), it is resolved into a file path based on +/// PATH from the parent environment. +pub fn spawn(io: Io, options: SpawnOptions) SpawnError!Child { + return io.vtable.processSpawn(io.userdata, options); +} + +/// Creates a child process. +/// +/// `argv[0]` is the file path of the program to execute, relative to `dir`. It +/// is *always* treated as a file path, even if it does not contain '/'. +pub fn spawnPath(io: Io, dir: Io.Dir, options: SpawnOptions) SpawnError!Child { + return io.vtable.processSpawnPath(io.userdata, dir, options); } +pub const RunError = posix.GetCwdError || posix.ReadError || SpawnError || posix.PollError || error{ + StdoutStreamTooLong, + StderrStreamTooLong, +}; + +pub const RunOptions = struct { + spawn_options: SpawnOptions, + max_output_bytes: usize = 50 * 1024, +}; + +pub const RunResult = struct { + term: Child.Term, + stdout: []u8, + stderr: []u8, +}; + +/// Spawns a child process, waits for it, collecting stdout and stderr, and then returns. +/// If it succeeds, the caller owns result.stdout and result.stderr memory. +pub fn run(gpa: Allocator, io: Io, options: RunOptions) RunError!RunResult { + var child = try spawn(io, options.spawn_options); + defer child.kill(io); + + var stdout: std.ArrayList(u8) = .empty; + defer stdout.deinit(gpa); + var stderr: std.ArrayList(u8) = .empty; + defer stderr.deinit(gpa); + + try child.collectOutput(gpa, &stdout, &stderr, options.max_output_bytes); + + return .{ + .stdout = try stdout.toOwnedSlice(gpa), + .stderr = try stderr.toOwnedSlice(gpa), + .term = try child.wait(io), + }; +} pub const TotalSystemMemoryError = error{ UnknownTotalSystemMemory, diff --git a/lib/std/process/Child.zig b/lib/std/process/Child.zig index f2e096bca27b970454ead0ed3f8dd518493ef1d1..6a170116ce74496706df13eb8c219a08f75a2a83 100644 --- a/lib/std/process/Child.zig +++ b/lib/std/process/Child.zig @@ -5,120 +5,38 @@ const native_os = builtin.os.tag; const std = @import("../std.zig"); const Io = std.Io; -const unicode = std.unicode; -const fs = std.fs; const process = std.process; const File = std.Io.File; -const windows = std.os.windows; -const linux = std.os.linux; -const posix = std.posix; -const mem = std.mem; -const maxInt = std.math.maxInt; const assert = std.debug.assert; const Allocator = std.mem.Allocator; const ArrayList = std.ArrayList; -/// Tells whether spawning child processes is supported. -pub const can_spawn = switch (native_os) { - .wasi, .ios, .tvos, .visionos, .watchos => false, - else => true, -}; - pub const Id = switch (native_os) { - .windows => windows.HANDLE, + .windows => std.os.windows.HANDLE, .wasi => void, - else => posix.pid_t, + else => std.posix.pid_t, }; -/// Available after calling `spawn()`. This becomes `undefined` after calling `wait()`. +/// After `wait` or `kill` is called, this becomes `null`. /// On Windows this is the hProcess. /// On POSIX this is the pid. -id: Id, -thread_handle: if (native_os == .windows) windows.HANDLE else void, - -allocator: Allocator, - +id: ?Id, +thread_handle: if (native_os == .windows) std.os.windows.HANDLE else void = {}, /// The writing end of the child process's standard input pipe. -/// Usage requires `stdin_behavior == StdIo.Pipe`. -/// Available after calling `spawn()`. +/// Usage requires `process.SpawnOptions.StdIo.pipe`. stdin: ?File, - /// The reading end of the child process's standard output pipe. -/// Usage requires `stdout_behavior == StdIo.Pipe`. -/// Available after calling `spawn()`. +/// Usage requires `process.SpawnOptions.StdIo.pipe`. stdout: ?File, - /// The reading end of the child process's standard error pipe. -/// Usage requires `stderr_behavior == StdIo.Pipe`. -/// Available after calling `spawn()`. +/// Usage requires `process.SpawnOptions.StdIo.pipe`. stderr: ?File, - -/// Terminated state of the child process. -/// Available after calling `wait()`. -term: ?(SpawnError!Term), - -argv: []const []const u8, - -parent_environ: process.Environ, -/// `null` means to use `parent_environ` also for the spawned process. -env_map: ?*const EnvMap, - -stdin_behavior: StdIo, -stdout_behavior: StdIo, -stderr_behavior: StdIo, - -/// Set to change the user id when spawning the child process. -uid: if (native_os == .windows or native_os == .wasi) void else ?posix.uid_t, - -/// Set to change the group id when spawning the child process. -gid: if (native_os == .windows or native_os == .wasi) void else ?posix.gid_t, - -/// Set to change the process group id when spawning the child process. -pgid: if (native_os == .windows or native_os == .wasi) void else ?posix.pid_t, - -/// Set to change the current working directory when spawning the child process. -cwd: ?[]const u8, -/// Set to change the current working directory when spawning the child process. -/// This is not yet implemented for Windows. See https://github.com/ziglang/zig/issues/5190 -/// Once that is done, `cwd` will be deprecated in favor of this field. -cwd_dir: ?Io.Dir = null, - -err_pipe: if (native_os == .windows) void else ?posix.fd_t, - -expand_arg0: Arg0Expand, - -/// Darwin-only. Disable ASLR for the child process. -disable_aslr: bool = false, - -/// Start child process in suspended state. -/// For Posix systems it's started as if SIGSTOP was sent. -start_suspended: bool = false, - -/// Windows-only. Sets the CREATE_NO_WINDOW flag in CreateProcess. -create_no_window: bool = false, - -/// Set to true to obtain rusage information for the child process. -/// Depending on the target platform and implementation status, the -/// requested statistics may or may not be available. If they are -/// available, then the `resource_usage_statistics` field will be populated -/// after calling `wait`. -/// On Linux and Darwin, this obtains rusage statistics from wait4(). -request_resource_usage_statistics: bool = false, - /// This is available after calling wait if /// `request_resource_usage_statistics` was set to `true` before calling /// `spawn`. +/// TODO move this data into `Term` resource_usage_statistics: ResourceUsageStatistics = .{}, - -/// When populated, a pipe will be created for the child process to -/// communicate progress back to the parent. The file descriptor of the -/// write end of the pipe will be specified in the `ZIG_PROGRESS` -/// environment variable inside the child process. The progress reported by -/// the child will be attached to this progress node in the parent process. -/// -/// The child's progress tree will be grafted into the parent's progress tree, -/// by substituting this node with the child's root node. -progress_node: std.Progress.Node = std.Progress.Node.none, +request_resource_usage_statistics: bool, pub const ResourceUsageStatistics = struct { rusage: @TypeOf(rusage_init) = rusage_init, @@ -168,233 +86,60 @@ pub const ResourceUsageStatistics = struct { .tvos, .visionos, .watchos, - => @as(?posix.rusage, null), - .windows => @as(?windows.VM_COUNTERS, null), + => @as(?std.posix.rusage, null), + .windows => @as(?std.os.windows.VM_COUNTERS, null), else => {}, }; }; -pub const Arg0Expand = posix.Arg0Expand; - -pub const SpawnError = error{ - OutOfMemory, - - /// POSIX-only. `StdIo.Ignore` was selected and opening `/dev/null` returned ENODEV. - NoDevice, - - /// Windows-only. `cwd` or `argv` was provided and it was invalid WTF-8. - /// https://wtf-8.codeberg.page/ - InvalidWtf8, - - /// Windows-only. `cwd` was provided, but the path did not exist when spawning the child process. - CurrentWorkingDirectoryUnlinked, - - /// Windows-only. NUL (U+0000), LF (U+000A), CR (U+000D) are not allowed - /// within arguments when executing a `.bat`/`.cmd` script. - /// - NUL/LF signifiies end of arguments, so anything afterwards - /// would be lost after execution. - /// - CR is stripped by `cmd.exe`, so any CR codepoints - /// would be lost after execution. - InvalidBatchScriptArg, -} || - posix.ExecveError || - posix.SetIdError || - posix.SetPgidError || - posix.ChangeCurDirError || - windows.CreateProcessError || - windows.GetProcessMemoryInfoError || - windows.WaitForSingleObjectError; - pub const Term = union(enum) { - Exited: u8, - Signal: u32, - Stopped: u32, - Unknown: u32, + exited: u8, + signal: std.posix.SIG, + stopped: u32, + unknown: u32, }; -/// Behavior of the child process's standard input, output, and error -/// streams. -pub const StdIo = enum { - /// Inherit the stream from the parent process. - Inherit, - - /// Pass a null stream to the child process. - /// This is /dev/null on POSIX and NUL on Windows. - Ignore, - - /// Create a pipe for the stream. - /// The corresponding field (`stdout`, `stderr`, or `stdin`) - /// will be assigned a `File` object that can be used - /// to read from or write to the pipe. - Pipe, - - /// Close the stream after the child process spawns. - Close, -}; - -/// First argument in argv is the executable. -pub fn init(gpa: Allocator, argv: []const []const u8, environ: Environ) Child { - return .{ - .allocator = gpa, - .argv = argv, - .environ = environ, - .id = undefined, - .thread_handle = undefined, - .err_pipe = if (native_os == .windows) {} else null, - .term = null, - .cwd = null, - .uid = if (native_os == .windows or native_os == .wasi) {} else null, - .gid = if (native_os == .windows or native_os == .wasi) {} else null, - .pgid = if (native_os == .windows or native_os == .wasi) {} else null, - .stdin = null, - .stdout = null, - .stderr = null, - .stdin_behavior = .Inherit, - .stdout_behavior = .Inherit, - .stderr_behavior = .Inherit, - .expand_arg0 = .no_expand, - }; -} - -pub fn setUserName(self: *Child, name: []const u8) !void { - const user_info = try process.getUserInfo(name); - self.uid = user_info.uid; - self.gid = user_info.gid; -} - -/// On success must call `kill` or `wait`. -/// After spawning the `id` is available. -pub fn spawn(self: *Child, io: Io) SpawnError!void { - if (!process.can_spawn) { - @compileError("the target operating system cannot spawn processes"); - } - - if (native_os == .windows) { - return self.spawnWindows(io); - } else { - return self.spawnPosix(io); - } -} - -pub fn spawnAndWait(child: *Child, io: Io) SpawnError!Term { - try child.spawn(io); - return child.wait(io); -} - -/// Forcibly terminates child process and then cleans up all resources. -pub fn kill(self: *Child, io: Io) !Term { - if (native_os == .windows) { - return self.killWindows(io, 1); - } else { - return self.killPosix(io); - } -} - -pub fn killWindows(self: *Child, io: Io, exit_code: windows.UINT) !Term { - if (self.term) |term| { - self.cleanupStreams(io); - return term; - } - - windows.TerminateProcess(self.id, exit_code) catch |err| switch (err) { - error.AccessDenied => { - // Usually when TerminateProcess triggers a ACCESS_DENIED error, it - // indicates that the process has already exited, but there may be - // some rare edge cases where our process handle no longer has the - // PROCESS_TERMINATE access right, so let's do another check to make - // sure the process is really no longer running: - windows.WaitForSingleObjectEx(self.id, 0, false) catch return err; - return error.AlreadyTerminated; - }, - else => return err, - }; - try self.waitUnwrappedWindows(io); - return self.term.?; -} - -pub fn killPosix(self: *Child, io: Io) !Term { - if (self.term) |term| { - self.cleanupStreams(io); - return term; - } - posix.kill(self.id, posix.SIG.TERM) catch |err| switch (err) { - error.ProcessNotFound => return error.AlreadyTerminated, - else => return err, - }; - self.waitUnwrappedPosix(io); - return self.term.?; -} - -pub const WaitError = SpawnError || std.os.windows.GetProcessMemoryInfoError; - -/// On some targets, `spawn` may not report all spawn errors, such as `error.InvalidExe`. -/// This function will block until any spawn errors can be reported, and return them. -pub fn waitForSpawn(self: *Child) SpawnError!void { - if (native_os == .windows) return; // `spawn` reports everything - if (self.term) |term| { - _ = term catch |spawn_err| return spawn_err; +/// Requests for the operating system to forcibly terminate the child process, +/// then blocks until it terminates, then cleans up all resources. +/// +/// Idempotent and does nothing after `wait` returns. +/// +/// Uncancelable. Ignores unexpected errors from the operating system. +pub fn kill(child: *Child, io: Io) void { + if (child.id != null) { + assert(child.stdin == null); + assert(child.stdout == null); + assert(child.stderr == null); return; } - - const err_pipe = self.err_pipe orelse return; - self.err_pipe = null; - // Wait for the child to report any errors in or before `execvpe`. - const report = readIntFd(err_pipe); - posix.close(err_pipe); - if (report) |child_err_int| { - const child_err: SpawnError = @errorCast(@errorFromInt(child_err_int)); - self.term = child_err; - return child_err; - } else |read_err| switch (read_err) { - error.EndOfStream => { - // Write end closed by CLOEXEC at the time of the `execvpe` call, - // indicating success. - }, - else => { - // Problem reading the error from the error reporting pipe. We - // don't know if the child is alive or dead. Better to assume it is - // alive so the resource does not risk being leaked. - }, - } + io.vtable.childKill(io.userdata, child); + assert(child.id == null); } +pub const WaitError = error{ + AccessDenied, +} || Io.Cancelable || Io.UnexpectedError; + /// Blocks until child process terminates and then cleans up all resources. -pub fn wait(self: *Child, io: Io) WaitError!Term { - try self.waitForSpawn(); // report spawn errors - if (self.term) |term| { - self.cleanupStreams(io); - return term; - } - switch (native_os) { - .windows => try self.waitUnwrappedWindows(io), - else => self.waitUnwrappedPosix(io), - } - self.id = undefined; - return self.term.?; +pub fn wait(child: *Child, io: Io) WaitError!Term { + assert(child.id != null); + return io.vtable.childWait(io.userdata, child); } -pub const RunResult = struct { - term: Term, - stdout: []u8, - stderr: []u8, -}; - /// Collect the output from the process's stdout and stderr. Will return once all output /// has been collected. This does not mean that the process has ended. `wait` should still /// be called to wait for and clean up the process. /// -/// The process must be started with stdout_behavior and stderr_behavior == .Pipe +/// The process must have been started with stdout and stderr set to +/// `process.SpawnOptions.StdIo.pipe`. pub fn collectOutput( - child: Child, + child: *const Child, /// Used for `stdout` and `stderr`. allocator: Allocator, stdout: *ArrayList(u8), stderr: *ArrayList(u8), max_output_bytes: usize, ) !void { - assert(child.stdout_behavior == .Pipe); - assert(child.stderr_behavior == .Pipe); - var poller = std.Io.poll(allocator, enum { stdout, stderr }, .{ .stdout = child.stdout.?, .stderr = child.stderr.?, @@ -431,1469 +176,3 @@ pub fn collectOutput( return error.StderrStreamTooLong; } } - -pub const RunError = posix.GetCwdError || posix.ReadError || SpawnError || posix.PollError || error{ - StdoutStreamTooLong, - StderrStreamTooLong, -}; - -/// Spawns a child process, waits for it, collecting stdout and stderr, and then returns. -/// If it succeeds, the caller owns result.stdout and result.stderr memory. -pub fn run(gpa: Allocator, io: Io, args: struct { - argv: []const []const u8, - environ: Environ, - cwd: ?[]const u8 = null, - cwd_dir: ?Io.Dir = null, - max_output_bytes: usize = 50 * 1024, - expand_arg0: Arg0Expand = .no_expand, - progress_node: std.Progress.Node = std.Progress.Node.none, -}) RunError!RunResult { - var child = Child.init(gpa, args.argv, args.environ); - child.stdin_behavior = .Ignore; - child.stdout_behavior = .Pipe; - child.stderr_behavior = .Pipe; - child.cwd = args.cwd; - child.cwd_dir = args.cwd_dir; - child.expand_arg0 = args.expand_arg0; - child.progress_node = args.progress_node; - - var stdout: ArrayList(u8) = .empty; - defer stdout.deinit(gpa); - var stderr: ArrayList(u8) = .empty; - defer stderr.deinit(gpa); - - try child.spawn(io); - errdefer { - _ = child.kill(io) catch {}; - } - try child.collectOutput(gpa, &stdout, &stderr, args.max_output_bytes); - - return .{ - .stdout = try stdout.toOwnedSlice(gpa), - .stderr = try stderr.toOwnedSlice(gpa), - .term = try child.wait(io), - }; -} - -fn waitUnwrappedWindows(self: *Child, io: Io) WaitError!void { - const result = windows.WaitForSingleObjectEx(self.id, windows.INFINITE, false); - - self.term = @as(SpawnError!Term, x: { - var exit_code: windows.DWORD = undefined; - if (windows.kernel32.GetExitCodeProcess(self.id, &exit_code) == 0) { - break :x Term{ .Unknown = 0 }; - } else { - break :x Term{ .Exited = @as(u8, @truncate(exit_code)) }; - } - }); - - if (self.request_resource_usage_statistics) { - self.resource_usage_statistics.rusage = try windows.GetProcessMemoryInfo(self.id); - } - - posix.close(self.id); - posix.close(self.thread_handle); - self.cleanupStreams(io); - return result; -} - -fn waitUnwrappedPosix(self: *Child, io: Io) void { - const res: posix.WaitPidResult = res: { - if (self.request_resource_usage_statistics) { - switch (native_os) { - .dragonfly, - .freebsd, - .netbsd, - .openbsd, - .illumos, - .linux, - .serenity, - .driverkit, - .ios, - .maccatalyst, - .macos, - .tvos, - .visionos, - .watchos, - => { - var ru: posix.rusage = undefined; - const res = posix.wait4(self.id, 0, &ru); - self.resource_usage_statistics.rusage = ru; - break :res res; - }, - else => {}, - } - } - - break :res posix.waitpid(self.id, 0); - }; - const status = res.status; - self.cleanupStreams(io); - self.handleWaitResult(status); -} - -fn handleWaitResult(self: *Child, status: u32) void { - self.term = statusToTerm(status); -} - -fn cleanupStreams(self: *Child, io: Io) void { - if (self.stdin) |*stdin| { - stdin.close(io); - self.stdin = null; - } - if (self.stdout) |*stdout| { - stdout.close(io); - self.stdout = null; - } - if (self.stderr) |*stderr| { - stderr.close(io); - self.stderr = null; - } -} - -fn statusToTerm(status: u32) Term { - return if (posix.W.IFEXITED(status)) - Term{ .Exited = posix.W.EXITSTATUS(status) } - else if (posix.W.IFSIGNALED(status)) - Term{ .Signal = posix.W.TERMSIG(status) } - else if (posix.W.IFSTOPPED(status)) - Term{ .Stopped = posix.W.STOPSIG(status) } - else - Term{ .Unknown = status }; -} - -fn spawnPosix(self: *Child, io: Io) SpawnError!void { - // The child process does need to access (one end of) these pipes. However, - // we must initially set CLOEXEC to avoid a race condition. If another thread - // is racing to spawn a different child process, we don't want it to inherit - // these FDs in any scenario; that would mean that, for instance, calls to - // `poll` from the parent would not report the child's stdout as closing when - // expected, since the other child may retain a reference to the write end of - // the pipe. So, we create the pipes with CLOEXEC initially. After fork, we - // need to do something in the new child to make sure we preserve the reference - // we want. We could use `fcntl` to remove CLOEXEC from the FD, but as it - // turns out, we `dup2` everything anyway, so there's no need! - const pipe_flags: posix.O = .{ .CLOEXEC = true }; - - const stdin_pipe = if (self.stdin_behavior == .Pipe) try posix.pipe2(pipe_flags) else undefined; - errdefer if (self.stdin_behavior == .Pipe) { - destroyPipe(stdin_pipe); - }; - - const stdout_pipe = if (self.stdout_behavior == .Pipe) try posix.pipe2(pipe_flags) else undefined; - errdefer if (self.stdout_behavior == .Pipe) { - destroyPipe(stdout_pipe); - }; - - const stderr_pipe = if (self.stderr_behavior == .Pipe) try posix.pipe2(pipe_flags) else undefined; - errdefer if (self.stderr_behavior == .Pipe) { - destroyPipe(stderr_pipe); - }; - - const any_ignore = (self.stdin_behavior == .Ignore or self.stdout_behavior == .Ignore or self.stderr_behavior == .Ignore); - const dev_null_fd = if (any_ignore) - posix.openZ("/dev/null", .{ .ACCMODE = .RDWR }, 0) catch |err| switch (err) { - error.PathAlreadyExists => unreachable, - error.NoSpaceLeft => unreachable, - error.FileTooBig => unreachable, - error.DeviceBusy => unreachable, - error.FileLocksUnsupported => unreachable, - error.BadPathName => unreachable, // Windows-only - error.WouldBlock => unreachable, - error.NetworkNotFound => unreachable, // Windows-only - error.Canceled => unreachable, // temporarily in the posix error set - error.SharingViolation => unreachable, // Windows-only - error.PipeBusy => unreachable, // not a pipe - error.AntivirusInterference => unreachable, // Windows-only - else => |e| return e, - } - else - undefined; - defer { - if (any_ignore) posix.close(dev_null_fd); - } - - const prog_pipe: [2]posix.fd_t = p: { - if (self.progress_node.index == .none) { - break :p .{ -1, -1 }; - } else { - // We use CLOEXEC for the same reason as in `pipe_flags`. - break :p try posix.pipe2(.{ .NONBLOCK = true, .CLOEXEC = true }); - } - }; - errdefer destroyPipe(prog_pipe); - - var arena_allocator = std.heap.ArenaAllocator.init(self.allocator); - defer arena_allocator.deinit(); - const arena = arena_allocator.allocator(); - - // The POSIX standard does not allow malloc() between fork() and execve(), - // and `self.allocator` may be a libc allocator. - // I have personally observed the child process deadlocking when it tries - // to call malloc() due to a heap allocation between fork() and execve(), - // in musl v1.1.24. - // Additionally, we want to reduce the number of possible ways things - // can fail between fork() and execve(). - // Therefore, we do all the allocation for the execve() before the fork(). - // This means we must do the null-termination of argv and env vars here. - const argv_buf = try arena.allocSentinel(?[*:0]const u8, self.argv.len, null); - for (self.argv, 0..) |arg, i| argv_buf[i] = (try arena.dupeZ(u8, arg)).ptr; - - const prog_fileno = 3; - comptime assert(@max(posix.STDIN_FILENO, posix.STDOUT_FILENO, posix.STDERR_FILENO) + 1 == prog_fileno); - - const envp: [*:null]const ?[*:0]const u8 = m: { - const prog_fd: i32 = if (prog_pipe[1] == -1) -1 else prog_fileno; - switch (self.environ) { - .empty => break :m (try process.Environ.createBlock(.{ .block = &.{} }, arena, .{ - .zig_progress_fd = prog_fd, - })).ptr, - .inherit => |b| break :m (try b.createBlock(arena, .{ - .zig_progress_fd = prog_fd, - })).ptr, - .map => |m| break :m (try m.createBlock(arena, .{ - .zig_progress_fd = prog_fd, - })).ptr, - } - }; - - // This pipe communicates to the parent errors in the child between `fork` and `execvpe`. - // It is closed by the child (via CLOEXEC) without writing if `execvpe` succeeds. - const err_pipe: [2]posix.fd_t = try posix.pipe2(.{ .CLOEXEC = true }); - errdefer destroyPipe(err_pipe); - - const pid_result = try posix.fork(); - if (pid_result == 0) { - // we are the child - setUpChildIo(self.stdin_behavior, stdin_pipe[0], posix.STDIN_FILENO, dev_null_fd) catch |err| forkChildErrReport(io, err_pipe[1], err); - setUpChildIo(self.stdout_behavior, stdout_pipe[1], posix.STDOUT_FILENO, dev_null_fd) catch |err| forkChildErrReport(io, err_pipe[1], err); - setUpChildIo(self.stderr_behavior, stderr_pipe[1], posix.STDERR_FILENO, dev_null_fd) catch |err| forkChildErrReport(io, err_pipe[1], err); - - if (self.cwd_dir) |cwd| { - posix.fchdir(cwd.handle) catch |err| forkChildErrReport(io, err_pipe[1], err); - } else if (self.cwd) |cwd| { - posix.chdir(cwd) catch |err| forkChildErrReport(io, err_pipe[1], err); - } - - // Must happen after fchdir above, the cwd file descriptor might be - // equal to prog_fileno and be clobbered by this dup2 call. - if (prog_pipe[1] != -1) posix.dup2(prog_pipe[1], prog_fileno) catch |err| forkChildErrReport(io, err_pipe[1], err); - - if (self.gid) |gid| { - posix.setregid(gid, gid) catch |err| forkChildErrReport(io, err_pipe[1], err); - } - - if (self.uid) |uid| { - posix.setreuid(uid, uid) catch |err| forkChildErrReport(io, err_pipe[1], err); - } - - if (self.pgid) |pid| { - posix.setpgid(0, pid) catch |err| forkChildErrReport(io, err_pipe[1], err); - } - - if (self.start_suspended) { - posix.kill(posix.getpid(), .STOP) catch |err| forkChildErrReport(io, err_pipe[1], err); - } - - const parent_PATH: ?[]const u8 = switch(self.environ) { - .empty => null, - .inherit => - .map => |m| m.get("PATH"), - }; - - const err = switch (self.expand_arg0) { - .expand => posix.execvpeZ_expandArg0(.expand, argv_buf.ptr[0].?, argv_buf.ptr, envp, parent_PATH), - .no_expand => posix.execvpeZ_expandArg0(.no_expand, argv_buf.ptr[0].?, argv_buf.ptr, envp, parent_PATH), - }; - forkChildErrReport(io, err_pipe[1], err); - } - - // we are the parent - errdefer comptime unreachable; // The child is forked; we must not error from now on - - posix.close(err_pipe[1]); // make sure only the child holds the write end open - self.err_pipe = err_pipe[0]; - - const pid: i32 = @intCast(pid_result); - if (self.stdin_behavior == .Pipe) { - self.stdin = .{ .handle = stdin_pipe[1] }; - } else { - self.stdin = null; - } - if (self.stdout_behavior == .Pipe) { - self.stdout = .{ .handle = stdout_pipe[0] }; - } else { - self.stdout = null; - } - if (self.stderr_behavior == .Pipe) { - self.stderr = .{ .handle = stderr_pipe[0] }; - } else { - self.stderr = null; - } - - self.id = pid; - self.term = null; - - if (self.stdin_behavior == .Pipe) { - posix.close(stdin_pipe[0]); - } - if (self.stdout_behavior == .Pipe) { - posix.close(stdout_pipe[1]); - } - if (self.stderr_behavior == .Pipe) { - posix.close(stderr_pipe[1]); - } - - if (prog_pipe[1] != -1) { - posix.close(prog_pipe[1]); - } - self.progress_node.setIpcFd(prog_pipe[0]); -} - -fn spawnWindows(self: *Child, io: Io) SpawnError!void { - var saAttr = windows.SECURITY_ATTRIBUTES{ - .nLength = @sizeOf(windows.SECURITY_ATTRIBUTES), - .bInheritHandle = windows.TRUE, - .lpSecurityDescriptor = null, - }; - - const any_ignore = (self.stdin_behavior == StdIo.Ignore or self.stdout_behavior == StdIo.Ignore or self.stderr_behavior == StdIo.Ignore); - - const nul_handle = if (any_ignore) - // "\Device\Null" or "\??\NUL" - windows.OpenFile(&[_]u16{ '\\', 'D', 'e', 'v', 'i', 'c', 'e', '\\', 'N', 'u', 'l', 'l' }, .{ - .access_mask = .{ - .STANDARD = .{ .SYNCHRONIZE = true }, - .GENERIC = .{ .WRITE = true, .READ = true }, - }, - .sa = &saAttr, - .creation = .OPEN, - }) catch |err| switch (err) { - error.PathAlreadyExists => return error.Unexpected, // not possible for "NUL" - error.PipeBusy => return error.Unexpected, // not possible for "NUL" - error.NoDevice => return error.Unexpected, // not possible for "NUL" - error.FileNotFound => return error.Unexpected, // not possible for "NUL" - error.AccessDenied => return error.Unexpected, // not possible for "NUL" - error.NameTooLong => return error.Unexpected, // not possible for "NUL" - error.WouldBlock => return error.Unexpected, // not possible for "NUL" - error.NetworkNotFound => return error.Unexpected, // not possible for "NUL" - error.AntivirusInterference => return error.Unexpected, // not possible for "NUL" - error.OperationCanceled => return error.Unexpected, // we're not canceling the operation - else => |e| return e, - } - else - undefined; - defer { - if (any_ignore) posix.close(nul_handle); - } - - var g_hChildStd_IN_Rd: ?windows.HANDLE = null; - var g_hChildStd_IN_Wr: ?windows.HANDLE = null; - switch (self.stdin_behavior) { - StdIo.Pipe => { - try windowsMakePipeIn(&g_hChildStd_IN_Rd, &g_hChildStd_IN_Wr, &saAttr); - }, - StdIo.Ignore => { - g_hChildStd_IN_Rd = nul_handle; - }, - StdIo.Inherit => { - g_hChildStd_IN_Rd = windows.GetStdHandle(windows.STD_INPUT_HANDLE) catch null; - }, - StdIo.Close => { - g_hChildStd_IN_Rd = null; - }, - } - errdefer if (self.stdin_behavior == StdIo.Pipe) { - windowsDestroyPipe(g_hChildStd_IN_Rd, g_hChildStd_IN_Wr); - }; - - var g_hChildStd_OUT_Rd: ?windows.HANDLE = null; - var g_hChildStd_OUT_Wr: ?windows.HANDLE = null; - switch (self.stdout_behavior) { - StdIo.Pipe => { - try windowsMakeAsyncPipe(&g_hChildStd_OUT_Rd, &g_hChildStd_OUT_Wr, &saAttr); - }, - StdIo.Ignore => { - g_hChildStd_OUT_Wr = nul_handle; - }, - StdIo.Inherit => { - g_hChildStd_OUT_Wr = windows.GetStdHandle(windows.STD_OUTPUT_HANDLE) catch null; - }, - StdIo.Close => { - g_hChildStd_OUT_Wr = null; - }, - } - errdefer if (self.stdout_behavior == StdIo.Pipe) { - windowsDestroyPipe(g_hChildStd_OUT_Rd, g_hChildStd_OUT_Wr); - }; - - var g_hChildStd_ERR_Rd: ?windows.HANDLE = null; - var g_hChildStd_ERR_Wr: ?windows.HANDLE = null; - switch (self.stderr_behavior) { - StdIo.Pipe => { - try windowsMakeAsyncPipe(&g_hChildStd_ERR_Rd, &g_hChildStd_ERR_Wr, &saAttr); - }, - StdIo.Ignore => { - g_hChildStd_ERR_Wr = nul_handle; - }, - StdIo.Inherit => { - g_hChildStd_ERR_Wr = windows.GetStdHandle(windows.STD_ERROR_HANDLE) catch null; - }, - StdIo.Close => { - g_hChildStd_ERR_Wr = null; - }, - } - errdefer if (self.stderr_behavior == StdIo.Pipe) { - windowsDestroyPipe(g_hChildStd_ERR_Rd, g_hChildStd_ERR_Wr); - }; - - var siStartInfo = windows.STARTUPINFOW{ - .cb = @sizeOf(windows.STARTUPINFOW), - .hStdError = g_hChildStd_ERR_Wr, - .hStdOutput = g_hChildStd_OUT_Wr, - .hStdInput = g_hChildStd_IN_Rd, - .dwFlags = windows.STARTF_USESTDHANDLES, - - .lpReserved = null, - .lpDesktop = null, - .lpTitle = null, - .dwX = 0, - .dwY = 0, - .dwXSize = 0, - .dwYSize = 0, - .dwXCountChars = 0, - .dwYCountChars = 0, - .dwFillAttribute = 0, - .wShowWindow = 0, - .cbReserved2 = 0, - .lpReserved2 = null, - }; - var piProcInfo: windows.PROCESS_INFORMATION = undefined; - - const cwd_w = if (self.cwd) |cwd| try unicode.wtf8ToWtf16LeAllocZ(self.allocator, cwd) else null; - defer if (cwd_w) |cwd| self.allocator.free(cwd); - const cwd_w_ptr = if (cwd_w) |cwd| cwd.ptr else null; - - const maybe_envp_buf = if (self.env_map) |env_map| try process.createWindowsEnvBlock(self.allocator, env_map) else null; - defer if (maybe_envp_buf) |envp_buf| self.allocator.free(envp_buf); - const envp_ptr = if (maybe_envp_buf) |envp_buf| envp_buf.ptr else null; - - const app_name_wtf8 = self.argv[0]; - const app_name_is_absolute = fs.path.isAbsolute(app_name_wtf8); - - // the cwd set in Child is in effect when choosing the executable path - // to match posix semantics - var cwd_path_w_needs_free = false; - const cwd_path_w = x: { - // If the app name is absolute, then we need to use its dirname as the cwd - if (app_name_is_absolute) { - cwd_path_w_needs_free = true; - const dir = fs.path.dirname(app_name_wtf8).?; - break :x try unicode.wtf8ToWtf16LeAllocZ(self.allocator, dir); - } else if (self.cwd) |cwd| { - cwd_path_w_needs_free = true; - break :x try unicode.wtf8ToWtf16LeAllocZ(self.allocator, cwd); - } else { - break :x &[_:0]u16{}; // empty for cwd - } - }; - defer if (cwd_path_w_needs_free) self.allocator.free(cwd_path_w); - - // If the app name has more than just a filename, then we need to separate that - // into the basename and dirname and use the dirname as an addition to the cwd - // path. This is because NtQueryDirectoryFile cannot accept FileName params with - // path separators. - const app_basename_wtf8 = fs.path.basename(app_name_wtf8); - // If the app name is absolute, then the cwd will already have the app's dirname in it, - // so only populate app_dirname if app name is a relative path with > 0 path separators. - const maybe_app_dirname_wtf8 = if (!app_name_is_absolute) fs.path.dirname(app_name_wtf8) else null; - const app_dirname_w: ?[:0]u16 = x: { - if (maybe_app_dirname_wtf8) |app_dirname_wtf8| { - break :x try unicode.wtf8ToWtf16LeAllocZ(self.allocator, app_dirname_wtf8); - } - break :x null; - }; - defer if (app_dirname_w != null) self.allocator.free(app_dirname_w.?); - - const app_name_w = try unicode.wtf8ToWtf16LeAllocZ(self.allocator, app_basename_wtf8); - defer self.allocator.free(app_name_w); - - const flags: windows.CreateProcessFlags = .{ - .create_suspended = self.start_suspended, - .create_unicode_environment = true, - .create_no_window = self.create_no_window, - }; - - run: { - const PATH: [:0]const u16 = process.getenvW(unicode.utf8ToUtf16LeStringLiteral("PATH")) orelse &[_:0]u16{}; - const PATHEXT: [:0]const u16 = process.getenvW(unicode.utf8ToUtf16LeStringLiteral("PATHEXT")) orelse &[_:0]u16{}; - - // In case the command ends up being a .bat/.cmd script, we need to escape things using the cmd.exe rules - // and invoke cmd.exe ourselves in order to mitigate arbitrary command execution from maliciously - // constructed arguments. - // - // We'll need to wait until we're actually trying to run the command to know for sure - // if the resolved command has the `.bat` or `.cmd` extension, so we defer actually - // serializing the command line until we determine how it should be serialized. - var cmd_line_cache = WindowsCommandLineCache.init(self.allocator, self.argv); - defer cmd_line_cache.deinit(); - - var app_buf: ArrayList(u16) = .empty; - defer app_buf.deinit(self.allocator); - - try app_buf.appendSlice(self.allocator, app_name_w); - - var dir_buf: ArrayList(u16) = .empty; - defer dir_buf.deinit(self.allocator); - - if (cwd_path_w.len > 0) { - try dir_buf.appendSlice(self.allocator, cwd_path_w); - } - if (app_dirname_w) |app_dir| { - if (dir_buf.items.len > 0) try dir_buf.append(self.allocator, fs.path.sep); - try dir_buf.appendSlice(self.allocator, app_dir); - } - - windowsCreateProcessPathExt(self.allocator, io, &dir_buf, &app_buf, PATHEXT, &cmd_line_cache, envp_ptr, cwd_w_ptr, flags, &siStartInfo, &piProcInfo) catch |no_path_err| { - const original_err = switch (no_path_err) { - // argv[0] contains unsupported characters that will never resolve to a valid exe. - error.InvalidArg0 => return error.FileNotFound, - error.FileNotFound, error.InvalidExe, error.AccessDenied => |e| e, - error.UnrecoverableInvalidExe => return error.InvalidExe, - else => |e| return e, - }; - - // If the app name had path separators, that disallows PATH searching, - // and there's no need to search the PATH if the app name is absolute. - // We still search the path if the cwd is absolute because of the - // "cwd set in Child is in effect when choosing the executable path - // to match posix semantics" behavior--we don't want to skip searching - // the PATH just because we were trying to set the cwd of the child process. - if (app_dirname_w != null or app_name_is_absolute) { - return original_err; - } - - var it = mem.tokenizeScalar(u16, PATH, ';'); - while (it.next()) |search_path| { - dir_buf.clearRetainingCapacity(); - try dir_buf.appendSlice(self.allocator, search_path); - - if (windowsCreateProcessPathExt(self.allocator, io, &dir_buf, &app_buf, PATHEXT, &cmd_line_cache, envp_ptr, cwd_w_ptr, flags, &siStartInfo, &piProcInfo)) { - break :run; - } else |err| switch (err) { - // argv[0] contains unsupported characters that will never resolve to a valid exe. - error.InvalidArg0 => return error.FileNotFound, - error.FileNotFound, error.AccessDenied, error.InvalidExe => continue, - error.UnrecoverableInvalidExe => return error.InvalidExe, - else => |e| return e, - } - } else { - return original_err; - } - }; - } - - if (g_hChildStd_IN_Wr) |h| { - self.stdin = File{ .handle = h }; - } else { - self.stdin = null; - } - if (g_hChildStd_OUT_Rd) |h| { - self.stdout = File{ .handle = h }; - } else { - self.stdout = null; - } - if (g_hChildStd_ERR_Rd) |h| { - self.stderr = File{ .handle = h }; - } else { - self.stderr = null; - } - - self.id = piProcInfo.hProcess; - self.thread_handle = piProcInfo.hThread; - self.term = null; - - if (self.stdin_behavior == StdIo.Pipe) { - posix.close(g_hChildStd_IN_Rd.?); - } - if (self.stderr_behavior == StdIo.Pipe) { - posix.close(g_hChildStd_ERR_Wr.?); - } - if (self.stdout_behavior == StdIo.Pipe) { - posix.close(g_hChildStd_OUT_Wr.?); - } -} - -fn setUpChildIo(stdio: StdIo, pipe_fd: i32, std_fileno: i32, dev_null_fd: i32) !void { - switch (stdio) { - .Pipe => try posix.dup2(pipe_fd, std_fileno), - .Close => posix.close(std_fileno), - .Inherit => {}, - .Ignore => try posix.dup2(dev_null_fd, std_fileno), - } -} - -fn destroyPipe(pipe: [2]posix.fd_t) void { - if (pipe[0] != -1) posix.close(pipe[0]); - if (pipe[0] != pipe[1]) posix.close(pipe[1]); -} - -// Child of fork calls this to report an error to the fork parent. -// Then the child exits. -fn forkChildErrReport(io: Io, fd: i32, err: Child.SpawnError) noreturn { - writeIntFd(io, fd, @as(ErrInt, @intFromError(err))) catch {}; - // If we're linking libc, some naughty applications may have registered atexit handlers - // which we really do not want to run in the fork child. I caught LLVM doing this and - // it caused a deadlock instead of doing an exit syscall. In the words of Avril Lavigne, - // "Why'd you have to go and make things so complicated?" - if (builtin.link_libc) { - // The _exit(2) function does nothing but make the exit syscall, unlike exit(3) - std.c._exit(1); - } - posix.system.exit(1); -} - -fn writeIntFd(io: Io, fd: i32, value: ErrInt) !void { - var buffer: [8]u8 = undefined; - var fw: File.Writer = .initStreaming(.{ .handle = fd }, io, &buffer); - fw.interface.writeInt(u64, value, .little) catch unreachable; - fw.interface.flush() catch return error.SystemResources; -} - -fn readIntFd(fd: i32) !ErrInt { - var buffer: [8]u8 = undefined; - var i: usize = 0; - while (i < buffer.len) { - const n = try std.posix.read(fd, buffer[i..]); - if (n == 0) return error.EndOfStream; - i += n; - } - const int = mem.readInt(u64, &buffer, .little); - return @intCast(int); -} - -const ErrInt = std.meta.Int(.unsigned, @sizeOf(anyerror) * 8); - -/// Expects `app_buf` to contain exactly the app name, and `dir_buf` to contain exactly the dir path. -/// After return, `app_buf` will always contain exactly the app name and `dir_buf` will always contain exactly the dir path. -/// Note: `app_buf` should not contain any leading path separators. -/// Note: If the dir is the cwd, dir_buf should be empty (len = 0). -fn windowsCreateProcessPathExt( - allocator: Allocator, - io: Io, - dir_buf: *ArrayList(u16), - app_buf: *ArrayList(u16), - pathext: [:0]const u16, - cmd_line_cache: *WindowsCommandLineCache, - envp_ptr: ?[*]u16, - cwd_ptr: ?[*:0]u16, - flags: windows.CreateProcessFlags, - lpStartupInfo: *windows.STARTUPINFOW, - lpProcessInformation: *windows.PROCESS_INFORMATION, -) !void { - const app_name_len = app_buf.items.len; - const dir_path_len = dir_buf.items.len; - - if (app_name_len == 0) return error.FileNotFound; - - defer app_buf.shrinkRetainingCapacity(app_name_len); - defer dir_buf.shrinkRetainingCapacity(dir_path_len); - - // The name of the game here is to avoid CreateProcessW calls at all costs, - // and only ever try calling it when we have a real candidate for execution. - // Secondarily, we want to minimize the number of syscalls used when checking - // for each PATHEXT-appended version of the app name. - // - // An overview of the technique used: - // - Open the search directory for iteration (either cwd or a path from PATH) - // - Use NtQueryDirectoryFile with a wildcard filename of `*` to - // check if anything that could possibly match either the unappended version - // of the app name or any of the versions with a PATHEXT value appended exists. - // - If the wildcard NtQueryDirectoryFile call found nothing, we can exit early - // without needing to use PATHEXT at all. - // - // This allows us to use a sequence - // for any directory that doesn't contain any possible matches, instead of having - // to use a separate look up for each individual filename combination (unappended + - // each PATHEXT appended). For directories where the wildcard *does* match something, - // we iterate the matches and take note of any that are either the unappended version, - // or a version with a supported PATHEXT appended. We then try calling CreateProcessW - // with the found versions in the appropriate order. - - // In the future, child process execution needs to move to Io implementation. - // Under those conditions, here we will have access to lower level directory - // opening function knowing which implementation we are in. Here, we imitate - // that scenario. - var dir = dir: { - // needs to be null-terminated - try dir_buf.append(allocator, 0); - defer dir_buf.shrinkRetainingCapacity(dir_path_len); - const dir_path_z = dir_buf.items[0 .. dir_buf.items.len - 1 :0]; - const prefixed_path = try windows.wToPrefixedFileW(null, dir_path_z); - break :dir Io.Threaded.dirOpenDirWindows(.cwd(), prefixed_path.span(), .{ - .iterate = true, - }) catch return error.FileNotFound; - }; - defer dir.close(io); - - // Add wildcard and null-terminator - try app_buf.append(allocator, '*'); - try app_buf.append(allocator, 0); - const app_name_wildcard = app_buf.items[0 .. app_buf.items.len - 1 :0]; - - // This 2048 is arbitrary, we just want it to be large enough to get multiple FILE_DIRECTORY_INFORMATION entries - // returned per NtQueryDirectoryFile call. - var file_information_buf: [2048]u8 align(@alignOf(windows.FILE_DIRECTORY_INFORMATION)) = undefined; - const file_info_maximum_single_entry_size = @sizeOf(windows.FILE_DIRECTORY_INFORMATION) + (windows.NAME_MAX * 2); - if (file_information_buf.len < file_info_maximum_single_entry_size) { - @compileError("file_information_buf must be large enough to contain at least one maximum size FILE_DIRECTORY_INFORMATION entry"); - } - var io_status: windows.IO_STATUS_BLOCK = undefined; - - const num_supported_pathext = @typeInfo(WindowsExtension).@"enum".fields.len; - var pathext_seen = [_]bool{false} ** num_supported_pathext; - var any_pathext_seen = false; - var unappended_exists = false; - - // Fully iterate the wildcard matches via NtQueryDirectoryFile and take note of all versions - // of the app_name we should try to spawn. - // Note: This is necessary because the order of the files returned is filesystem-dependent: - // On NTFS, `blah.exe*` will always return `blah.exe` first if it exists. - // On FAT32, it's possible for something like `blah.exe.obj` to be returned first. - while (true) { - const app_name_len_bytes = std.math.cast(u16, app_name_wildcard.len * 2) orelse return error.NameTooLong; - var app_name_unicode_string = windows.UNICODE_STRING{ - .Length = app_name_len_bytes, - .MaximumLength = app_name_len_bytes, - .Buffer = @constCast(app_name_wildcard.ptr), - }; - const rc = windows.ntdll.NtQueryDirectoryFile( - dir.handle, - null, - null, - null, - &io_status, - &file_information_buf, - file_information_buf.len, - .Directory, - windows.FALSE, // single result - &app_name_unicode_string, - windows.FALSE, // restart iteration - ); - - // If we get nothing with the wildcard, then we can just bail out - // as we know appending PATHEXT will not yield anything. - switch (rc) { - .SUCCESS => {}, - .NO_SUCH_FILE => return error.FileNotFound, - .NO_MORE_FILES => break, - .ACCESS_DENIED => return error.AccessDenied, - else => return windows.unexpectedStatus(rc), - } - - // According to the docs, this can only happen if there is not enough room in the - // buffer to write at least one complete FILE_DIRECTORY_INFORMATION entry. - // Therefore, this condition should not be possible to hit with the buffer size we use. - std.debug.assert(io_status.Information != 0); - - var it = windows.FileInformationIterator(windows.FILE_DIRECTORY_INFORMATION){ .buf = &file_information_buf }; - while (it.next()) |info| { - // Skip directories - if (info.FileAttributes.DIRECTORY) continue; - const filename = @as([*]u16, @ptrCast(&info.FileName))[0 .. info.FileNameLength / 2]; - // Because all results start with the app_name since we're using the wildcard `app_name*`, - // if the length is equal to app_name then this is an exact match - if (filename.len == app_name_len) { - // Note: We can't break early here because it's possible that the unappended version - // fails to spawn, in which case we still want to try the PATHEXT appended versions. - unappended_exists = true; - } else if (windowsCreateProcessSupportsExtension(filename[app_name_len..])) |pathext_ext| { - pathext_seen[@intFromEnum(pathext_ext)] = true; - any_pathext_seen = true; - } - } - } - - const unappended_err = unappended: { - if (unappended_exists) { - if (dir_path_len != 0) switch (dir_buf.items[dir_buf.items.len - 1]) { - '/', '\\' => {}, - else => try dir_buf.append(allocator, fs.path.sep), - }; - try dir_buf.appendSlice(allocator, app_buf.items[0..app_name_len]); - try dir_buf.append(allocator, 0); - const full_app_name = dir_buf.items[0 .. dir_buf.items.len - 1 :0]; - - const is_bat_or_cmd = bat_or_cmd: { - const app_name = app_buf.items[0..app_name_len]; - const ext_start = std.mem.lastIndexOfScalar(u16, app_name, '.') orelse break :bat_or_cmd false; - const ext = app_name[ext_start..]; - const ext_enum = windowsCreateProcessSupportsExtension(ext) orelse break :bat_or_cmd false; - switch (ext_enum) { - .cmd, .bat => break :bat_or_cmd true, - else => break :bat_or_cmd false, - } - }; - const cmd_line_w = if (is_bat_or_cmd) - try cmd_line_cache.scriptCommandLine(full_app_name) - else - try cmd_line_cache.commandLine(); - const app_name_w = if (is_bat_or_cmd) - try cmd_line_cache.cmdExePath() - else - full_app_name; - - if (windowsCreateProcess(app_name_w.ptr, cmd_line_w.ptr, envp_ptr, cwd_ptr, flags, lpStartupInfo, lpProcessInformation)) |_| { - return; - } else |err| switch (err) { - error.FileNotFound, - error.AccessDenied, - => break :unappended err, - error.InvalidExe => { - // On InvalidExe, if the extension of the app name is .exe then - // it's treated as an unrecoverable error. Otherwise, it'll be - // skipped as normal. - const app_name = app_buf.items[0..app_name_len]; - const ext_start = std.mem.lastIndexOfScalar(u16, app_name, '.') orelse break :unappended err; - const ext = app_name[ext_start..]; - if (windows.eqlIgnoreCaseWtf16(ext, unicode.utf8ToUtf16LeStringLiteral(".EXE"))) { - return error.UnrecoverableInvalidExe; - } - break :unappended err; - }, - else => return err, - } - } - break :unappended error.FileNotFound; - }; - - if (!any_pathext_seen) return unappended_err; - - // Now try any PATHEXT appended versions that we've seen - var ext_it = mem.tokenizeScalar(u16, pathext, ';'); - while (ext_it.next()) |ext| { - const ext_enum = windowsCreateProcessSupportsExtension(ext) orelse continue; - if (!pathext_seen[@intFromEnum(ext_enum)]) continue; - - dir_buf.shrinkRetainingCapacity(dir_path_len); - if (dir_path_len != 0) switch (dir_buf.items[dir_buf.items.len - 1]) { - '/', '\\' => {}, - else => try dir_buf.append(allocator, fs.path.sep), - }; - try dir_buf.appendSlice(allocator, app_buf.items[0..app_name_len]); - try dir_buf.appendSlice(allocator, ext); - try dir_buf.append(allocator, 0); - const full_app_name = dir_buf.items[0 .. dir_buf.items.len - 1 :0]; - - const is_bat_or_cmd = switch (ext_enum) { - .cmd, .bat => true, - else => false, - }; - const cmd_line_w = if (is_bat_or_cmd) - try cmd_line_cache.scriptCommandLine(full_app_name) - else - try cmd_line_cache.commandLine(); - const app_name_w = if (is_bat_or_cmd) - try cmd_line_cache.cmdExePath() - else - full_app_name; - - if (windowsCreateProcess(app_name_w.ptr, cmd_line_w.ptr, envp_ptr, cwd_ptr, flags, lpStartupInfo, lpProcessInformation)) |_| { - return; - } else |err| switch (err) { - error.FileNotFound => continue, - error.AccessDenied => continue, - error.InvalidExe => { - // On InvalidExe, if the extension of the app name is .exe then - // it's treated as an unrecoverable error. Otherwise, it'll be - // skipped as normal. - if (windows.eqlIgnoreCaseWtf16(ext, unicode.utf8ToUtf16LeStringLiteral(".EXE"))) { - return error.UnrecoverableInvalidExe; - } - continue; - }, - else => return err, - } - } - - return unappended_err; -} - -fn windowsCreateProcess( - app_name: [*:0]u16, - cmd_line: [*:0]u16, - envp_ptr: ?[*]u16, - cwd_ptr: ?[*:0]u16, - flags: windows.CreateProcessFlags, - lpStartupInfo: *windows.STARTUPINFOW, - lpProcessInformation: *windows.PROCESS_INFORMATION, -) !void { - // TODO the docs for environment pointer say: - // > A pointer to the environment block for the new process. If this parameter - // > is NULL, the new process uses the environment of the calling process. - // > ... - // > An environment block can contain either Unicode or ANSI characters. If - // > the environment block pointed to by lpEnvironment contains Unicode - // > characters, be sure that dwCreationFlags includes CREATE_UNICODE_ENVIRONMENT. - // > If this parameter is NULL and the environment block of the parent process - // > contains Unicode characters, you must also ensure that dwCreationFlags - // > includes CREATE_UNICODE_ENVIRONMENT. - // This seems to imply that we have to somehow know whether our process parent passed - // CREATE_UNICODE_ENVIRONMENT if we want to pass NULL for the environment parameter. - // Since we do not know this information that would imply that we must not pass NULL - // for the parameter. - // However this would imply that programs compiled with -DUNICODE could not pass - // environment variables to programs that were not, which seems unlikely. - // More investigation is needed. - return windows.CreateProcessW( - app_name, - cmd_line, - null, - null, - windows.TRUE, - flags, - @as(?*anyopaque, @ptrCast(envp_ptr)), - cwd_ptr, - lpStartupInfo, - lpProcessInformation, - ); -} - -fn windowsMakePipeIn(rd: *?windows.HANDLE, wr: *?windows.HANDLE, sattr: *const windows.SECURITY_ATTRIBUTES) !void { - var rd_h: windows.HANDLE = undefined; - var wr_h: windows.HANDLE = undefined; - try windows.CreatePipe(&rd_h, &wr_h, sattr); - errdefer windowsDestroyPipe(rd_h, wr_h); - try windows.SetHandleInformation(wr_h, windows.HANDLE_FLAG_INHERIT, 0); - rd.* = rd_h; - wr.* = wr_h; -} - -fn windowsDestroyPipe(rd: ?windows.HANDLE, wr: ?windows.HANDLE) void { - if (rd) |h| posix.close(h); - if (wr) |h| posix.close(h); -} - -fn windowsMakeAsyncPipe(rd: *?windows.HANDLE, wr: *?windows.HANDLE, sattr: *const windows.SECURITY_ATTRIBUTES) !void { - var tmp_bufw: [128]u16 = undefined; - - // Anonymous pipes are built upon Named pipes. - // https://docs.microsoft.com/en-us/windows/win32/api/namedpipeapi/nf-namedpipeapi-createpipe - // Asynchronous (overlapped) read and write operations are not supported by anonymous pipes. - // https://docs.microsoft.com/en-us/windows/win32/ipc/anonymous-pipe-operations - const pipe_path = blk: { - var tmp_buf: [128]u8 = undefined; - // Forge a random path for the pipe. - const pipe_path = std.fmt.bufPrintSentinel( - &tmp_buf, - "\\\\.\\pipe\\zig-childprocess-{d}-{d}", - .{ windows.GetCurrentProcessId(), pipe_name_counter.fetchAdd(1, .monotonic) }, - 0, - ) catch unreachable; - const len = std.unicode.wtf8ToWtf16Le(&tmp_bufw, pipe_path) catch unreachable; - tmp_bufw[len] = 0; - break :blk tmp_bufw[0..len :0]; - }; - - // Create the read handle that can be used with overlapped IO ops. - const read_handle = windows.kernel32.CreateNamedPipeW( - pipe_path.ptr, - windows.PIPE_ACCESS_INBOUND | windows.FILE_FLAG_OVERLAPPED, - windows.PIPE_TYPE_BYTE, - 1, - 4096, - 4096, - 0, - sattr, - ); - if (read_handle == windows.INVALID_HANDLE_VALUE) { - switch (windows.GetLastError()) { - else => |err| return windows.unexpectedError(err), - } - } - errdefer posix.close(read_handle); - - var sattr_copy = sattr.*; - const write_handle = windows.kernel32.CreateFileW( - pipe_path.ptr, - .{ .GENERIC = .{ .WRITE = true } }, - 0, - &sattr_copy, - windows.OPEN_EXISTING, - @bitCast(windows.FILE.ATTRIBUTE{ .NORMAL = true }), - null, - ); - if (write_handle == windows.INVALID_HANDLE_VALUE) { - switch (windows.GetLastError()) { - else => |err| return windows.unexpectedError(err), - } - } - errdefer posix.close(write_handle); - - try windows.SetHandleInformation(read_handle, windows.HANDLE_FLAG_INHERIT, 0); - - rd.* = read_handle; - wr.* = write_handle; -} - -var pipe_name_counter = std.atomic.Value(u32).init(1); - -/// File name extensions supported natively by `CreateProcess()` on Windows. -// Should be kept in sync with `windowsCreateProcessSupportsExtension`. -pub const WindowsExtension = enum { - bat, - cmd, - com, - exe, -}; - -/// Case-insensitive WTF-16 lookup -fn windowsCreateProcessSupportsExtension(ext: []const u16) ?WindowsExtension { - if (ext.len != 4) return null; - const State = enum { - start, - dot, - b, - ba, - c, - cm, - co, - e, - ex, - }; - var state: State = .start; - for (ext) |c| switch (state) { - .start => switch (c) { - '.' => state = .dot, - else => return null, - }, - .dot => switch (c) { - 'b', 'B' => state = .b, - 'c', 'C' => state = .c, - 'e', 'E' => state = .e, - else => return null, - }, - .b => switch (c) { - 'a', 'A' => state = .ba, - else => return null, - }, - .c => switch (c) { - 'm', 'M' => state = .cm, - 'o', 'O' => state = .co, - else => return null, - }, - .e => switch (c) { - 'x', 'X' => state = .ex, - else => return null, - }, - .ba => switch (c) { - 't', 'T' => return .bat, - else => return null, - }, - .cm => switch (c) { - 'd', 'D' => return .cmd, - else => return null, - }, - .co => switch (c) { - 'm', 'M' => return .com, - else => return null, - }, - .ex => switch (c) { - 'e', 'E' => return .exe, - else => return null, - }, - }; - return null; -} - -test windowsCreateProcessSupportsExtension { - try std.testing.expectEqual(WindowsExtension.exe, windowsCreateProcessSupportsExtension(&[_]u16{ '.', 'e', 'X', 'e' }).?); - try std.testing.expect(windowsCreateProcessSupportsExtension(&[_]u16{ '.', 'e', 'X', 'e', 'c' }) == null); -} - -/// Serializes argv into a WTF-16 encoded command-line string for use with CreateProcessW. -/// -/// Serialization is done on-demand and the result is cached in order to allow for: -/// - Only serializing the particular type of command line needed (`.bat`/`.cmd` -/// command line serialization is different from `.exe`/etc) -/// - Reusing the serialized command lines if necessary (i.e. if the execution -/// of a command fails and the PATH is going to be continued to be searched -/// for more candidates) -const WindowsCommandLineCache = struct { - cmd_line: ?[:0]u16 = null, - script_cmd_line: ?[:0]u16 = null, - cmd_exe_path: ?[:0]u16 = null, - argv: []const []const u8, - allocator: Allocator, - - fn init(allocator: Allocator, argv: []const []const u8) WindowsCommandLineCache { - return .{ - .allocator = allocator, - .argv = argv, - }; - } - - fn deinit(self: *WindowsCommandLineCache) void { - if (self.cmd_line) |cmd_line| self.allocator.free(cmd_line); - if (self.script_cmd_line) |script_cmd_line| self.allocator.free(script_cmd_line); - if (self.cmd_exe_path) |cmd_exe_path| self.allocator.free(cmd_exe_path); - } - - fn commandLine(self: *WindowsCommandLineCache) ![:0]u16 { - if (self.cmd_line == null) { - self.cmd_line = try argvToCommandLineWindows(self.allocator, self.argv); - } - return self.cmd_line.?; - } - - /// Not cached, since the path to the batch script will change during PATH searching. - /// `script_path` should be as qualified as possible, e.g. if the PATH is being searched, - /// then script_path should include both the search path and the script filename - /// (this allows avoiding cmd.exe having to search the PATH again). - fn scriptCommandLine(self: *WindowsCommandLineCache, script_path: []const u16) ![:0]u16 { - if (self.script_cmd_line) |v| self.allocator.free(v); - self.script_cmd_line = try argvToScriptCommandLineWindows( - self.allocator, - script_path, - self.argv[1..], - ); - return self.script_cmd_line.?; - } - - fn cmdExePath(self: *WindowsCommandLineCache) ![:0]u16 { - if (self.cmd_exe_path == null) { - self.cmd_exe_path = try windowsCmdExePath(self.allocator); - } - return self.cmd_exe_path.?; - } -}; - -/// Returns the absolute path of `cmd.exe` within the Windows system directory. -/// The caller owns the returned slice. -fn windowsCmdExePath(allocator: Allocator) error{ OutOfMemory, Unexpected }![:0]u16 { - var buf = try ArrayList(u16).initCapacity(allocator, 128); - errdefer buf.deinit(allocator); - while (true) { - const unused_slice = buf.unusedCapacitySlice(); - // TODO: Get the system directory from PEB.ReadOnlyStaticServerData - const len = windows.kernel32.GetSystemDirectoryW(@ptrCast(unused_slice), @intCast(unused_slice.len)); - if (len == 0) { - switch (windows.GetLastError()) { - else => |err| return windows.unexpectedError(err), - } - } - if (len > unused_slice.len) { - try buf.ensureUnusedCapacity(allocator, len); - } else { - buf.items.len = len; - break; - } - } - switch (buf.items[buf.items.len - 1]) { - '/', '\\' => {}, - else => try buf.append(allocator, fs.path.sep), - } - try buf.appendSlice(allocator, unicode.utf8ToUtf16LeStringLiteral("cmd.exe")); - return try buf.toOwnedSliceSentinel(allocator, 0); -} - -const ArgvToCommandLineError = error{ OutOfMemory, InvalidWtf8, InvalidArg0 }; - -/// Serializes `argv` to a Windows command-line string suitable for passing to a child process and -/// parsing by the `CommandLineToArgvW` algorithm. The caller owns the returned slice. -/// -/// To avoid arbitrary command execution, this function should not be used when spawning `.bat`/`.cmd` scripts. -/// https://flatt.tech/research/posts/batbadbut-you-cant-securely-execute-commands-on-windows/ -/// -/// When executing `.bat`/`.cmd` scripts, use `argvToScriptCommandLineWindows` instead. -fn argvToCommandLineWindows( - allocator: Allocator, - argv: []const []const u8, -) ArgvToCommandLineError![:0]u16 { - var buf = std.array_list.Managed(u8).init(allocator); - defer buf.deinit(); - - if (argv.len != 0) { - const arg0 = argv[0]; - - // The first argument must be quoted if it contains spaces or ASCII control characters - // (excluding DEL). It also follows special quoting rules where backslashes have no special - // interpretation, which makes it impossible to pass certain first arguments containing - // double quotes to a child process without characters from the first argument leaking into - // subsequent ones (which could have security implications). - // - // Empty arguments technically don't need quotes, but we quote them anyway for maximum - // compatibility with different implementations of the 'CommandLineToArgvW' algorithm. - // - // Double quotes are illegal in paths on Windows, so for the sake of simplicity we reject - // all first arguments containing double quotes, even ones that we could theoretically - // serialize in unquoted form. - var needs_quotes = arg0.len == 0; - for (arg0) |c| { - if (c <= ' ') { - needs_quotes = true; - } else if (c == '"') { - return error.InvalidArg0; - } - } - if (needs_quotes) { - try buf.append('"'); - try buf.appendSlice(arg0); - try buf.append('"'); - } else { - try buf.appendSlice(arg0); - } - - for (argv[1..]) |arg| { - try buf.append(' '); - - // Subsequent arguments must be quoted if they contain spaces, tabs or double quotes, - // or if they are empty. For simplicity and for maximum compatibility with different - // implementations of the 'CommandLineToArgvW' algorithm, we also quote all ASCII - // control characters (again, excluding DEL). - needs_quotes = for (arg) |c| { - if (c <= ' ' or c == '"') { - break true; - } - } else arg.len == 0; - if (!needs_quotes) { - try buf.appendSlice(arg); - continue; - } - - try buf.append('"'); - var backslash_count: usize = 0; - for (arg) |byte| { - switch (byte) { - '\\' => { - backslash_count += 1; - }, - '"' => { - try buf.appendNTimes('\\', backslash_count * 2 + 1); - try buf.append('"'); - backslash_count = 0; - }, - else => { - try buf.appendNTimes('\\', backslash_count); - try buf.append(byte); - backslash_count = 0; - }, - } - } - try buf.appendNTimes('\\', backslash_count * 2); - try buf.append('"'); - } - } - - return try unicode.wtf8ToWtf16LeAllocZ(allocator, buf.items); -} - -test argvToCommandLineWindows { - const t = testArgvToCommandLineWindows; - - try t(&.{ - \\C:\Program Files\zig\zig.exe - , - \\run - , - \\.\src\main.zig - , - \\-target - , - \\x86_64-windows-gnu - , - \\-O - , - \\ReleaseSafe - , - \\-- - , - \\--emoji=🗿 - , - \\--eval=new Regex("Dwayne \"The Rock\" Johnson") - , - }, - \\"C:\Program Files\zig\zig.exe" run .\src\main.zig -target x86_64-windows-gnu -O ReleaseSafe -- --emoji=🗿 "--eval=new Regex(\"Dwayne \\\"The Rock\\\" Johnson\")" - ); - - try t(&.{}, ""); - try t(&.{""}, "\"\""); - try t(&.{" "}, "\" \""); - try t(&.{"\t"}, "\"\t\""); - try t(&.{"\x07"}, "\"\x07\""); - try t(&.{"🦎"}, "🦎"); - - try t( - &.{ "zig", "aa aa", "bb\tbb", "cc\ncc", "dd\r\ndd", "ee\x7Fee" }, - "zig \"aa aa\" \"bb\tbb\" \"cc\ncc\" \"dd\r\ndd\" ee\x7Fee", - ); - - try t( - &.{ "\\\\foo bar\\foo bar\\", "\\\\zig zag\\zig zag\\" }, - "\"\\\\foo bar\\foo bar\\\" \"\\\\zig zag\\zig zag\\\\\"", - ); - - try std.testing.expectError( - error.InvalidArg0, - argvToCommandLineWindows(std.testing.allocator, &.{"\"quotes\"quotes\""}), - ); - try std.testing.expectError( - error.InvalidArg0, - argvToCommandLineWindows(std.testing.allocator, &.{"quotes\"quotes"}), - ); - try std.testing.expectError( - error.InvalidArg0, - argvToCommandLineWindows(std.testing.allocator, &.{"q u o t e s \" q u o t e s"}), - ); -} - -fn testArgvToCommandLineWindows(argv: []const []const u8, expected_cmd_line: []const u8) !void { - const cmd_line_w = try argvToCommandLineWindows(std.testing.allocator, argv); - defer std.testing.allocator.free(cmd_line_w); - - const cmd_line = try unicode.wtf16LeToWtf8Alloc(std.testing.allocator, cmd_line_w); - defer std.testing.allocator.free(cmd_line); - - try std.testing.expectEqualStrings(expected_cmd_line, cmd_line); -} - -const ArgvToScriptCommandLineError = error{ - OutOfMemory, - InvalidWtf8, - /// NUL (U+0000), LF (U+000A), CR (U+000D) are not allowed - /// within arguments when executing a `.bat`/`.cmd` script. - /// - NUL/LF signifiies end of arguments, so anything afterwards - /// would be lost after execution. - /// - CR is stripped by `cmd.exe`, so any CR codepoints - /// would be lost after execution. - InvalidBatchScriptArg, -}; - -/// Serializes `argv` to a Windows command-line string that uses `cmd.exe /c` and `cmd.exe`-specific -/// escaping rules. The caller owns the returned slice. -/// -/// Escapes `argv` using the suggested mitigation against arbitrary command execution from: -/// https://flatt.tech/research/posts/batbadbut-you-cant-securely-execute-commands-on-windows/ -/// -/// The return of this function will look like -/// `cmd.exe /d /e:ON /v:OFF /c ""` -/// and should be used as the `lpCommandLine` of `CreateProcessW`, while the -/// return of `windowsCmdExePath` should be used as `lpApplicationName`. -/// -/// Should only be used when spawning `.bat`/`.cmd` scripts, see `argvToCommandLineWindows` otherwise. -/// The `.bat`/`.cmd` file must be known to both have the `.bat`/`.cmd` extension and exist on the filesystem. -fn argvToScriptCommandLineWindows( - allocator: Allocator, - /// Path to the `.bat`/`.cmd` script. If this path is relative, it is assumed to be relative to the CWD. - /// The script must have been verified to exist at this path before calling this function. - script_path: []const u16, - /// Arguments, not including the script name itself. Expected to be encoded as WTF-8. - script_args: []const []const u8, -) ArgvToScriptCommandLineError![:0]u16 { - var buf = try std.array_list.Managed(u8).initCapacity(allocator, 64); - defer buf.deinit(); - - // `/d` disables execution of AutoRun commands. - // `/e:ON` and `/v:OFF` are needed for BatBadBut mitigation: - // > If delayed expansion is enabled via the registry value DelayedExpansion, - // > it must be disabled by explicitly calling cmd.exe with the /V:OFF option. - // > Escaping for % requires the command extension to be enabled. - // > If it’s disabled via the registry value EnableExtensions, it must be enabled with the /E:ON option. - // https://flatt.tech/research/posts/batbadbut-you-cant-securely-execute-commands-on-windows/ - buf.appendSliceAssumeCapacity("cmd.exe /d /e:ON /v:OFF /c \""); - - // Always quote the path to the script arg - buf.appendAssumeCapacity('"'); - // We always want the path to the batch script to include a path separator in order to - // avoid cmd.exe searching the PATH for the script. This is not part of the arbitrary - // command execution mitigation, we just know exactly what script we want to execute - // at this point, and potentially making cmd.exe re-find it is unnecessary. - // - // If the script path does not have a path separator, then we know its relative to CWD and - // we can just put `.\` in the front. - if (mem.findAny(u16, script_path, &[_]u16{ mem.nativeToLittle(u16, '\\'), mem.nativeToLittle(u16, '/') }) == null) { - try buf.appendSlice(".\\"); - } - // Note that we don't do any escaping/mitigations for this argument, since the relevant - // characters (", %, etc) are illegal in file paths and this function should only be called - // with script paths that have been verified to exist. - try unicode.wtf16LeToWtf8ArrayList(&buf, script_path); - buf.appendAssumeCapacity('"'); - - for (script_args) |arg| { - // Literal carriage returns get stripped when run through cmd.exe - // and NUL/newlines act as 'end of command.' Because of this, it's basically - // always a mistake to include these characters in argv, so it's - // an error condition in order to ensure that the return of this - // function can always roundtrip through cmd.exe. - if (std.mem.findAny(u8, arg, "\x00\r\n") != null) { - return error.InvalidBatchScriptArg; - } - - // Separate args with a space. - try buf.append(' '); - - // Need to quote if the argument is empty (otherwise the arg would just be lost) - // or if the last character is a `\`, since then something like "%~2" in a .bat - // script would cause the closing " to be escaped which we don't want. - var needs_quotes = arg.len == 0 or arg[arg.len - 1] == '\\'; - if (!needs_quotes) { - for (arg) |c| { - switch (c) { - // Known good characters that don't need to be quoted - 'A'...'Z', 'a'...'z', '0'...'9', '#', '$', '*', '+', '-', '.', '/', ':', '?', '@', '\\', '_' => {}, - // When in doubt, quote - else => { - needs_quotes = true; - break; - }, - } - } - } - if (needs_quotes) { - try buf.append('"'); - } - var backslashes: usize = 0; - for (arg) |c| { - switch (c) { - '\\' => { - backslashes += 1; - }, - '"' => { - try buf.appendNTimes('\\', backslashes); - try buf.append('"'); - backslashes = 0; - }, - // Replace `%` with `%%cd:~,%`. - // - // cmd.exe allows extracting a substring from an environment - // variable with the syntax: `%foo:~,%`. - // Therefore, `%cd:~,%` will always expand to an empty string - // since both the start and end index are blank, and it is assumed - // that `%cd%` is always available since it is a built-in variable - // that corresponds to the current directory. - // - // This means that replacing `%foo%` with `%%cd:~,%foo%%cd:~,%` - // will stop `%foo%` from being expanded and *after* expansion - // we'll still be left with `%foo%` (the literal string). - '%' => { - // the trailing `%` is appended outside the switch - try buf.appendSlice("%%cd:~,"); - backslashes = 0; - }, - else => { - backslashes = 0; - }, - } - try buf.append(c); - } - if (needs_quotes) { - try buf.appendNTimes('\\', backslashes); - try buf.append('"'); - } - } - - try buf.append('"'); - - return try unicode.wtf8ToWtf16LeAllocZ(allocator, buf.items); -} diff --git a/lib/std/zig/LibCInstallation.zig b/lib/std/zig/LibCInstallation.zig index f2489f9ee741ac30a063ca81170e2a4976d314d9..444a9c7df9d81509ff72f8d7150e2d336b0978ae 100644 --- a/lib/std/zig/LibCInstallation.zig +++ b/lib/std/zig/LibCInstallation.zig @@ -268,15 +268,17 @@ fn findNativeIncludeDirPosix(self: *LibCInstallation, gpa: Allocator, io: Io, ar dev_null, }); - const run_res = std.process.Child.run(gpa, io, .{ - .argv = argv.items, + const run_res = std.process.run(gpa, io, .{ .max_output_bytes = 1024 * 1024, - .env_map = &env_map, - // Some C compilers, such as Clang, are known to rely on argv[0] to find the path - // to their own executable, without even bothering to resolve PATH. This results in the message: - // error: unable to execute command: Executable "" doesn't exist! - // So we use the expandArg0 variant of ChildProcess to give them a helping hand. - .expand_arg0 = .expand, + .spawn_options = .{ + .argv = argv.items, + .env_map = &env_map, + // Some C compilers, such as Clang, are known to rely on argv[0] to find the path + // to their own executable, without even bothering to resolve PATH. This results in the message: + // error: unable to execute command: Executable "" doesn't exist! + // So we use the expandArg0 variant of ChildProcess to give them a helping hand. + .expand_arg0 = .expand, + }, }) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, else => { @@ -289,7 +291,7 @@ fn findNativeIncludeDirPosix(self: *LibCInstallation, gpa: Allocator, io: Io, ar gpa.free(run_res.stderr); } switch (run_res.term) { - .Exited => |code| if (code != 0) { + .exited => |code| if (code != 0) { printVerboseInvocation(argv.items, null, args.verbose, run_res.stderr); return error.CCompilerExitCode; }, @@ -585,15 +587,17 @@ fn ccPrintFileName(gpa: Allocator, io: Io, args: CCPrintFileNameOptions) ![:0]u8 try appendCcExe(&argv, skip_cc_env_var); try argv.append(arg1); - const run_res = std.process.Child.run(gpa, io, .{ - .argv = argv.items, + const run_res = std.process.run(gpa, io, .{ .max_output_bytes = 1024 * 1024, - .env_map = &env_map, - // Some C compilers, such as Clang, are known to rely on argv[0] to find the path - // to their own executable, without even bothering to resolve PATH. This results in the message: - // error: unable to execute command: Executable "" doesn't exist! - // So we use the expandArg0 variant of ChildProcess to give them a helping hand. - .expand_arg0 = .expand, + .spawn_options = .{ + .argv = argv.items, + .env_map = &env_map, + // Some C compilers, such as Clang, are known to rely on argv[0] to find the path + // to their own executable, without even bothering to resolve PATH. This results in the message: + // error: unable to execute command: Executable "" doesn't exist! + // So we use the expandArg0 variant of ChildProcess to give them a helping hand. + .expand_arg0 = .expand, + }, }) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, else => return error.UnableToSpawnCCompiler, @@ -603,7 +607,7 @@ fn ccPrintFileName(gpa: Allocator, io: Io, args: CCPrintFileNameOptions) ![:0]u8 gpa.free(run_res.stderr); } switch (run_res.term) { - .Exited => |code| if (code != 0) { + .exited => |code| if (code != 0) { printVerboseInvocation(argv.items, args.search_basename, args.verbose, run_res.stderr); return error.CCompilerExitCode; }, diff --git a/lib/std/zig/system/darwin.zig b/lib/std/zig/system/darwin.zig index e69de48d262ec9daed3bdf17e41a65b458f9e6e4..df07c68cbaa7fad5d65d802a2de0c63eea3694e4 100644 --- a/lib/std/zig/system/darwin.zig +++ b/lib/std/zig/system/darwin.zig @@ -17,15 +17,15 @@ pub const macos = @import("darwin/macos.zig"); /// /// If error.OutOfMemory occurs in Allocator, this function returns null. pub fn isSdkInstalled(gpa: Allocator, io: Io) bool { - const result = std.process.Child.run(gpa, io, .{ + const result = std.process.run(gpa, io, .{ .spawn_options = .{ .argv = &.{ "xcode-select", "--print-path" }, - }) catch return false; + } }) catch return false; defer { gpa.free(result.stderr); gpa.free(result.stdout); } return switch (result.term) { - .Exited => |code| if (code == 0) result.stdout.len > 0 else false, + .exited => |code| if (code == 0) result.stdout.len > 0 else false, else => false, }; } @@ -35,7 +35,7 @@ pub fn isSdkInstalled(gpa: Allocator, io: Io) bool { /// Caller owns the memory. /// stderr from xcrun is ignored. /// If error.OutOfMemory occurs in Allocator, this function returns null. -pub fn getSdk(gpa: Allocator, io: Io, environ: std.process.Child.Environ, target: *const Target) ?[]const u8 { +pub fn getSdk(gpa: Allocator, io: Io, target: *const Target) ?[]const u8 { const is_simulator_abi = target.abi == .simulator; const sdk = switch (target.os.tag) { .driverkit => "driverkit", @@ -47,16 +47,13 @@ pub fn getSdk(gpa: Allocator, io: Io, environ: std.process.Child.Environ, target else => return null, }; const argv = &[_][]const u8{ "xcrun", "--sdk", sdk, "--show-sdk-path" }; - const result = std.process.Child.run(gpa, io, .{ - .argv = argv, - .environ = environ, - }) catch return null; + const result = std.process.run(gpa, io, .{ .spawn_options = .{ .argv = argv } }) catch return null; defer { gpa.free(result.stderr); gpa.free(result.stdout); } switch (result.term) { - .Exited => |code| if (code != 0) return null, + .exited => |code| if (code != 0) return null, else => return null, } return gpa.dupe(u8, mem.trimEnd(u8, result.stdout, "\r\n")) catch null; diff --git a/src/Compilation.zig b/src/Compilation.zig index a11519d07a84ef11c073955890d137352bee61aa..b3751b55828b25d1a828da2acd8355866867061b 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -6339,15 +6339,15 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr if (std.process.can_spawn) { var child = std.process.Child.init(argv.items, arena); if (comp.clang_passthrough_mode) { - child.stdin_behavior = .Inherit; - child.stdout_behavior = .Inherit; - child.stderr_behavior = .Inherit; + child.stdin_behavior = .inherit; + child.stdout_behavior = .inherit; + child.stderr_behavior = .inherit; const term = child.spawnAndWait(io) catch |err| { return comp.failCObj(c_object, "failed to spawn zig clang (passthrough mode) {s}: {s}", .{ argv.items[0], @errorName(err) }); }; switch (term) { - .Exited => |code| { + .exited => |code| { if (code != 0) { std.process.exit(code); } @@ -6357,9 +6357,9 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr else => std.process.abort(), } } else { - child.stdin_behavior = .Ignore; - child.stdout_behavior = .Ignore; - child.stderr_behavior = .Pipe; + child.stdin_behavior = .ignore; + child.stdout_behavior = .ignore; + child.stderr_behavior = .pipe; try child.spawn(io); @@ -6371,7 +6371,7 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr }; switch (term) { - .Exited => |code| if (code != 0) if (out_diag_path) |diag_file_path| { + .exited => |code| if (code != 0) if (out_diag_path) |diag_file_path| { const bundle = CObject.Diag.Bundle.parse(gpa, io, diag_file_path) catch |err| { log.err("{}: failed to parse clang diagnostics: {s}", .{ err, stderr }); return comp.failCObj(c_object, "clang exited with code {d}", .{code}); @@ -6742,9 +6742,9 @@ fn spawnZigRc( defer node_name.deinit(arena); var child = std.process.Child.init(argv, arena); - child.stdin_behavior = .Ignore; - child.stdout_behavior = .Pipe; - child.stderr_behavior = .Pipe; + child.stdin_behavior = .ignore; + child.stdout_behavior = .pipe; + child.stderr_behavior = .pipe; child.progress_node = child_progress_node; child.spawn(io) catch |err| { @@ -6785,12 +6785,16 @@ fn spawnZigRc( }; switch (term) { - .Exited => |code| { + .exited => |code| { if (code != 0) { log.err("zig rc failed with stderr:\n{s}", .{stderr.buffered()}); return comp.failWin32Resource(win32_resource, "zig rc exited with code {d}", .{code}); } }, + .signal => |sig| { + log.err("zig rc signaled {t} with stderr:\n{s}", .{ sig, stderr.buffered() }); + return comp.failWin32Resource(win32_resource, "zig rc terminated unexpectedly", .{}); + }, else => { log.err("zig rc terminated with stderr:\n{s}", .{stderr.buffered()}); return comp.failWin32Resource(win32_resource, "zig rc terminated unexpectedly", .{}); diff --git a/src/link/Lld.zig b/src/link/Lld.zig index b2a0f6e396399307fddbd2e04ea7255461163f56..d1d2ebf07a885b02587efdd85493599d6e948379 100644 --- a/src/link/Lld.zig +++ b/src/link/Lld.zig @@ -1606,15 +1606,15 @@ fn spawnLld(comp: *Compilation, arena: Allocator, argv: []const []const u8) !voi var child = std.process.Child.init(argv, arena); const term = (if (comp.clang_passthrough_mode) term: { - child.stdin_behavior = .Inherit; - child.stdout_behavior = .Inherit; - child.stderr_behavior = .Inherit; + child.stdin_behavior = .inherit; + child.stdout_behavior = .inherit; + child.stderr_behavior = .inherit; break :term child.spawnAndWait(io); } else term: { - child.stdin_behavior = .Ignore; - child.stdout_behavior = .Ignore; - child.stderr_behavior = .Pipe; + child.stdin_behavior = .ignore; + child.stdout_behavior = .ignore; + child.stderr_behavior = .pipe; child.spawn(io) catch |err| break :term err; var stderr_reader = child.stderr.?.readerStreaming(io, &.{}); @@ -1656,15 +1656,15 @@ fn spawnLld(comp: *Compilation, arena: Allocator, argv: []const []const u8) !voi .{try comp.dirs.local_cache.join(arena, &.{rsp_path})}, ) }, arena); if (comp.clang_passthrough_mode) { - rsp_child.stdin_behavior = .Inherit; - rsp_child.stdout_behavior = .Inherit; - rsp_child.stderr_behavior = .Inherit; + rsp_child.stdin_behavior = .inherit; + rsp_child.stdout_behavior = .inherit; + rsp_child.stderr_behavior = .inherit; break :term rsp_child.spawnAndWait(io) catch |err| break :err err; } else { - rsp_child.stdin_behavior = .Ignore; - rsp_child.stdout_behavior = .Ignore; - rsp_child.stderr_behavior = .Pipe; + rsp_child.stdin_behavior = .ignore; + rsp_child.stdout_behavior = .ignore; + rsp_child.stderr_behavior = .pipe; rsp_child.spawn(io) catch |err| break :err err; var stderr_reader = rsp_child.stderr.?.readerStreaming(io, &.{}); @@ -1680,7 +1680,7 @@ fn spawnLld(comp: *Compilation, arena: Allocator, argv: []const []const u8) !voi const diags = &comp.link_diags; switch (term) { - .Exited => |code| if (code != 0) { + .exited => |code| if (code != 0) { if (comp.clang_passthrough_mode) std.process.exit(code); diags.lockAndParseLldStderr(argv[1], stderr); return error.LinkFailure; diff --git a/src/main.zig b/src/main.zig index f71cf5d144cd1ff1f254b6e50fd93154941368ff..25d14f0d7ae74253938f22bf285c81dbe57d257b 100644 --- a/src/main.zig +++ b/src/main.zig @@ -4442,9 +4442,9 @@ fn runOrTest( } else if (process.can_spawn) { var child = std.process.Child.init(argv.items, gpa); child.env_map = &env_map; - child.stdin_behavior = .Inherit; - child.stdout_behavior = .Inherit; - child.stderr_behavior = .Inherit; + child.stdin_behavior = .inherit; + child.stdout_behavior = .inherit; + child.stderr_behavior = .inherit; // Here we release all the locks associated with the Compilation so // that whatever this child process wants to do won't deadlock. @@ -4587,9 +4587,9 @@ fn runOrTestHotSwap( else => { var child = std.process.Child.init(argv.items, gpa); - child.stdin_behavior = .Inherit; - child.stdout_behavior = .Inherit; - child.stderr_behavior = .Inherit; + child.stdin_behavior = .inherit; + child.stdout_behavior = .inherit; + child.stderr_behavior = .inherit; try child.spawn(io); @@ -5417,9 +5417,9 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) if (process.can_spawn) { var child = std.process.Child.init(child_argv.items, gpa); - child.stdin_behavior = .Inherit; - child.stdout_behavior = .Inherit; - child.stderr_behavior = .Inherit; + child.stdin_behavior = .inherit; + child.stdout_behavior = .inherit; + child.stderr_behavior = .inherit; const term = t: { _ = try io.lockStderr(&.{}, .no_color); @@ -5686,9 +5686,9 @@ fn jitCmd( } var child = std.process.Child.init(child_argv.items, gpa); - child.stdin_behavior = .Inherit; - child.stdout_behavior = if (options.capture == null) .Inherit else .Pipe; - child.stderr_behavior = .Inherit; + child.stdin_behavior = .inherit; + child.stdout_behavior = if (options.capture == null) .inherit else .pipe; + child.stderr_behavior = .inherit; const term = t: { _ = try io.lockStderr(&.{}, .no_color); diff --git a/test/link/macho.zig b/test/link/macho.zig index 844273b8e546840507d7c8ca5a02f77d725abf04..ccfecefa4402e235a7bcfb525e93444f6e047861 100644 --- a/test/link/macho.zig +++ b/test/link/macho.zig @@ -871,7 +871,7 @@ fn testLinkDirectlyCppTbd(b: *Build, opts: Options) *Step { const io = b.graph.io; const test_step = addTestStep(b, "link-directly-cpp-tbd", opts); - const sdk = std.zig.system.darwin.getSdk(b.allocator, io, .{ .map = &b.graph.env_map }, &opts.target.result) orelse + const sdk = std.zig.system.darwin.getSdk(b.allocator, io, &opts.target.result) orelse @panic("macOS SDK is required to run the test"); const exe = addExecutable(b, opts, .{ diff --git a/test/src/Debugger.zig b/test/src/Debugger.zig index d975c8fbb56e31ea7e60f9f01300db7e3225d14f..0d389f2508b2e4304d0f7ded9859659d9aaca102 100644 --- a/test/src/Debugger.zig +++ b/test/src/Debugger.zig @@ -2306,7 +2306,7 @@ fn addTest( run.addArgs(db_argv2); run.addArtifactArg(exe); for (expected_output) |expected| run.addCheck(.{ .expect_stdout_match = db.b.fmt("{s}\n", .{expected}) }); - run.addCheck(.{ .expect_term = .{ .Exited = success } }); + run.addCheck(.{ .expect_term = .{ .exited = success } }); run.setStdIn(.{ .bytes = "" }); db.root_step.dependOn(&run.step); } diff --git a/test/src/StackTrace.zig b/test/src/StackTrace.zig index 57e29dd8000ab3f892ab354ee2ea2dbed251f943..4e5c946682681ad47f458e67de7b7fd7c8a3dc24 100644 --- a/test/src/StackTrace.zig +++ b/test/src/StackTrace.zig @@ -193,9 +193,9 @@ fn addCaseInstance( run.removeEnvironmentVariable("CLICOLOR_FORCE"); run.setEnvironmentVariable("NO_COLOR", "1"); run.addCheck(.{ .expect_term = term: { - if (!expect_panic) break :term .{ .Exited = 0 }; - if (target.result.os.tag == .windows) break :term .{ .Exited = 3 }; - break :term .{ .Signal = 6 }; + if (!expect_panic) break :term .{ .exited = 0 }; + if (target.result.os.tag == .windows) break :term .{ .exited = 3 }; + break :term .{ .signal = @enumFromInt(6) }; } }); run.expectStdOutEqual(""); diff --git a/test/standalone/child_process/main.zig b/test/standalone/child_process/main.zig index 98d38bdee3677184a554fcbe01961b0e930e9865..0bd96061b44433ffd225c0d5cb5642a81b05242f 100644 --- a/test/standalone/child_process/main.zig +++ b/test/standalone/child_process/main.zig @@ -26,9 +26,9 @@ pub fn main() !void { const io = threaded.io(); var child = std.process.Child.init(&.{ child_path, "hello arg" }, gpa); - child.stdin_behavior = .Pipe; - child.stdout_behavior = .Pipe; - child.stderr_behavior = .Inherit; + child.stdin_behavior = .pipe; + child.stdout_behavior = .pipe; + child.stderr_behavior = .inherit; try child.spawn(io); const child_stdin = child.stdin.?; try child_stdin.writeStreamingAll(io, "hello from stdin"); // verified in child diff --git a/test/standalone/ios/build.zig b/test/standalone/ios/build.zig index d9bd93875b11afc1351474d1ce3e91935b214d75..b87d55993b6615c60700cb05d39cd8148be717fb 100644 --- a/test/standalone/ios/build.zig +++ b/test/standalone/ios/build.zig @@ -25,7 +25,7 @@ pub fn build(b: *std.Build) void { const io = b.graph.io; - if (std.zig.system.darwin.getSdk(b.allocator, io, .{ .map = &b.graph.env_map }, &target.result)) |sdk| { + if (std.zig.system.darwin.getSdk(b.allocator, io, &target.result)) |sdk| { b.sysroot = sdk; exe.root_module.addSystemIncludePath(.{ .cwd_relative = b.pathJoin(&.{ sdk, "/usr/include" }) }); exe.root_module.addSystemFrameworkPath(.{ .cwd_relative = b.pathJoin(&.{ sdk, "/System/Library/Frameworks" }) }); diff --git a/test/standalone/simple/hello_world/hello.zig b/test/standalone/simple/hello_world/hello.zig index a031d6c6f0482bce684392fda834c0784212801b..27cedd428ee7bc8dad67c216878f84ad963c28f8 100644 --- a/test/standalone/simple/hello_world/hello.zig +++ b/test/standalone/simple/hello_world/hello.zig @@ -1,15 +1,5 @@ const std = @import("std"); -// See https://github.com/ziglang/zig/issues/24510 -// for the plan to simplify this code. -pub fn main() !void { - var debug_allocator: std.heap.DebugAllocator(.{}) = .init; - defer _ = debug_allocator.deinit(); - const gpa = debug_allocator.allocator(); - - var threaded: std.Io.Threaded = .init(gpa, .{}); - defer threaded.deinit(); - const io = threaded.io(); - - try std.Io.File.stdout().writeStreamingAll(io, "Hello, World!\n"); +pub fn main(init: std.process.Init) !void { + try std.Io.File.stdout().writeStreamingAll(init.io, "Hello, World!\n"); } diff --git a/test/tests.zig b/test/tests.zig index aa3c018a627ce2558374a0e787372b4d2cf0b194..1634112daec7289248b9dca1ba7518e577a162ad 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -2718,7 +2718,7 @@ pub fn addIncrementalTests(b: *std.Build, test_step: *Step, test_filters: []cons if (b.enable_wasmtime) run.addArg("-fwasmtime"); if (b.enable_darling) run.addArg("-fdarling"); - run.addCheck(.{ .expect_term = .{ .Exited = 0 } }); + run.addCheck(.{ .expect_term = .{ .exited = 0 } }); test_step.dependOn(&run.step); } -- 2.54.0 From b85524d0c83686b7492aee78d5f766e76b59fc90 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 30 Dec 2025 21:45:01 -0800 Subject: [PATCH 03/60] std.process.Environ: fix contains function --- lib/std/Io.zig | 2 +- lib/std/Io/Threaded.zig | 6 +++--- lib/std/process/Environ.zig | 7 ++++--- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/lib/std/Io.zig b/lib/std/Io.zig index 9670ac3bd77c3b7a26bae7515b2d00a9e858ed36..f7de23aa5477742da0b231922b24e55b920cd825 100644 --- a/lib/std/Io.zig +++ b/lib/std/Io.zig @@ -2242,5 +2242,5 @@ pub fn unlockStderr(io: Io) void { pub fn environ(io: Io, name: []const u8) ?[]const u8 { _ = io; _ = name; - if (true) @panic("TODO"); + if (true) @panic("TODO Io.environ"); } diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index df1ce77fe57df7303fda1c4b68d6f8dce630227c..7f143970ae4121ead7777658b36ff67f92645a9e 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -12711,21 +12711,21 @@ fn scanEnviron(t: *Threaded) void { fn processReplace(userdata: ?*anyopaque, options: std.process.ReplaceOptions) std.process.ReplaceError { _ = userdata; _ = options; - @panic("TODO"); + @panic("TODO processReplace"); } fn processReplacePath(userdata: ?*anyopaque, dir: Dir, options: std.process.ReplaceOptions) std.process.ReplaceError { _ = userdata; _ = dir; _ = options; - @panic("TODO"); + @panic("TODO processReplacePath"); } fn processSpawnPath(userdata: ?*anyopaque, dir: Dir, options: process.SpawnOptions) process.SpawnError!process.Child { _ = userdata; _ = dir; _ = options; - @panic("TODO"); + @panic("TODO processSpawnPath"); } const processSpawn = switch (native_os) { diff --git a/lib/std/process/Environ.zig b/lib/std/process/Environ.zig index 5f5386f02d539a29c41d5fd3fd98051dedb7853a..ea89edd313285eb65e25750334d116a8d09427d6 100644 --- a/lib/std/process/Environ.zig +++ b/lib/std/process/Environ.zig @@ -150,7 +150,7 @@ pub const Map = struct { } pub fn contains(m: *const Map, key: []const u8) bool { - return m.contains(key); + return m.array_hash_map.contains(key); } /// If there is an entry with a matching key, it is deleted from the hash @@ -579,10 +579,11 @@ pub const CreateBlockOptions = struct { /// Creates a null-delimited environment variable block in the format expected /// by POSIX, from a different one. pub fn createBlock(existing: Environ, arena: Allocator, options: CreateBlockOptions) Allocator.Error![:null]?[*:0]u8 { + const existing_block: [*:null]const ?[*:0]const u8 = @ptrCast(existing.block); const existing_count, const contains_zig_progress = c: { var count: usize = 0; var contains = false; - while (existing.block[count]) |line| : (count += 1) { + while (existing_block[count]) |line| : (count += 1) { contains = contains or mem.eql(u8, mem.sliceTo(line, '='), "ZIG_PROGRESS"); } break :c .{ count, contains }; @@ -617,7 +618,7 @@ pub fn createBlock(existing: Environ, arena: Allocator, options: CreateBlockOpti i += 1; } - while (existing.block[existing_index]) |line| : (existing_index += 1) { + while (existing_block[existing_index]) |line| : (existing_index += 1) { if (mem.eql(u8, mem.sliceTo(line, '='), "ZIG_PROGRESS")) switch (zig_progress_action) { .add => unreachable, .delete => continue, -- 2.54.0 From 42988fc5f43abdbac5ef5abc7cb5f74f8bc55ad4 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 30 Dec 2025 21:53:56 -0800 Subject: [PATCH 04/60] std.process.Environ.Block: enhance type safety --- lib/std/Io/Threaded.zig | 3 ++- lib/std/process/Environ.zig | 22 +++++++++------------- lib/std/start.zig | 6 +++--- 3 files changed, 14 insertions(+), 17 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 7f143970ae4121ead7777658b36ff67f92645a9e..499ba76ef48c7fbdb2eb1706a8bd9624510a5605 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -12688,7 +12688,8 @@ fn scanEnviron(t: *Threaded) void { } } } else { - for (t.environ.block) |line| { + for (t.environ.block) |opt_line| { + const line = opt_line.?; var line_i: usize = 0; while (line[line_i] != 0 and line[line_i] != '=') : (line_i += 1) {} const key = line[0..line_i]; diff --git a/lib/std/process/Environ.zig b/lib/std/process/Environ.zig index ea89edd313285eb65e25750334d116a8d09427d6..f9edb910c4e6c7d585c7b7578f08c5c0d648211a 100644 --- a/lib/std/process/Environ.zig +++ b/lib/std/process/Environ.zig @@ -15,7 +15,7 @@ block: Block, pub const Block = switch (native_os) { .windows => []const u16, - else => []const [*:0]const u8, + else => [:null]const ?[*:0]const u8, }; pub const Map = struct { @@ -364,7 +364,8 @@ pub fn createMap(env: Environ, allocator: Allocator) CreateMapError!Map { } return result; } else { - for (env.block) |line| { + for (env.block) |opt_line| { + const line = opt_line.?; var line_i: usize = 0; while (line[line_i] != 0 and line[line_i] != '=') : (line_i += 1) {} const key = line[0..line_i]; @@ -579,15 +580,10 @@ pub const CreateBlockOptions = struct { /// Creates a null-delimited environment variable block in the format expected /// by POSIX, from a different one. pub fn createBlock(existing: Environ, arena: Allocator, options: CreateBlockOptions) Allocator.Error![:null]?[*:0]u8 { - const existing_block: [*:null]const ?[*:0]const u8 = @ptrCast(existing.block); - const existing_count, const contains_zig_progress = c: { - var count: usize = 0; - var contains = false; - while (existing_block[count]) |line| : (count += 1) { - contains = contains or mem.eql(u8, mem.sliceTo(line, '='), "ZIG_PROGRESS"); - } - break :c .{ count, contains }; - }; + const contains_zig_progress = for (existing.block) |opt_line| { + if (mem.eql(u8, mem.sliceTo(opt_line.?, '='), "ZIG_PROGRESS")) break true; + } else false; + const ZigProgressAction = enum { nothing, edit, delete, add }; const zig_progress_action: ZigProgressAction = a: { const fd = options.zig_progress_fd orelse break :a .nothing; @@ -600,7 +596,7 @@ pub fn createBlock(existing: Environ, arena: Allocator, options: CreateBlockOpti }; const envp_count: usize = c: { - var count: usize = existing_count; + var count: usize = existing.block.len; switch (zig_progress_action) { .add => count += 1, .delete => count -= 1, @@ -618,7 +614,7 @@ pub fn createBlock(existing: Environ, arena: Allocator, options: CreateBlockOpti i += 1; } - while (existing_block[existing_index]) |line| : (existing_index += 1) { + while (existing.block[existing_index]) |line| : (existing_index += 1) { if (mem.eql(u8, mem.sliceTo(line, '='), "ZIG_PROGRESS")) switch (zig_progress_action) { .add => unreachable, .delete => continue, diff --git a/lib/std/start.zig b/lib/std/start.zig index 51a498c554261fb2e48fc80ce49bac9c07030fc9..4c2d47b42fcef5d14e9c481fe34e078377a07fe2 100644 --- a/lib/std/start.zig +++ b/lib/std/start.zig @@ -559,7 +559,7 @@ fn posixCallMainAndExit(argc_argv_ptr: [*]usize) callconv(.c) noreturn { const envp_optional: [*:null]?[*:0]u8 = @ptrCast(@alignCast(argv + argc + 1)); var envp_count: usize = 0; while (envp_optional[envp_count]) |_| : (envp_count += 1) {} - const envp = @as([*][*:0]u8, @ptrCast(envp_optional))[0..envp_count]; + const envp = envp_optional[0..envp_count :null]; // Find the beginning of the auxiliary vector const auxv: [*]elf.Auxv = @ptrCast(@alignCast(envp.ptr + envp_count + 1)); @@ -668,7 +668,7 @@ fn expandStackSize(phdrs: []elf.Phdr) void { } } -inline fn callMainWithArgs(argc: usize, argv: [*][*:0]u8, envp: [][*:0]u8) u8 { +inline fn callMainWithArgs(argc: usize, argv: [*][*:0]u8, envp: [:null]?[*:0]u8) u8 { if (std.Options.debug_threaded_io) |t| { if (@sizeOf(std.Io.Threaded.Argv0) != 0) t.argv0.value = argv[0]; t.environ = .{ .block = envp }; @@ -680,7 +680,7 @@ inline fn callMainWithArgs(argc: usize, argv: [*][*:0]u8, envp: [][*:0]u8) u8 { fn main(c_argc: c_int, c_argv: [*][*:0]c_char, c_envp: [*:null]?[*:0]c_char) callconv(.c) c_int { var env_count: usize = 0; while (c_envp[env_count] != null) : (env_count += 1) {} - const envp = @as([*][*:0]u8, @ptrCast(c_envp))[0..env_count]; + const envp = c_envp[0..env_count :null]; if (builtin.os.tag == .linux) { const at_phdr = std.c.getauxval(elf.AT_PHDR); -- 2.54.0 From 384bfc5f99d0d46521d62e09858facc2edd3bc74 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 30 Dec 2025 22:21:53 -0800 Subject: [PATCH 05/60] std.Progress: go through Io interface for parent IPC mechanism and fix start code --- lib/std/Io.zig | 5 +++-- lib/std/Io/Threaded.zig | 24 ++++++++++++++++++++++++ lib/std/Progress.zig | 23 +++++++++++------------ lib/std/start.zig | 6 +++--- 4 files changed, 41 insertions(+), 17 deletions(-) diff --git a/lib/std/Io.zig b/lib/std/Io.zig index f7de23aa5477742da0b231922b24e55b920cd825..2f1a180fc5d21a2e4bc49b05934b9d5a2db94504 100644 --- a/lib/std/Io.zig +++ b/lib/std/Io.zig @@ -724,6 +724,8 @@ pub const VTable = struct { childWait: *const fn (?*anyopaque, *std.process.Child) std.process.Child.WaitError!std.process.Child.Term, childKill: *const fn (?*anyopaque, *std.process.Child) void, + progressParentFile: *const fn (?*anyopaque) std.Progress.ParentFileError!File, + now: *const fn (?*anyopaque, Clock) Clock.Error!Timestamp, sleep: *const fn (?*anyopaque, Timeout) SleepError!void, @@ -2241,6 +2243,5 @@ pub fn unlockStderr(io: Io) void { pub fn environ(io: Io, name: []const u8) ?[]const u8 { _ = io; - _ = name; - if (true) @panic("TODO Io.environ"); + std.debug.panic("TODO: environ query: {s}", .{name}); } diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 499ba76ef48c7fbdb2eb1706a8bd9624510a5605..7333e9bb103bf551c358e653d719c582f59b1a42 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -82,6 +82,8 @@ pub const Environ = struct { exist: Exist = .{}, /// Protected by `mutex`. Memoized based on `block`. string: String = .{}, + /// ZIG_PROGRESS + zig_progress_handle: std.Progress.ParentFileError!u31 = error.EnvironmentVariableMissing, /// Protected by `mutex`. Tracks the problem, if any, that occurred when /// trying to scan environment variables. /// @@ -1408,6 +1410,8 @@ pub fn io(t: *Threaded) Io { .childWait = childWait, // TODO audit for cancelation and unreachable .childKill = childKill, // TODO audit for cancelation and unreachable + .progressParentFile = progressParentFile, + .now = now, .sleep = sleep, @@ -1552,6 +1556,8 @@ pub fn ioBasic(t: *Threaded) Io { .childWait = childWait, .childKill = childKill, + .progressParentFile = progressParentFile, + .now = now, .sleep = sleep, @@ -12685,6 +12691,8 @@ fn scanEnviron(t: *Threaded) void { t.environ.exist.CLICOLOR_FORCE = true; } else if (@hasField(Environ.String, "PATH") and std.mem.eql(u8, key, "PATH")) { t.environ.string.PATH = value; + } else if (std.mem.eql(u8, key, "ZIG_PROGRESS")) { + t.environ.zig_progress_handle = std.fmt.parseInt(u31, value, 10) catch error.UnrecognizedFormat; } } } else { @@ -12704,6 +12712,8 @@ fn scanEnviron(t: *Threaded) void { t.environ.exist.CLICOLOR_FORCE = true; } else if (@hasField(Environ.String, "PATH") and std.mem.eql(u8, key, "PATH")) { t.environ.string.PATH = value; + } else if (std.mem.eql(u8, key, "ZIG_PROGRESS")) { + t.environ.zig_progress_handle = std.fmt.parseInt(u31, value, 10) catch error.UnrecognizedFormat; } } } @@ -14343,6 +14353,20 @@ fn windowsMakeAsyncPipe(rd: *?windows.HANDLE, wr: *?windows.HANDLE, sattr: *cons var pipe_name_counter = std.atomic.Value(u32).init(1); +fn progressParentFile(userdata: ?*anyopaque) std.Progress.ParentFileError!File { + const t: *Threaded = @ptrCast(@alignCast(userdata)); + + t.scanEnviron(); + + const int = try t.environ.zig_progress_handle; + + return .{ .handle = switch (@typeInfo(Io.File.Handle)) { + .int => int, + .pointer => @ptrFromInt(int), + else => return error.UnsupportedOperation, + } }; +} + test { _ = @import("Threaded/test.zig"); } diff --git a/lib/std/Progress.zig b/lib/std/Progress.zig index 94fd6b47a053d217a234f11de66b6ab9ec8eec1b..75ef13ca91f16307ca028977f5db6e0144996e77 100644 --- a/lib/std/Progress.zig +++ b/lib/std/Progress.zig @@ -422,7 +422,7 @@ pub const StartFailure = union(enum) { unstarted, spawn_ipc_worker: error{ConcurrencyUnavailable}, spawn_update_worker: error{ConcurrencyUnavailable}, - parse_env_var: error{ InvalidCharacter, Overflow }, + parent_ipc: error{ UnsupportedOperation, UnrecognizedFormat }, }; const node_storage_buffer_len = 83; @@ -446,6 +446,12 @@ const noop_impl = builtin.single_threaded or switch (builtin.os.tag) { else => false, }; +pub const ParentFileError = error{ + UnsupportedOperation, + EnvironmentVariableMissing, + UnrecognizedFormat, +}; + /// Initializes a global Progress instance. /// /// Asserts there is only one global Progress instance. @@ -476,20 +482,13 @@ pub fn start(io: Io, options: Options) Node { global_progress.io = io; - if (std.process.Environ.parseInt(io, "ZIG_PROGRESS", u31, 10)) |ipc_fd| { - global_progress.update_worker = io.concurrent(ipcThreadRun, .{ - io, - @as(Io.File, .{ .handle = switch (@typeInfo(Io.File.Handle)) { - .int => ipc_fd, - .pointer => @ptrFromInt(ipc_fd), - else => @compileError("unsupported fd_t of " ++ @typeName(Io.File.Handle)), - } }), - }) catch |err| { + if (io.vtable.progressParentFile(io.userdata)) |ipc_file| { + global_progress.update_worker = io.concurrent(ipcThreadRun, .{ io, ipc_file }) catch |err| { global_progress.start_failure = .{ .spawn_ipc_worker = err }; return Node.none; }; } else |env_err| switch (env_err) { - error.EnvironmentVariableNotFound => { + error.EnvironmentVariableMissing => { if (options.disable_printing) { return Node.none; } @@ -535,7 +534,7 @@ pub fn start(io: Io, options: Options) Node { } }, else => |e| { - global_progress.start_failure = .{ .parse_env_var = e }; + global_progress.start_failure = .{ .parent_ipc = e }; return Node.none; }, } diff --git a/lib/std/start.zig b/lib/std/start.zig index 4c2d47b42fcef5d14e9c481fe34e078377a07fe2..e0a910f753b1e688a562d826b9eb5b3f59ac8204 100644 --- a/lib/std/start.zig +++ b/lib/std/start.zig @@ -733,11 +733,11 @@ inline fn callMain(args: std.process.Args.Vector, environ: std.process.Environ.B var threaded: std.Io.Threaded = .init(gpa, .{ .argv0 = if (@sizeOf(std.Io.Threaded.Argv0) != 0) .{ .value = args[0] } else .{}, - .environ = environ, + .environ = .{ .block = environ }, }); defer threaded.deinit(); - var env_map = environ.getEnvMap(gpa) catch |err| + var env_map = std.process.Environ.createMap(.{ .block = environ }, gpa) catch |err| std.process.fatal("failed to parse environment variables: {t}", .{err}); defer env_map.deinit(); @@ -749,7 +749,7 @@ inline fn callMain(args: std.process.Args.Vector, environ: std.process.Environ.B .arena = &arena_allocator, .gpa = gpa, .io = threaded.io(), - .env_map = env_map, + .env_map = &env_map, })); } -- 2.54.0 From 3e6d6150d98658e6c46ad5c378f90fb628f97d2a Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 31 Dec 2025 15:06:13 -0800 Subject: [PATCH 06/60] std.process.Environ: fix compile errors on POSIX --- lib/compiler/test_runner.zig | 5 +- lib/std/Io/Threaded.zig | 4 +- lib/std/dynamic_library.zig | 28 ++- lib/std/posix/test.zig | 7 +- lib/std/process/Args.zig | 2 +- lib/std/process/Environ.zig | 400 +++++++++++++++++++---------------- lib/std/zig.zig | 2 + src/main.zig | 44 ++-- 8 files changed, 266 insertions(+), 226 deletions(-) diff --git a/lib/compiler/test_runner.zig b/lib/compiler/test_runner.zig index cd3c27412b055fb53bcabe3942f4fdff7d0065e7..9219c0040b9c0f9bb189e674e82b3496a9177c6c 100644 --- a/lib/compiler/test_runner.zig +++ b/lib/compiler/test_runner.zig @@ -29,7 +29,7 @@ const need_simple = switch (builtin.zig_backend) { else => false, }; -pub fn main() void { +pub fn main(init: std.process.Init.Minimal) void { @disableInstrumentation(); if (builtin.cpu.arch.isSpirV()) { @@ -41,8 +41,7 @@ pub fn main() void { return mainSimple() catch @panic("test failure\n"); } - const args = std.process.argsAlloc(fba.allocator()) catch - @panic("unable to parse command line args"); + const args = init.args.toSlice(fba.allocator()) catch @panic("unable to parse command line args"); var listen = false; var opt_cache_dir: ?[]const u8 = null; diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 7333e9bb103bf551c358e653d719c582f59b1a42..8bb3201bb898fb9829ae3027e55cbc35ce768c26 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -12836,11 +12836,11 @@ fn processSpawnPosix(userdata: ?*anyopaque, options: process.SpawnOptions) proce const envp: [*:null]const ?[*:0]const u8 = m: { const prog_fd: i32 = if (prog_pipe[1] == -1) -1 else prog_fileno; if (options.env_map) |env_map| { - break :m (try env_map.createBlock(arena, .{ + break :m (try env_map.createBlockPosix(arena, .{ .zig_progress_fd = prog_fd, })).ptr; } - break :m (try process.Environ.createBlock(.{ .block = t.environ.block }, arena, .{ + break :m (try process.Environ.createBlockPosix(.{ .block = t.environ.block }, arena, .{ .zig_progress_fd = prog_fd, })).ptr; }; diff --git a/lib/std/dynamic_library.zig b/lib/std/dynamic_library.zig index ccd60ecd97f12dea8912ecb69dd51e2c1a396975..05a2562e29b0312ec9089b7b1bf2349a5d671a08 100644 --- a/lib/std/dynamic_library.zig +++ b/lib/std/dynamic_library.zig @@ -31,12 +31,20 @@ pub const DynLib = struct { /// Trusts the file. Malicious file will be able to execute arbitrary code. pub fn open(path: []const u8) Error!DynLib { - return .{ .inner = try InnerType.open(path) }; + if (InnerType == ElfDynLib) { + return .{ .inner = try InnerType.open(path, null) }; + } else { + return .{ .inner = try InnerType.open(path) }; + } } /// Trusts the file. Malicious file will be able to execute arbitrary code. pub fn openZ(path_c: [*:0]const u8) Error!DynLib { - return .{ .inner = try InnerType.openZ(path_c) }; + if (InnerType == ElfDynLib) { + return .{ .inner = try InnerType.openZ(path_c, null) }; + } else { + return .{ .inner = try InnerType.openZ(path_c) }; + } } /// Trusts the file. @@ -197,7 +205,7 @@ pub const ElfDynLib = struct { // - DT_RPATH of the calling binary is not used as a search path // - DT_RUNPATH of the calling binary is not used as a search path // - /etc/ld.so.cache is not read - fn resolveFromName(io: Io, path_or_name: []const u8) !posix.fd_t { + fn resolveFromName(io: Io, path_or_name: []const u8, LD_LIBRARY_PATH: ?[]const u8) !posix.fd_t { // If filename contains a slash ("/"), then it is interpreted as a (relative or absolute) pathname if (std.mem.findScalarPos(u8, path_or_name, 0, '/')) |_| { return posix.open(path_or_name, .{ .ACCMODE = .RDONLY, .CLOEXEC = true }, 0); @@ -207,7 +215,7 @@ pub const ElfDynLib = struct { if (std.os.linux.geteuid() == std.os.linux.getuid() and std.os.linux.getegid() == std.os.linux.getgid()) { - if (posix.getenvZ("LD_LIBRARY_PATH")) |ld_library_path| { + if (LD_LIBRARY_PATH) |ld_library_path| { if (resolveFromSearchPath(io, ld_library_path, path_or_name, ':')) |fd| { return fd; } @@ -221,10 +229,10 @@ pub const ElfDynLib = struct { } /// Trusts the file. Malicious file will be able to execute arbitrary code. - pub fn open(path: []const u8) Error!ElfDynLib { + pub fn open(path: []const u8, LD_LIBRARY_PATH: ?[]const u8) Error!ElfDynLib { const io = std.Options.debug_io; - const fd = try resolveFromName(io, path); + const fd = try resolveFromName(io, path, LD_LIBRARY_PATH); defer posix.close(fd); const file: Io.File = .{ .handle = fd }; @@ -371,8 +379,8 @@ pub const ElfDynLib = struct { } /// Trusts the file. Malicious file will be able to execute arbitrary code. - pub fn openZ(path_c: [*:0]const u8) Error!ElfDynLib { - return open(mem.sliceTo(path_c, 0)); + pub fn openZ(path_c: [*:0]const u8, LD_LIBRARY_PATH: ?[]const u8) Error!ElfDynLib { + return open(mem.sliceTo(path_c, 0), LD_LIBRARY_PATH); } /// Trusts the file @@ -554,8 +562,8 @@ fn checkver(def_arg: *elf.Verdef, vsym_arg: elf.Versym, vername: []const u8, str test "ElfDynLib" { if (native_os != .linux) return error.SkipZigTest; - try testing.expectError(error.FileNotFound, ElfDynLib.open("invalid_so.so")); - try testing.expectError(error.FileNotFound, ElfDynLib.openZ("invalid_so.so")); + try testing.expectError(error.FileNotFound, ElfDynLib.open("invalid_so.so", null)); + try testing.expectError(error.FileNotFound, ElfDynLib.openZ("invalid_so.so", null)); } /// Separated to avoid referencing `WindowsDynLib`, because its field types may not diff --git a/lib/std/posix/test.zig b/lib/std/posix/test.zig index 6fdb980b1bb93ab2db7d1a404cb996c7cf3c24ac..d95789c254115e6a0ed965bae7121307e0cf401f 100644 --- a/lib/std/posix/test.zig +++ b/lib/std/posix/test.zig @@ -140,11 +140,6 @@ test "pipe" { posix.close(fds[0]); } -test "argsAlloc" { - const args = try std.process.argsAlloc(std.testing.allocator); - std.process.argsFree(std.testing.allocator, args); -} - test "memfd_create" { const io = testing.io; @@ -473,7 +468,7 @@ test "getpid" { if (native_os == .wasi) return error.SkipZigTest; if (native_os == .windows) return error.SkipZigTest; - try expect(posix.getpid() != 0); + try expect(posix.system.getpid() != 0); } test "getppid" { diff --git a/lib/std/process/Args.zig b/lib/std/process/Args.zig index 0eb5d813090c0cc498d3d11b80638524dde55805..60bd3c984893b33179420aaa0cd5d58ad81fdd69 100644 --- a/lib/std/process/Args.zig +++ b/lib/std/process/Args.zig @@ -6,7 +6,7 @@ const native_os = builtin.os.tag; const std = @import("../std.zig"); const Allocator = std.mem.Allocator; const assert = std.debug.assert; -const testing = std.debug.testing; +const testing = std.testing; vector: Vector, diff --git a/lib/std/process/Environ.zig b/lib/std/process/Environ.zig index f9edb910c4e6c7d585c7b7578f08c5c0d648211a..831ba5a4680982855a929f7c549cc570bc8ebba9 100644 --- a/lib/std/process/Environ.zig +++ b/lib/std/process/Environ.zig @@ -6,15 +6,25 @@ const native_os = builtin.os.tag; const std = @import("../std.zig"); const Allocator = std.mem.Allocator; const assert = std.debug.assert; -const testing = std.debug.testing; +const testing = std.testing; const unicode = std.unicode; const posix = std.posix; const mem = std.mem; +/// Unmodified, unprocessed data provided by the operating system. +/// +/// On Windows this might point to memory in the PEB. +/// +/// On WASI without libc, this is void because the environment has to be +/// queried and heap-allocated at runtime. block: Block, pub const Block = switch (native_os) { .windows => []const u16, + .wasi => switch (builtin.link_libc) { + false => void, + true => [:null]const ?[*:0]const u8, + }, else => [:null]const ?[*:0]const u8, }; @@ -89,11 +99,11 @@ pub const Map = struct { self.* = undefined; } - pub fn keys(m: *Map) [][]const u8 { + pub fn keys(m: *const Map) [][]const u8 { return m.array_hash_map.keys(); } - pub fn values(m: *Map) [][]const u8 { + pub fn values(m: *const Map) [][]const u8 { return m.array_hash_map.values(); } @@ -214,10 +224,10 @@ pub const Map = struct { /// Creates a null-delimited environment variable block in the format /// expected by POSIX, from a hash map plus options. - pub fn createBlock( + pub fn createBlockPosix( map: *const Map, arena: Allocator, - options: CreateBlockOptions, + options: CreateBlockPosixOptions, ) Allocator.Error![:null]?[*:0]u8 { const ZigProgressAction = enum { nothing, edit, delete, add }; const zig_progress_action: ZigProgressAction = a: { @@ -273,6 +283,46 @@ pub const Map = struct { assert(i == envp_count); return envp_buf; } + + /// Caller must free result. + pub fn createBlockWindows(map: *const Map, gpa: Allocator) Allocator.Error![]u16 { + // count bytes needed + const max_chars_needed = x: { + // Only need 2 trailing NUL code units for an empty environment + var max_chars_needed: usize = if (map.count() == 0) 2 else 1; + var it = map.iterator(); + while (it.next()) |pair| { + // +1 for '=' + // +1 for null byte + max_chars_needed += pair.key_ptr.len + pair.value_ptr.len + 2; + } + break :x max_chars_needed; + }; + const result = try gpa.alloc(u16, max_chars_needed); + errdefer gpa.free(result); + + var it = map.iterator(); + var i: usize = 0; + while (it.next()) |pair| { + i += try unicode.wtf8ToWtf16Le(result[i..], pair.key_ptr.*); + result[i] = '='; + i += 1; + i += try unicode.wtf8ToWtf16Le(result[i..], pair.value_ptr.*); + result[i] = 0; + i += 1; + } + result[i] = 0; + i += 1; + // An empty environment is a special case that requires a redundant + // NUL terminator. CreateProcess will read the second code unit even + // though theoretically the first should be enough to recognize that the + // environment is empty (see https://nullprogram.com/blog/2023/08/23/) + if (map.count() == 0) { + result[i] = 0; + i += 1; + } + return try gpa.realloc(result, i); + } }; pub const CreateMapError = error{ @@ -380,162 +430,131 @@ pub fn createMap(env: Environ, allocator: Allocator) CreateMapError!Map { } } -test createMap { - var env = try createMap(testing.allocator); - defer env.deinit(); -} - -pub const GetEnvVarOwnedError = error{ +pub const ContainsError = error{ OutOfMemory, - EnvironmentVariableNotFound, - - /// On Windows, environment variable keys provided by the user must be valid WTF-8. - /// https://wtf-8.codeberg.page/ + /// On Windows, environment variable keys provided by the user must be + /// valid [WTF-8](https://wtf-8.codeberg.page/). This error is unreachable + /// if the key is statically known to be valid. InvalidWtf8, + /// WASI-only. `environ_sizes_get` or `environ_get` failed for an + /// unexpected reason. + Unexpected, }; -/// Caller must free returned memory. /// On Windows, if `key` is not valid [WTF-8](https://wtf-8.codeberg.page/), /// then `error.InvalidWtf8` is returned. -/// On Windows, the value is encoded as [WTF-8](https://wtf-8.codeberg.page/). -/// On other platforms, the value is an opaque sequence of bytes with no particular encoding. -pub fn getEnvVarOwned(allocator: Allocator, key: []const u8) GetEnvVarOwnedError![]u8 { - if (native_os == .windows) { - const result_w = blk: { - var stack_alloc = std.heap.stackFallback(256 * @sizeOf(u16), allocator); - const stack_allocator = stack_alloc.get(); - const key_w = try unicode.wtf8ToWtf16LeAllocZ(stack_allocator, key); - defer stack_allocator.free(key_w); +/// +/// See also: +/// * `createMap` +/// * `containsConstant` +/// * `containsUnempty` +pub fn contains(environ: Environ, gpa: Allocator, key: []const u8) ContainsError!bool { + var map = try createMap(environ, gpa); + defer map.deinit(); + return map.contains(key); +} - break :blk getenvW(key_w) orelse return error.EnvironmentVariableNotFound; - }; - // wtf16LeToWtf8Alloc can only fail with OutOfMemory - return unicode.wtf16LeToWtf8Alloc(allocator, result_w); - } else if (native_os == .wasi and !builtin.link_libc) { - var envmap = createMap(allocator) catch return error.OutOfMemory; - defer envmap.deinit(); - const val = envmap.get(key) orelse return error.EnvironmentVariableNotFound; - return allocator.dupe(u8, val); - } else { - const result = posix.getenv(key) orelse return error.EnvironmentVariableNotFound; - return allocator.dupe(u8, result); - } +/// On Windows, if `key` is not valid [WTF-8](https://wtf-8.codeberg.page/), +/// then `error.InvalidWtf8` is returned. +/// +/// See also: +/// * `createMap` +/// * `containsUnemptyConstant` +/// * `contains` +pub fn containsUnempty(environ: Environ, gpa: Allocator, key: []const u8) ContainsError!bool { + var map = try createMap(environ, gpa); + defer map.deinit(); + const value = map.get(key) orelse return false; + return value.len != 0; } -/// On Windows, `key` must be valid WTF-8. -pub inline fn hasEnvVarConstant(comptime key: []const u8) bool { +/// This function is unavailable on WASI without libc due to the memory +/// allocation requirement. +/// +/// On Windows, `key` must be valid [WTF-8](https://wtf-8.codeberg.page/), +/// +/// See also: +/// * `contains` +/// * `containsUnemptyConstant` +/// * `createMap` +pub inline fn containsConstant(environ: Environ, comptime key: []const u8) bool { if (native_os == .windows) { const key_w = comptime unicode.wtf8ToWtf16LeStringLiteral(key); - return getenvW(key_w) != null; - } else if (native_os == .wasi and !builtin.link_libc) { - return false; + return getWindows(environ, key_w) != null; } else { - return posix.getenv(key) != null; + return getPosix(environ, key) != null; } } -/// On Windows, `key` must be valid WTF-8. -pub inline fn hasNonEmptyEnvVarConstant(comptime key: []const u8) bool { +/// This function is unavailable on WASI without libc due to the memory +/// allocation requirement. +/// +/// On Windows, `key` must be valid [WTF-8](https://wtf-8.codeberg.page/), +/// +/// See also: +/// * `containsUnempty` +/// * `containsConstant` +/// * `createMap` +pub inline fn containsUnemptyConstant(environ: Environ, comptime key: []const u8) bool { if (native_os == .windows) { const key_w = comptime unicode.wtf8ToWtf16LeStringLiteral(key); - const value = getenvW(key_w) orelse return false; + const value = getWindows(environ, key_w) orelse return false; return value.len != 0; - } else if (native_os == .wasi and !builtin.link_libc) { - return false; } else { - const value = posix.getenv(key) orelse return false; + const value = getPosix(environ, key) orelse return false; return value.len != 0; } } -pub const ParseIntError = std.fmt.ParseIntError || error{EnvironmentVariableNotFound}; - -/// Parses an environment variable as an integer. +/// This function is unavailable on WASI without libc due to the memory +/// allocation requirement. /// -/// On Windows, `key` must be valid WTF-8. -pub fn parseInt(io: std.Io, key: []const u8, comptime I: type, base: u8) ParseIntError!I { - const text = io.environ(key) orelse return error.EnvironmentVariableNotFound; - return std.fmt.parseInt(I, text, base); -} - -pub const HasEnvVarError = error{ - OutOfMemory, - - /// On Windows, environment variable keys provided by the user must be valid WTF-8. - /// https://wtf-8.codeberg.page/ - InvalidWtf8, -}; - -/// On Windows, if `key` is not valid [WTF-8](https://wtf-8.codeberg.page/), -/// then `error.InvalidWtf8` is returned. -pub fn hasEnvVar(allocator: Allocator, key: []const u8) HasEnvVarError!bool { - if (native_os == .windows) { - var stack_alloc = std.heap.stackFallback(256 * @sizeOf(u16), allocator); - const stack_allocator = stack_alloc.get(); - const key_w = try unicode.wtf8ToWtf16LeAllocZ(stack_allocator, key); - defer stack_allocator.free(key_w); - return getenvW(key_w) != null; - } else if (native_os == .wasi and !builtin.link_libc) { - var envmap = createMap(allocator) catch return error.OutOfMemory; - defer envmap.deinit(); - return envmap.getPtr(key) != null; - } else { - return posix.getenv(key) != null; - } -} +/// See also: +/// * `getWindows` +/// * `createMap` +pub fn getPosix(environ: Environ, key: []const u8) ?[:0]const u8 { + if (mem.findScalar(u8, key, '=') != null) return null; + for (environ.block) |opt_line| { + const line = opt_line.?; + var line_i: usize = 0; + while (line[line_i] != 0) : (line_i += 1) { + if (line_i == key.len) break; + if (line[line_i] != key[line_i]) break; + } + if ((line_i != key.len) or (line[line_i] != '=')) continue; -/// On Windows, if `key` is not valid [WTF-8](https://wtf-8.codeberg.page/), -/// then `error.InvalidWtf8` is returned. -pub fn hasNonEmptyEnvVar(allocator: Allocator, key: []const u8) HasEnvVarError!bool { - if (native_os == .windows) { - var stack_alloc = std.heap.stackFallback(256 * @sizeOf(u16), allocator); - const stack_allocator = stack_alloc.get(); - const key_w = try unicode.wtf8ToWtf16LeAllocZ(stack_allocator, key); - defer stack_allocator.free(key_w); - const value = getenvW(key_w) orelse return false; - return value.len != 0; - } else if (native_os == .wasi and !builtin.link_libc) { - var envmap = createMap(allocator) catch return error.OutOfMemory; - defer envmap.deinit(); - const value = envmap.getPtr(key) orelse return false; - return value.len != 0; - } else { - const value = posix.getenv(key) orelse return false; - return value.len != 0; + return mem.sliceTo(line + line_i + 1, 0); } + return null; } -/// Windows-only. Get an environment variable with a null-terminated, WTF-16 encoded name. -/// The returned slice points to memory in the PEB. +/// Windows-only. Get an environment variable with a null-terminated, WTF-16 +/// encoded name. /// -/// This function performs a Unicode-aware case-insensitive lookup using RtlEqualUnicodeString. +/// This function performs a Unicode-aware case-insensitive lookup using +/// RtlEqualUnicodeString. /// /// See also: -/// * `std.posix.getenv` /// * `createMap` -/// * `getEnvVarOwned` -/// * `hasEnvVarConstant` -/// * `hasEnvVar` -pub fn getenvW(key: [*:0]const u16) ?[:0]const u16 { - if (native_os != .windows) { - @compileError("Windows-only"); - } +/// * `containsConstant` +/// * `contains` +pub fn getWindows(environ: Environ, key: [*:0]const u16) ?[:0]const u16 { + comptime assert(native_os == .windows); + + // '=' anywhere but the start makes this an invalid environment variable name. const key_slice = mem.sliceTo(key, 0); - // '=' anywhere but the start makes this an invalid environment variable name - if (key_slice.len > 0 and std.mem.findScalar(u16, key_slice[1..], '=') != null) { - return null; - } - const ptr = std.os.windows.peb().ProcessParameters.Environment; + if (key_slice.len > 0 and mem.findScalar(u16, key_slice[1..], '=') != null) return null; + var i: usize = 0; - while (ptr[i] != 0) { - const key_value = mem.sliceTo(ptr[i..], 0); + while (environ.block[i] != 0) { + const key_value = mem.sliceTo(environ.block[i..], 0); // There are some special environment variables that start with =, // so we need a special case to not treat = as a key/value separator // if it's the first character. // https://devblogs.microsoft.com/oldnewthing/20100506-00/?p=14133 const equal_search_start: usize = if (key_value[0] == '=') 1 else 0; - const equal_index = std.mem.findScalarPos(u16, key_value, equal_search_start, '=') orelse { + const equal_index = mem.findScalarPos(u16, key_value, equal_search_start, '=') orelse { // This is enforced by CreateProcess. // If violated, CreateProcess will fail with INVALID_PARAMETER. unreachable; // must contain a = @@ -552,25 +571,35 @@ pub fn getenvW(key: [*:0]const u16) ?[:0]const u16 { return null; } -test getEnvVarOwned { - try testing.expectError( - error.EnvironmentVariableNotFound, - getEnvVarOwned(std.testing.allocator, "BADENV"), - ); -} - -test hasEnvVarConstant { - if (native_os == .wasi and !builtin.link_libc) return error.SkipZigTest; - - try testing.expect(!hasEnvVarConstant("BADENV")); -} +pub const GetAllocError = error{ + OutOfMemory, + EnvironmentVariableMissing, + /// On Windows, environment variable keys provided by the user must be + /// valid [WTF-8](https://wtf-8.codeberg.page/). This error is unreachable + /// if the key is statically known to be valid. + InvalidWtf8, +}; -test hasEnvVar { - const has_env = try hasEnvVar(std.testing.allocator, "BADENV"); - try testing.expect(!has_env); +/// Caller owns returned memory. +/// +/// On Windows: +/// * If `key` is not valid [WTF-8](https://wtf-8.codeberg.page/), then +/// `error.InvalidWtf8` is returned. +/// * The returned value is encoded as [WTF-8](https://wtf-8.codeberg.page/). +/// +/// On other platforms, the value is an opaque sequence of bytes with no +/// particular encoding. +/// +/// See also: +/// * `createMap` +pub fn getAlloc(environ: Environ, gpa: Allocator, key: []const u8) GetAllocError![]u8 { + var map = createMap(environ, gpa) catch return error.OutOfMemory; + defer map.deinit(); + const val = map.get(key) orelse return error.EnvironmentVariableMissing; + return gpa.dupe(u8, val); } -pub const CreateBlockOptions = struct { +pub const CreateBlockPosixOptions = struct { /// `null` means to leave the `ZIG_PROGRESS` environment variable unmodified. /// If non-null, negative means to remove the environment variable, and >= 0 /// means to provide it with the given integer. @@ -579,7 +608,11 @@ pub const CreateBlockOptions = struct { /// Creates a null-delimited environment variable block in the format expected /// by POSIX, from a different one. -pub fn createBlock(existing: Environ, arena: Allocator, options: CreateBlockOptions) Allocator.Error![:null]?[*:0]u8 { +pub fn createBlockPosix( + existing: Environ, + arena: Allocator, + options: CreateBlockPosixOptions, +) Allocator.Error![:null]?[*:0]u8 { const contains_zig_progress = for (existing.block) |opt_line| { if (mem.eql(u8, mem.sliceTo(opt_line.?, '='), "ZIG_PROGRESS")) break true; } else false; @@ -646,7 +679,7 @@ test "Map.createBlock" { var arena = std.heap.ArenaAllocator.init(allocator); defer arena.deinit(); - const environ = try envmap.createBlock(arena.allocator(), .{}); + const environ = try envmap.createBlockPosix(arena.allocator(), .{}); try testing.expectEqual(@as(usize, 5), environ.len); @@ -665,46 +698,6 @@ test "Map.createBlock" { } } -/// Caller must free result. -pub fn createWindowsEnvBlock(allocator: mem.Allocator, env_map: *const Map) ![]u16 { - // count bytes needed - const max_chars_needed = x: { - // Only need 2 trailing NUL code units for an empty environment - var max_chars_needed: usize = if (env_map.count() == 0) 2 else 1; - var it = env_map.iterator(); - while (it.next()) |pair| { - // +1 for '=' - // +1 for null byte - max_chars_needed += pair.key_ptr.len + pair.value_ptr.len + 2; - } - break :x max_chars_needed; - }; - const result = try allocator.alloc(u16, max_chars_needed); - errdefer allocator.free(result); - - var it = env_map.iterator(); - var i: usize = 0; - while (it.next()) |pair| { - i += try unicode.wtf8ToWtf16Le(result[i..], pair.key_ptr.*); - result[i] = '='; - i += 1; - i += try unicode.wtf8ToWtf16Le(result[i..], pair.value_ptr.*); - result[i] = 0; - i += 1; - } - result[i] = 0; - i += 1; - // An empty environment is a special case that requires a redundant - // NUL terminator. CreateProcess will read the second code unit even - // though theoretically the first should be enough to recognize that the - // environment is empty (see https://nullprogram.com/blog/2023/08/23/) - if (env_map.count() == 0) { - result[i] = 0; - i += 1; - } - return try allocator.realloc(result, i); -} - test Map { var env = Map.init(testing.allocator); defer env.deinit(); @@ -733,13 +726,14 @@ test Map { var it = env.iterator(); var count: Map.Size = 0; while (it.next()) |entry| { - const is_an_expected_name = std.mem.eql(u8, "SOMETHING_NEW", entry.key_ptr.*) or std.mem.eql(u8, "SOMETHING_NEW_AND_LONGER", entry.key_ptr.*); + const is_an_expected_name = mem.eql(u8, "SOMETHING_NEW", entry.key_ptr.*) or mem.eql(u8, "SOMETHING_NEW_AND_LONGER", entry.key_ptr.*); try testing.expect(is_an_expected_name); count += 1; } try testing.expectEqual(@as(Map.Size, 2), count); - env.remove("SOMETHING_NEW"); + try testing.expect(env.swapRemove("SOMETHING_NEW")); + try testing.expect(!env.swapRemove("SOMETHING_NEW")); try testing.expect(env.get("SOMETHING_NEW") == null); try testing.expectEqual(@as(Map.Size, 1), env.count()); @@ -751,7 +745,7 @@ test Map { // and WTF-8 that's not valid UTF-8 const wtf8_with_surrogate_pair = try unicode.wtf16LeToWtf8Alloc(testing.allocator, &[_]u16{ - std.mem.nativeToLittle(u16, 0xD83D), // unpaired high surrogate + mem.nativeToLittle(u16, 0xD83D), // unpaired high surrogate }); defer testing.allocator.free(wtf8_with_surrogate_pair); @@ -759,3 +753,45 @@ test Map { try testing.expectEqualSlices(u8, wtf8_with_surrogate_pair, env.get(wtf8_with_surrogate_pair).?); } } + +test "convert from Environ to Map and back again" { + const gpa = testing.allocator; + + var map: Map = .init(gpa); + defer map.deinit(); + try map.put("FOO", "BAR"); + try map.put("A", ""); + try map.put("", "B"); + + var arena_allocator = std.heap.ArenaAllocator.init(gpa); + defer arena_allocator.deinit(); + const arena = arena_allocator.allocator(); + + const environ: Environ = switch (native_os) { + .windows => .{ .block = try map.createBlockWindows(arena) }, + .wasi => if (!builtin.libc) return error.SkipZigTest, + else => .{ .block = try map.createBlockPosix(arena, .{}) }, + }; + + try testing.expectEqual(true, environ.contains(gpa, "FOO")); + try testing.expectEqual(false, environ.contains(gpa, "BAR")); + try testing.expectEqual(true, environ.contains(gpa, "A")); + try testing.expectEqual(true, environ.containsConstant("A")); + try testing.expectEqual(false, environ.containsUnempty(gpa, "A")); + try testing.expectEqual(false, environ.containsUnemptyConstant("A")); + try testing.expectEqual(true, environ.contains(gpa, "")); + try testing.expectEqual(false, environ.contains(gpa, "B")); + + try testing.expectError(error.EnvironmentVariableMissing, environ.getAlloc(gpa, "BOGUS")); + { + const value = try environ.getAlloc(gpa, "FOO"); + defer gpa.free(value); + try testing.expectEqualStrings("BAR", value); + } + + var map2 = try environ.createMap(gpa); + defer map2.deinit(); + + try testing.expectEqualSlices([]const u8, map.keys(), map2.keys()); + try testing.expectEqualSlices([]const u8, map.values(), map2.values()); +} diff --git a/lib/std/zig.zig b/lib/std/zig.zig index 5524cac22d3e4e5a0adaba9fd50f8a09d7c9b8b1..6a25556ff048becaf8448f1a008456b12fc9a0d3 100644 --- a/lib/std/zig.zig +++ b/lib/std/zig.zig @@ -739,6 +739,8 @@ pub const EnvVar = enum { ZIG_VERBOSE_CC, ZIG_BTRFS_WORKAROUND, ZIG_DEBUG_CMD, + ZIG_IS_DETECTING_LIBC_PATHS, + ZIG_IS_TRYING_TO_NOT_CALL_ITSELF, CC, NO_COLOR, CLICOLOR_FORCE, diff --git a/src/main.zig b/src/main.zig index 25d14f0d7ae74253938f22bf285c81dbe57d257b..543fe72ddea8d044f1923421b3585bc7367218b0 100644 --- a/src/main.zig +++ b/src/main.zig @@ -168,7 +168,7 @@ const use_debug_allocator = build_options.debug_gpa or .ReleaseFast, .ReleaseSmall => false, }); -pub fn main() anyerror!void { +pub fn main(init: std.process.Init.Minimal) anyerror!void { const gpa = gpa: { if (use_debug_allocator) break :gpa debug_allocator.allocator(); if (native_os == .wasi) break :gpa std.heap.wasm_allocator; @@ -182,26 +182,25 @@ pub fn main() anyerror!void { defer arena_instance.deinit(); const arena = arena_instance.allocator(); - const args = try process.argsAlloc(arena); + const args = try init.args.toSlice(arena); if (args.len > 0) crash_report.zig_argv0 = args[0]; + var env_map = init.environ.createMap(arena) catch |err| fatal("failed to parse environment: {t}", .{err}); + if (tracy.enable_allocation) { var gpa_tracy = tracy.tracyAllocator(gpa); - return mainArgs(gpa_tracy.allocator(), arena, args); + return mainArgs(gpa_tracy.allocator(), arena, args, &env_map); } if (native_os == .wasi) { wasi_preopens = try fs.wasi.preopensAlloc(arena); } - return mainArgs(gpa, arena, args); + return mainArgs(gpa, arena, args, &env_map); } -fn mainArgs(gpa: Allocator, arena: Allocator, args: []const [:0]const u8) !void { - const tr = tracy.trace(@src()); - defer tr.end(); - +fn mainArgs(gpa: Allocator, arena: Allocator, args: []const [:0]const u8, env_map: *process.Environ.Map) !void { Compilation.setMainThread(); if (args.len <= 1) { @@ -209,7 +208,7 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const [:0]const u8) !void fatal("expected command argument", .{}); } - if (process.can_execv and std.posix.getenvZ("ZIG_IS_DETECTING_LIBC_PATHS") != null) { + if (process.can_replace and std.zig.EnvVar.ZIG_IS_DETECTING_LIBC_PATHS.isSet(env_map)) { dev.check(.cc_command); // In this case we have accidentally invoked ourselves as "the system C compiler" // to figure out where libc is installed. This is essentially infinite recursion @@ -217,27 +216,28 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const [:0]const u8) !void // Here we ignore the CC environment variable and exec `cc` as a child process. // However it's possible Zig is installed as *that* C compiler as well, which is // why we have this additional environment variable here to check. - var env_map = try process.getEnvMap(arena); - const inf_loop_env_key = "ZIG_IS_TRYING_TO_NOT_CALL_ITSELF"; - if (env_map.get(inf_loop_env_key) != null) { - fatal("The compilation links against libc, but Zig is unable to provide a libc " ++ - "for this operating system, and no --libc " ++ - "parameter was provided, so Zig attempted to invoke the system C compiler " ++ - "in order to determine where libc is installed. However the system C " ++ - "compiler is `zig cc`, so no libc installation was found.", .{}); + const inf_loop_env_key: std.zig.EnvVar = .ZIG_IS_TRYING_TO_NOT_CALL_ITSELF; + if (inf_loop_env_key.isSet(env_map)) { + fatal("{s}", .{ + "The compilation links against libc, but Zig is unable to provide a libc " ++ + "for this operating system, and no --libc " ++ + "parameter was provided, so Zig attempted to invoke the system C compiler " ++ + "in order to determine where libc is installed. However the system C " ++ + "compiler is `zig cc`, so no libc installation was found.", + }); } - try env_map.put(inf_loop_env_key, "1"); + try env_map.put(@tagName(inf_loop_env_key), "1"); // Some programs such as CMake will strip the `cc` and subsequent args from the // CC environment variable. We detect and support this scenario here because of // the ZIG_IS_DETECTING_LIBC_PATHS environment variable. if (mem.eql(u8, args[1], "cc")) { - return process.execve(arena, args[1..], &env_map); + return process.replace(.{ .argv = args[1..], .env_map = env_map }); } else { const modified_args = try arena.dupe([]const u8, args); modified_args[0] = "cc"; - return process.execve(arena, modified_args, &env_map); + return process.replace(.{ .argv = modified_args, .env_map = env_map }); } } @@ -4431,7 +4431,7 @@ fn runOrTest( // We do not execve for tests because if the test fails we want to print // the error message and invocation below. - if (process.can_execv and arg_mode == .run) { + if (process.can_replace and arg_mode == .run) { // execv releases the locks; no need to destroy the Compilation here. _ = try io.lockStderr(&.{}, .no_color); const err = process.execve(gpa, argv.items, &env_map); @@ -5668,7 +5668,7 @@ fn jitCmd( child_argv.appendSliceAssumeCapacity(args); - if (process.can_execv and options.capture == null) { + if (process.can_replace and options.capture == null) { if (EnvVar.ZIG_DEBUG_CMD.isSet()) { const cmd = try std.mem.join(arena, " ", child_argv.items); std.debug.print("{s}\n", .{cmd}); -- 2.54.0 From 69d07472a12c8ec8f83a43ed63c1ab7e2ab71c14 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 31 Dec 2025 16:02:44 -0800 Subject: [PATCH 07/60] std lib tests passing on linux --- lib/std/Build/Step.zig | 19 ++++++------- lib/std/Io.zig | 5 ---- lib/std/Io/Threaded.zig | 50 ++++++++++++++-------------------- lib/std/debug/ElfFile.zig | 12 ++++---- lib/std/debug/SelfInfo/Elf.zig | 4 +-- lib/std/process/Child.zig | 2 +- lib/std/process/Environ.zig | 4 +-- lib/std/std.zig | 5 ++++ 8 files changed, 46 insertions(+), 55 deletions(-) diff --git a/lib/std/Build/Step.zig b/lib/std/Build/Step.zig index 2699e3f01b2e9c6efdcf1398ea8ff39b1f26a621..93d6b9cdd63923a17e0cdacdfbfd0c00bb0085e3 100644 --- a/lib/std/Build/Step.zig +++ b/lib/std/Build/Step.zig @@ -448,7 +448,10 @@ pub fn evalZigProcess( try handleChildProcUnsupported(s); try handleVerbose(s.owner, null, argv); - var child = std.process.spawn(io, .{ + const zp = try gpa.create(ZigProcess); + defer if (!watch) gpa.destroy(zp); + + zp.child = std.process.spawn(io, .{ .argv = argv, .env_map = &b.graph.env_map, .stdin = .pipe, @@ -457,22 +460,18 @@ pub fn evalZigProcess( .request_resource_usage_statistics = true, .progress_node = prog_node, }) catch |err| return s.fail("failed to spawn zig compiler {s}: {t}", .{ argv[0], err }); - defer if (!watch) child.kill(io); + defer if (!watch) zp.child.kill(io); - const zp = try gpa.create(ZigProcess); zp.* = .{ - .child = child, + .child = zp.child, .poller = Io.poll(gpa, ZigProcess.StreamEnum, .{ - .stdout = child.stdout.?, - .stderr = child.stderr.?, + .stdout = zp.child.stdout.?, + .stderr = zp.child.stderr.?, }), .progress_ipc_fd = if (std.Progress.have_ipc) prog_node.getIpcFd() else {}, }; if (watch) s.setZigProcess(zp); - defer if (!watch) { - zp.poller.deinit(); - gpa.destroy(zp); - }; + defer if (!watch) zp.poller.deinit(); const result = try zigProcessUpdate(s, zp, watch, web_server, gpa); diff --git a/lib/std/Io.zig b/lib/std/Io.zig index 2f1a180fc5d21a2e4bc49b05934b9d5a2db94504..a1e38135bd4b2b92d842f78025ce888ba8f97484 100644 --- a/lib/std/Io.zig +++ b/lib/std/Io.zig @@ -2240,8 +2240,3 @@ pub fn tryLockStderr(io: Io, buffer: []u8, terminal_mode: ?Terminal.Mode) Cancel pub fn unlockStderr(io: Io) void { return io.vtable.unlockStderr(io.userdata); } - -pub fn environ(io: Io, name: []const u8) ?[]const u8 { - _ = io; - std.debug.panic("TODO: environ query: {s}", .{name}); -} diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 8bb3201bb898fb9829ae3027e55cbc35ce768c26..a94b20ab79e91dad3e28fef0dc7db9d191d7ce03 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -101,6 +101,9 @@ pub const Environ = struct { .windows, .wasi => struct {}, else => struct { PATH: ?[:0]const u8 = null, + DEBUGINFOD_CACHE_PATH: ?[:0]const u8 = null, + XDG_CACHE_HOME: ?[:0]const u8 = null, + HOME: ?[:0]const u8 = null, }, }; }; @@ -12674,27 +12677,6 @@ fn scanEnviron(t: *Threaded) void { } comptime assert(@sizeOf(Environ.String) == 0); } - } else if (builtin.link_libc) { - var ptr = std.c.environ; - while (ptr[0]) |line| : (ptr += 1) { - var line_i: usize = 0; - while (line[line_i] != 0 and line[line_i] != '=') : (line_i += 1) {} - const key = line[0..line_i]; - - var end_i: usize = line_i; - while (line[end_i] != 0) : (end_i += 1) {} - const value = line[line_i + 1 .. end_i :0]; - - if (std.mem.eql(u8, key, "NO_COLOR")) { - t.environ.exist.NO_COLOR = true; - } else if (std.mem.eql(u8, key, "CLICOLOR_FORCE")) { - t.environ.exist.CLICOLOR_FORCE = true; - } else if (@hasField(Environ.String, "PATH") and std.mem.eql(u8, key, "PATH")) { - t.environ.string.PATH = value; - } else if (std.mem.eql(u8, key, "ZIG_PROGRESS")) { - t.environ.zig_progress_handle = std.fmt.parseInt(u31, value, 10) catch error.UnrecognizedFormat; - } - } } else { for (t.environ.block) |opt_line| { const line = opt_line.?; @@ -12710,10 +12692,10 @@ fn scanEnviron(t: *Threaded) void { t.environ.exist.NO_COLOR = true; } else if (std.mem.eql(u8, key, "CLICOLOR_FORCE")) { t.environ.exist.CLICOLOR_FORCE = true; - } else if (@hasField(Environ.String, "PATH") and std.mem.eql(u8, key, "PATH")) { - t.environ.string.PATH = value; } else if (std.mem.eql(u8, key, "ZIG_PROGRESS")) { t.environ.zig_progress_handle = std.fmt.parseInt(u31, value, 10) catch error.UnrecognizedFormat; + } else inline for (@typeInfo(Environ.String).@"struct".fields) |field| { + if (std.mem.eql(u8, key, field.name)) @field(t.environ.string, field.name) = value; } } } @@ -12966,12 +12948,10 @@ fn childKill(userdata: ?*anyopaque, child: *std.process.Child) void { if (is_windows) { childKillWindows(t, child, 1) catch { childCleanupStreams(child); - child.id = null; }; } else { childKillPosix(t, child) catch { childCleanupStreams(child); - child.id = null; }; } } @@ -13012,7 +12992,6 @@ fn childWaitWindows(t: *Threaded, child: *process.Child) process.Child.WaitError posix.close(child.id); posix.close(child.thread_handle); childCleanupStreams(child); - child.id = null; return term; } @@ -13028,10 +13007,8 @@ fn childWaitPosix(t: *Threaded, child: *process.Child) process.Child.WaitError!p } break :res posix.waitpid(pid, 0); }; - const status = res.status; childCleanupStreams(child); - child.id = null; - return statusToTerm(status); + return statusToTerm(res.status); } fn statusToTerm(status: u32) process.Child.Term { @@ -13046,7 +13023,14 @@ fn statusToTerm(status: u32) process.Child.Term { } fn childKillPosix(t: *Threaded, child: *process.Child) !void { - try posix.kill(child.id.?, posix.SIG.TERM); + while (true) switch (posix.errno(posix.system.kill(child.id.?, .TERM))) { + .SUCCESS => break, + .INTR => continue, + .PERM => return error.PermissionDenied, + .INVAL => |err| return errnoBug(err), + .SRCH => |err| return errnoBug(err), + else => |err| return posix.unexpectedErrno(err), + }; _ = try childWaitPosix(t, child); } @@ -13063,6 +13047,7 @@ fn childCleanupStreams(child: *process.Child) void { posix.close(stderr.handle); child.stderr = null; } + child.id = null; } /// Errors that can occur between fork() and execv() @@ -14367,6 +14352,11 @@ fn progressParentFile(userdata: ?*anyopaque) std.Progress.ParentFileError!File { } }; } +pub fn environString(t: *Threaded, comptime name: []const u8) ?[:0]const u8 { + t.scanEnviron(); + return @field(t.environ.string, name); +} + test { _ = @import("Threaded/test.zig"); } diff --git a/lib/std/debug/ElfFile.zig b/lib/std/debug/ElfFile.zig index e17c518271f65c66eba95a2b414c791bef5cd14b..53db1c755a56255ccf9968c1fd7503ee39236a3a 100644 --- a/lib/std/debug/ElfFile.zig +++ b/lib/std/debug/ElfFile.zig @@ -66,16 +66,17 @@ pub const DebugInfoSearchPaths = struct { .exe_dir = null, }; - pub fn native(exe_path: []const u8, io: Io) DebugInfoSearchPaths { - return .{ + pub fn native(exe_path: []const u8) DebugInfoSearchPaths { + if (std.options.elf_debug_info_search_paths) |f| return f(exe_path); + if (std.Options.debug_threaded_io) |t| return .{ .debuginfod_client = p: { - if (io.environ("DEBUGINFOD_CACHE_PATH")) |p| { + if (t.environString("DEBUGINFOD_CACHE_PATH")) |p| { break :p .{ p, "" }; } - if (io.environ("XDG_CACHE_HOME")) |cache_path| { + if (t.environString("XDG_CACHE_HOME")) |cache_path| { break :p .{ cache_path, "/debuginfod_client" }; } - if (io.environ("HOME")) |home_path| { + if (t.environString("HOME")) |home_path| { break :p .{ home_path, "/.cache/debuginfod_client" }; } break :p null; @@ -85,6 +86,7 @@ pub const DebugInfoSearchPaths = struct { }, .exe_dir = std.fs.path.dirname(exe_path) orelse ".", }; + @compileError("std.Options.elf_debug_info_search_paths must be provided"); } }; diff --git a/lib/std/debug/SelfInfo/Elf.zig b/lib/std/debug/SelfInfo/Elf.zig index c62c2df4b892d0773b52b7a72241acd0f38465e0..ffcb4dfd26fdb9b1a80d030221d83b87283ef499 100644 --- a/lib/std/debug/SelfInfo/Elf.zig +++ b/lib/std/debug/SelfInfo/Elf.zig @@ -327,7 +327,7 @@ const Module = struct { const load_result = if (mod.name.len > 0) res: { var file = Io.Dir.cwd().openFile(io, mod.name, .{}) catch return error.MissingDebugInfo; defer file.close(io); - break :res std.debug.ElfFile.load(gpa, io, file, mod.build_id, &.native(mod.name, io)); + break :res std.debug.ElfFile.load(gpa, io, file, mod.build_id, &.native(mod.name)); } else res: { const path = std.process.executablePathAlloc(io, gpa) catch |err| switch (err) { error.OutOfMemory => |e| return e, @@ -336,7 +336,7 @@ const Module = struct { defer gpa.free(path); var file = Io.Dir.cwd().openFile(io, path, .{}) catch return error.MissingDebugInfo; defer file.close(io); - break :res std.debug.ElfFile.load(gpa, io, file, mod.build_id, &.native(path, io)); + break :res std.debug.ElfFile.load(gpa, io, file, mod.build_id, &.native(path)); }; var elf_file = load_result catch |err| switch (err) { diff --git a/lib/std/process/Child.zig b/lib/std/process/Child.zig index 6a170116ce74496706df13eb8c219a08f75a2a83..176405cc8b5fc3d0a3efc90155d94ce7c1d0f01f 100644 --- a/lib/std/process/Child.zig +++ b/lib/std/process/Child.zig @@ -106,7 +106,7 @@ pub const Term = union(enum) { /// /// Uncancelable. Ignores unexpected errors from the operating system. pub fn kill(child: *Child, io: Io) void { - if (child.id != null) { + if (child.id == null) { assert(child.stdin == null); assert(child.stdout == null); assert(child.stderr == null); diff --git a/lib/std/process/Environ.zig b/lib/std/process/Environ.zig index 831ba5a4680982855a929f7c549cc570bc8ebba9..4601d389ed3324904faf65d985215ac5b602f44d 100644 --- a/lib/std/process/Environ.zig +++ b/lib/std/process/Environ.zig @@ -792,6 +792,6 @@ test "convert from Environ to Map and back again" { var map2 = try environ.createMap(gpa); defer map2.deinit(); - try testing.expectEqualSlices([]const u8, map.keys(), map2.keys()); - try testing.expectEqualSlices([]const u8, map.values(), map2.values()); + try testing.expectEqualDeep(map.keys(), map2.keys()); + try testing.expectEqualDeep(map.values(), map2.values()); } diff --git a/lib/std/std.zig b/lib/std/std.zig index c4cb3b153701cf883b29a31eb969bed3edf1b841..627e7de6ddace2b60d84f9ce46c52ccd765151c3 100644 --- a/lib/std/std.zig +++ b/lib/std/std.zig @@ -173,6 +173,11 @@ pub const Options = struct { /// stack traces will just print an error to the relevant `Io.Writer` and return. allow_stack_tracing: bool = !@import("builtin").strip_debug_info, + elf_debug_info_search_paths: ?fn (exe_path: []const u8) switch (@import("builtin").object_format) { + .elf => debug.ElfFile.DebugInfoSearchPaths, + else => void, + } = null, + pub const debug_threaded_io: ?*Io.Threaded = if (@hasDecl(root, "std_options_debug_threaded_io")) root.std_options_debug_threaded_io else -- 2.54.0 From f612464331a3f878bce2da284960bab349090c00 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 31 Dec 2025 17:00:36 -0800 Subject: [PATCH 08/60] compiler: delete DarwinPosixSpawn if this is reintroduced it will need to be part of the std.Io implementation --- CMakeLists.txt | 1 - src/DarwinPosixSpawn.zig | 225 --------------------------------------- src/main.zig | 44 ++------ 3 files changed, 7 insertions(+), 263 deletions(-) delete mode 100644 src/DarwinPosixSpawn.zig diff --git a/CMakeLists.txt b/CMakeLists.txt index 53f4a2bd1980ecfbefa7f542b9c80b4f1b2d5945..ebd86915f4e55871a0a8d65cb3f42ab5f40dd93b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -513,7 +513,6 @@ set(ZIG_STAGE2_SOURCES src/Builtin.zig src/Compilation.zig src/Compilation/Config.zig - src/DarwinPosixSpawn.zig src/InternPool.zig src/Package.zig src/Package/Fetch.zig diff --git a/src/DarwinPosixSpawn.zig b/src/DarwinPosixSpawn.zig deleted file mode 100644 index 98197119845f0fa2fcadaea02f012111a473f687..0000000000000000000000000000000000000000 --- a/src/DarwinPosixSpawn.zig +++ /dev/null @@ -1,225 +0,0 @@ -const errno = std.posix.errno; -const unexpectedErrno = std.posix.unexpectedErrno; - -pub const Error = error{ - SystemResources, - InvalidFileDescriptor, - NameTooLong, - TooBig, - AccessDenied, - PermissionDenied, - InputOutput, - FileSystem, - FileNotFound, - InvalidExe, - NotDir, - FileBusy, - /// Returned when the child fails to execute either in the pre-exec() initialization step, or - /// when exec(3) is invoked. - ChildExecFailed, -} || std.posix.UnexpectedError; - -pub const Attr = struct { - attr: std.c.posix_spawnattr_t, - - pub fn init() Error!Attr { - var attr: std.c.posix_spawnattr_t = undefined; - switch (errno(std.c.posix_spawnattr_init(&attr))) { - .SUCCESS => return Attr{ .attr = attr }, - .NOMEM => return error.SystemResources, - .INVAL => unreachable, - else => |err| return unexpectedErrno(err), - } - } - - pub fn deinit(self: *Attr) void { - defer self.* = undefined; - switch (errno(std.c.posix_spawnattr_destroy(&self.attr))) { - .SUCCESS => return, - .INVAL => unreachable, // Invalid parameters. - else => unreachable, - } - } - - pub fn get(self: Attr) Error!std.c.POSIX_SPAWN { - var flags: std.c.POSIX_SPAWN = undefined; - switch (errno(std.c.posix_spawnattr_getflags(&self.attr, &flags))) { - .SUCCESS => return flags, - .INVAL => unreachable, - else => |err| return unexpectedErrno(err), - } - } - - pub fn set(self: *Attr, flags: std.c.POSIX_SPAWN) Error!void { - switch (errno(std.c.posix_spawnattr_setflags(&self.attr, flags))) { - .SUCCESS => return, - .INVAL => unreachable, - else => |err| return unexpectedErrno(err), - } - } -}; - -pub const Actions = struct { - actions: std.c.posix_spawn_file_actions_t, - - pub fn init() Error!Actions { - var actions: std.c.posix_spawn_file_actions_t = undefined; - switch (errno(std.c.posix_spawn_file_actions_init(&actions))) { - .SUCCESS => return Actions{ .actions = actions }, - .NOMEM => return error.SystemResources, - .INVAL => unreachable, - else => |err| return unexpectedErrno(err), - } - } - - pub fn deinit(self: *Actions) void { - defer self.* = undefined; - switch (errno(std.c.posix_spawn_file_actions_destroy(&self.actions))) { - .SUCCESS => return, - .INVAL => unreachable, // Invalid parameters. - else => unreachable, - } - } - - pub fn open(self: *Actions, fd: std.c.fd_t, path: []const u8, flags: u32, mode: std.c.mode_t) Error!void { - const posix_path = try std.posix.toPosixPath(path); - return self.openZ(fd, &posix_path, flags, mode); - } - - pub fn openZ(self: *Actions, fd: std.c.fd_t, path: [*:0]const u8, flags: u32, mode: std.c.mode_t) Error!void { - switch (errno(std.c.posix_spawn_file_actions_addopen(&self.actions, fd, path, @as(c_int, @bitCast(flags)), mode))) { - .SUCCESS => return, - .BADF => return error.InvalidFileDescriptor, - .NOMEM => return error.SystemResources, - .NAMETOOLONG => return error.NameTooLong, - .INVAL => unreachable, // the value of file actions is invalid - else => |err| return unexpectedErrno(err), - } - } - - pub fn close(self: *Actions, fd: std.c.fd_t) Error!void { - switch (errno(std.c.posix_spawn_file_actions_addclose(&self.actions, fd))) { - .SUCCESS => return, - .BADF => return error.InvalidFileDescriptor, - .NOMEM => return error.SystemResources, - .INVAL => unreachable, // the value of file actions is invalid - .NAMETOOLONG => unreachable, - else => |err| return unexpectedErrno(err), - } - } - - pub fn dup2(self: *Actions, fd: std.c.fd_t, newfd: std.c.fd_t) Error!void { - switch (errno(std.c.posix_spawn_file_actions_adddup2(&self.actions, fd, newfd))) { - .SUCCESS => return, - .BADF => return error.InvalidFileDescriptor, - .NOMEM => return error.SystemResources, - .INVAL => unreachable, // the value of file actions is invalid - .NAMETOOLONG => unreachable, - else => |err| return unexpectedErrno(err), - } - } - - pub fn inherit(self: *Actions, fd: std.c.fd_t) Error!void { - switch (errno(std.c.posix_spawn_file_actions_addinherit_np(&self.actions, fd))) { - .SUCCESS => return, - .BADF => return error.InvalidFileDescriptor, - .NOMEM => return error.SystemResources, - .INVAL => unreachable, // the value of file actions is invalid - .NAMETOOLONG => unreachable, - else => |err| return unexpectedErrno(err), - } - } - - pub fn chdir(self: *Actions, path: []const u8) Error!void { - const posix_path = try std.posix.toPosixPath(path); - return self.chdirZ(&posix_path); - } - - pub fn chdirZ(self: *Actions, path: [*:0]const u8) Error!void { - switch (errno(std.c.posix_spawn_file_actions_addchdir_np(&self.actions, path))) { - .SUCCESS => return, - .NOMEM => return error.SystemResources, - .NAMETOOLONG => return error.NameTooLong, - .BADF => unreachable, - .INVAL => unreachable, // the value of file actions is invalid - else => |err| return unexpectedErrno(err), - } - } - - pub fn fchdir(self: *Actions, fd: std.c.fd_t) Error!void { - switch (errno(std.c.posix_spawn_file_actions_addfchdir_np(&self.actions, fd))) { - .SUCCESS => return, - .BADF => return error.InvalidFileDescriptor, - .NOMEM => return error.SystemResources, - .INVAL => unreachable, // the value of file actions is invalid - .NAMETOOLONG => unreachable, - else => |err| return unexpectedErrno(err), - } - } -}; - -pub fn spawn( - path: []const u8, - actions: ?Actions, - attr: ?Attr, - argv: [*:null]const ?[*:0]const u8, - envp: [*:null]const ?[*:0]const u8, -) Error!std.c.pid_t { - const posix_path = try std.posix.toPosixPath(path); - return spawnZ(&posix_path, actions, attr, argv, envp); -} - -pub fn spawnZ( - path: [*:0]const u8, - actions: ?Actions, - attr: ?Attr, - argv: [*:null]const ?[*:0]const u8, - envp: [*:null]const ?[*:0]const u8, -) Error!std.c.pid_t { - var pid: std.c.pid_t = undefined; - switch (errno(std.c.posix_spawn( - &pid, - path, - if (actions) |a| &a.actions else null, - if (attr) |a| &a.attr else null, - argv, - envp, - ))) { - .SUCCESS => return pid, - .@"2BIG" => return error.TooBig, - .NOMEM => return error.SystemResources, - .BADF => return error.InvalidFileDescriptor, - .ACCES => return error.AccessDenied, - .IO => return error.InputOutput, - .LOOP => return error.FileSystem, - .NAMETOOLONG => return error.NameTooLong, - .NOENT => return error.FileNotFound, - .NOEXEC => return error.InvalidExe, - .NOTDIR => return error.NotDir, - .TXTBSY => return error.FileBusy, - .BADARCH => return error.InvalidExe, - .BADEXEC => return error.InvalidExe, - .FAULT => unreachable, - .INVAL => unreachable, - else => |err| return unexpectedErrno(err), - } -} - -pub fn waitpid(pid: std.c.pid_t, flags: u32) Error!std.posix.WaitPidResult { - var status: c_int = undefined; - while (true) { - const rc = waitpid(pid, &status, @as(c_int, @intCast(flags))); - switch (errno(rc)) { - .SUCCESS => return std.posix.WaitPidResult{ - .pid = @as(std.c.pid_t, @intCast(rc)), - .status = @as(u32, @bitCast(status)), - }, - .INTR => continue, - .CHILD => return error.ChildExecFailed, - .INVAL => unreachable, // Invalid flags. - else => unreachable, - } - } -} - -const std = @import("std"); diff --git a/src/main.zig b/src/main.zig index 543fe72ddea8d044f1923421b3585bc7367218b0..c3d69081f1dad2f54c187b8a3f3ee75429b3b04b 100644 --- a/src/main.zig +++ b/src/main.zig @@ -4559,43 +4559,13 @@ fn runOrTestHotSwap( try argv.appendSlice(all_args[i..]); } - switch (builtin.target.os.tag) { - .macos => { - const PosixSpawn = @import("DarwinPosixSpawn.zig"); - - var attr = try PosixSpawn.Attr.init(); - defer attr.deinit(); - - // ASLR is probably a good default for better debugging experience/programming - // with hot-code updates in mind. However, we can also make it work with ASLR on. - try attr.set(.{ - .SETSIGDEF = true, - .SETSIGMASK = true, - .DISABLE_ASLR = true, - }); - - var arena_allocator = std.heap.ArenaAllocator.init(gpa); - defer arena_allocator.deinit(); - const arena = arena_allocator.allocator(); - - const argv_buf = try arena.allocSentinel(?[*:0]u8, argv.items.len, null); - for (argv.items, 0..) |arg, i| argv_buf[i] = (try arena.dupeZ(u8, arg)).ptr; - - const pid = try PosixSpawn.spawn(argv.items[0], null, attr, argv_buf, std.c.environ); - return pid; - }, - else => { - var child = std.process.Child.init(argv.items, gpa); - - child.stdin_behavior = .inherit; - child.stdout_behavior = .inherit; - child.stderr_behavior = .inherit; - - try child.spawn(io); - - return child.id; - }, - } + var child = try std.process.spwan(io, .{ + .argv = argv.items, + .stdin = .inherit, + .stdout = .inherit, + .stderr = .inherit, + }); + return child.id; } const UpdateModuleError = Compilation.UpdateError || error{ -- 2.54.0 From de8c4cd64e0599abda0a0c5e1187391352020478 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 31 Dec 2025 16:59:31 -0800 Subject: [PATCH 09/60] compiler: update to new std.process APIs --- lib/std/http/Client.zig | 19 +- lib/std/process/Args.zig | 16 +- lib/std/process/Environ.zig | 2 +- lib/std/start.zig | 7 +- lib/std/zig.zig | 9 + lib/std/zig/LibCDirs.zig | 14 +- lib/std/zig/LibCInstallation.zig | 25 +- lib/std/zig/WindowsSdk.zig | 13 +- lib/std/zig/system/NativePaths.zig | 35 +-- src/Compilation.zig | 38 ++- src/introspect.zig | 22 +- src/link/Lld.zig | 53 ++-- src/main.zig | 452 ++++++++++++++++------------- src/print_env.zig | 10 +- 14 files changed, 390 insertions(+), 325 deletions(-) diff --git a/lib/std/http/Client.zig b/lib/std/http/Client.zig index 7f8c9827ef03ab3ba70477a0ec0d226c260df9f1..d07ba89c6116cea401231a354a1eb7e91107657e 100644 --- a/lib/std/http/Client.zig +++ b/lib/std/http/Client.zig @@ -1307,7 +1307,7 @@ pub fn deinit(client: *Client) void { /// Asserts the client has no active connections. /// Uses `arena` for a few small allocations that must outlive the client, or /// at least until those fields are set to different values. -pub fn initDefaultProxies(client: *Client, arena: Allocator) !void { +pub fn initDefaultProxies(client: *Client, arena: Allocator, env_map: *std.process.Environ.Map) !void { // Prevent any new connections from being created. client.connection_pool.mutex.lock(); defer client.connection_pool.mutex.unlock(); @@ -1315,27 +1315,26 @@ pub fn initDefaultProxies(client: *Client, arena: Allocator) !void { assert(client.connection_pool.used.first == null); // There are active requests. if (client.http_proxy == null) { - client.http_proxy = try createProxyFromEnvVar(arena, &.{ + client.http_proxy = try createProxyFromEnvVar(arena, env_map, &.{ "http_proxy", "HTTP_PROXY", "all_proxy", "ALL_PROXY", }); } if (client.https_proxy == null) { - client.https_proxy = try createProxyFromEnvVar(arena, &.{ + client.https_proxy = try createProxyFromEnvVar(arena, env_map, &.{ "https_proxy", "HTTPS_PROXY", "all_proxy", "ALL_PROXY", }); } } -fn createProxyFromEnvVar(arena: Allocator, env_var_names: []const []const u8) !?*Proxy { +fn createProxyFromEnvVar( + arena: Allocator, + env_map: *std.process.Environ.Map, + env_var_names: []const []const u8, +) !?*Proxy { const content = for (env_var_names) |name| { - const content = std.process.getEnvVarOwned(arena, name) catch |err| switch (err) { - error.EnvironmentVariableNotFound => continue, - else => |e| return e, - }; - + const content = env_map.get(name) orelse continue; if (content.len == 0) continue; - break content; } else return null; diff --git a/lib/std/process/Args.zig b/lib/std/process/Args.zig index 60bd3c984893b33179420aaa0cd5d58ad81fdd69..4cf9c438b2703745ce6ca3da81260c6b0c4b4c6d 100644 --- a/lib/std/process/Args.zig +++ b/lib/std/process/Args.zig @@ -516,7 +516,7 @@ pub fn freeSlice(gpa: Allocator, to_slice_result: []const [:0]u8) void { } test "Iterator.Windows" { - const t = testArgIteratorWindows; + const t = testIteratorWindows; try t( \\"C:\Program Files\zig\zig.exe" run .\src\main.zig -target x86_64-windows-gnu -O ReleaseSafe -- --emoji=🗿 --eval="new Regex(\"Dwayne \\\"The Rock\\\" Johnson\")" @@ -648,7 +648,7 @@ test "Iterator.Windows" { try t("foo.exe \"\xed\xa0\x81\"\xed\xb0\xb7", &.{ "foo.exe", "𐐷" }); } -fn testArgIteratorWindows(cmd_line: []const u8, expected_args: []const []const u8) !void { +fn testIteratorWindows(cmd_line: []const u8, expected_args: []const []const u8) !void { const cmd_line_w = try std.unicode.wtf8ToWtf16LeAllocZ(testing.allocator, cmd_line); defer testing.allocator.free(cmd_line_w); @@ -679,7 +679,7 @@ fn testArgIteratorWindows(cmd_line: []const u8, expected_args: []const []const u } } -test "general arg parsing" { +test "general parsing" { try testGeneralCmdLine("a b\tc d", &.{ "a", "b", "c", "d" }); try testGeneralCmdLine("\"abc\" d e", &.{ "abc", "d", "e" }); try testGeneralCmdLine("a\\\\\\b d\"e f\"g h", &.{ "a\\\\\\b", "de fg", "h" }); @@ -703,7 +703,7 @@ test "general arg parsing" { } fn testGeneralCmdLine(input_cmd_line: []const u8, expected_args: []const []const u8) !void { - var it = try ArgIteratorGeneral(.{}).init(std.testing.allocator, input_cmd_line); + var it = try IteratorGeneral(.{}).init(std.testing.allocator, input_cmd_line); defer it.deinit(); for (expected_args) |expected_arg| { const arg = it.next().?; @@ -712,14 +712,14 @@ fn testGeneralCmdLine(input_cmd_line: []const u8, expected_args: []const []const try testing.expect(it.next() == null); } -/// Optional parameters for `ArgIteratorGeneral` -pub const ArgIteratorGeneralOptions = struct { +/// Optional parameters for `IteratorGeneral` +pub const IteratorGeneralOptions = struct { comments: bool = false, single_quotes: bool = false, }; /// A general Iterator to parse a string into a set of arguments -pub fn ArgIteratorGeneral(comptime options: ArgIteratorGeneralOptions) type { +pub fn IteratorGeneral(comptime options: IteratorGeneralOptions) type { return struct { allocator: Allocator, index: usize = 0, @@ -947,7 +947,7 @@ test "response file arg parsing" { } fn testResponseFileCmdLine(input_cmd_line: []const u8, expected_args: []const []const u8) !void { - var it = try ArgIteratorGeneral(.{ .comments = true, .single_quotes = true }) + var it = try IteratorGeneral(.{ .comments = true, .single_quotes = true }) .init(std.testing.allocator, input_cmd_line); defer it.deinit(); for (expected_args) |expected_arg| { diff --git a/lib/std/process/Environ.zig b/lib/std/process/Environ.zig index 4601d389ed3324904faf65d985215ac5b602f44d..3581634516613cde402ad908490633d86e04f9ac 100644 --- a/lib/std/process/Environ.zig +++ b/lib/std/process/Environ.zig @@ -20,7 +20,7 @@ const mem = std.mem; block: Block, pub const Block = switch (native_os) { - .windows => []const u16, + .windows => [*:0]const u16, .wasi => switch (builtin.link_libc) { false => void, true => [:null]const ?[*:0]const u8, diff --git a/lib/std/start.zig b/lib/std/start.zig index e0a910f753b1e688a562d826b9eb5b3f59ac8204..df4d6e9aff13335480d9cd01c921e99eca4a9f5e 100644 --- a/lib/std/start.zig +++ b/lib/std/start.zig @@ -524,9 +524,12 @@ fn WinStartup() callconv(.withStackAlign(.c, 1)) noreturn { std.debug.maybeEnableSegfaultHandler(); + const peb = std.os.windows.peb(); + const cmd_line = std.os.windows.peb().ProcessParameters.CommandLine; + std.os.windows.ntdll.RtlExitUserProcess(callMain( - std.os.windows.peb().ProcessParameters.CommandLine, - std.os.windows.peb().ProcessParameters.Environment, + cmd_line.Buffer.?[0..@divExact(cmd_line.Length, 2)], + peb.ProcessParameters.Environment, )); } diff --git a/lib/std/zig.zig b/lib/std/zig.zig index 6a25556ff048becaf8448f1a008456b12fc9a0d3..5e7b0efdf1a1d0a73aa6995ac50be046d5ee0c18 100644 --- a/lib/std/zig.zig +++ b/lib/std/zig.zig @@ -741,9 +741,18 @@ pub const EnvVar = enum { ZIG_DEBUG_CMD, ZIG_IS_DETECTING_LIBC_PATHS, ZIG_IS_TRYING_TO_NOT_CALL_ITSELF, + + NIX_CFLAGS_COMPILE, + NIX_CFLAGS_LINK, + NIX_LDFLAGS, + C_INCLUDE_PATH, + CPLUS_INCLUDE_PATH, + LIBRARY_PATH, CC, + NO_COLOR, CLICOLOR_FORCE, + XDG_CACHE_HOME, LOCALAPPDATA, HOME, diff --git a/lib/std/zig/LibCDirs.zig b/lib/std/zig/LibCDirs.zig index e05ccde5895c7eae0426a64ff1b5c176490065df..45a51e9d2978e5d93f0e62f33b12a6ba0cee885b 100644 --- a/lib/std/zig/LibCDirs.zig +++ b/lib/std/zig/LibCDirs.zig @@ -28,6 +28,7 @@ pub fn detect( is_native_abi: bool, link_libc: bool, libc_installation: ?*const LibCInstallation, + env_map: *const std.process.Environ.Map, ) LibCInstallation.FindError!LibCDirs { if (!link_libc) { return .{ @@ -47,7 +48,10 @@ pub fn detect( // using the system libc installation. if (is_native_abi and !target.isMinGW()) { const libc = try arena.create(LibCInstallation); - libc.* = LibCInstallation.findNative(arena, io, .{ .target = target }) catch |err| switch (err) { + libc.* = LibCInstallation.findNative(arena, io, .{ + .target = target, + .env_map = env_map, + }) catch |err| switch (err) { error.CCompilerExitCode, error.CCompilerCrashed, error.CCompilerCannotFindHeaders, @@ -84,12 +88,16 @@ pub fn detect( if (use_system_abi) { const libc = try arena.create(LibCInstallation); - libc.* = try LibCInstallation.findNative(arena, io, .{ .verbose = true, .target = target }); + libc.* = try LibCInstallation.findNative(arena, io, .{ + .verbose = true, + .target = target, + .env_map = env_map, + }); return detectFromInstallation(arena, target, libc); } return .{ - .libc_include_dir_list = &[0][]u8{}, + .libc_include_dir_list = &.{}, .libc_installation = null, .libc_framework_dir_list = &.{}, .sysroot = null, diff --git a/lib/std/zig/LibCInstallation.zig b/lib/std/zig/LibCInstallation.zig index 444a9c7df9d81509ff72f8d7150e2d336b0978ae..d709ae15110179039713a62b40e394af42b9ef01 100644 --- a/lib/std/zig/LibCInstallation.zig +++ b/lib/std/zig/LibCInstallation.zig @@ -167,6 +167,7 @@ pub fn render(self: LibCInstallation, out: *std.Io.Writer) !void { pub const FindNativeOptions = struct { target: *const std.Target, + env_map: *const std.process.Environ.Map, /// If enabled, will print human-friendly errors to stderr. verbose: bool = false, @@ -238,10 +239,7 @@ pub fn deinit(self: *LibCInstallation, allocator: Allocator) void { fn findNativeIncludeDirPosix(self: *LibCInstallation, gpa: Allocator, io: Io, args: FindNativeOptions) FindError!void { // Detect infinite loops. - var env_map = std.process.getEnvMap(gpa) catch |err| switch (err) { - error.Unexpected => unreachable, // WASI-only - else => |e| return e, - }; + var env_map = try args.env_map.clone(gpa); defer env_map.deinit(); const skip_cc_env_var = if (env_map.get(inf_loop_env_key)) |phase| blk: { if (std.mem.eql(u8, phase, "1")) { @@ -260,7 +258,7 @@ fn findNativeIncludeDirPosix(self: *LibCInstallation, gpa: Allocator, io: Io, ar var argv = std.array_list.Managed([]const u8).init(gpa); defer argv.deinit(); - try appendCcExe(&argv, skip_cc_env_var); + try appendCcExe(&argv, skip_cc_env_var, &env_map); try argv.appendSlice(&.{ "-E", "-Wp,-v", @@ -449,6 +447,7 @@ fn findNativeCrtDirWindows( fn findNativeCrtDirPosix(self: *LibCInstallation, gpa: Allocator, io: Io, args: FindNativeOptions) FindError!void { self.crt_dir = try ccPrintFileName(gpa, io, .{ + .env_map = args.env_map, .search_basename = switch (args.target.os.tag) { .linux => if (args.target.abi.isAndroid()) "crtbegin_dynamic.o" else "crt1.o", else => "crt1.o", @@ -553,6 +552,7 @@ fn findNativeMsvcLibDir( } pub const CCPrintFileNameOptions = struct { + env_map: *const std.process.Environ.Map, search_basename: []const u8, want_dirname: enum { full_path, only_dir }, verbose: bool = false, @@ -561,10 +561,7 @@ pub const CCPrintFileNameOptions = struct { /// caller owns returned memory fn ccPrintFileName(gpa: Allocator, io: Io, args: CCPrintFileNameOptions) ![:0]u8 { // Detect infinite loops. - var env_map = std.process.getEnvMap(gpa) catch |err| switch (err) { - error.Unexpected => unreachable, // WASI-only - else => |e| return e, - }; + var env_map = try args.env_map.clone(gpa); defer env_map.deinit(); const skip_cc_env_var = if (env_map.get(inf_loop_env_key)) |phase| blk: { if (std.mem.eql(u8, phase, "1")) { @@ -584,7 +581,7 @@ fn ccPrintFileName(gpa: Allocator, io: Io, args: CCPrintFileNameOptions) ![:0]u8 const arg1 = try std.fmt.allocPrint(gpa, "-print-file-name={s}", .{args.search_basename}); defer gpa.free(arg1); - try appendCcExe(&argv, skip_cc_env_var); + try appendCcExe(&argv, skip_cc_env_var, &env_map); try argv.append(arg1); const run_res = std.process.run(gpa, io, .{ @@ -672,14 +669,18 @@ fn fillInstallations( const inf_loop_env_key = "ZIG_IS_DETECTING_LIBC_PATHS"; -fn appendCcExe(args: *std.array_list.Managed([]const u8), skip_cc_env_var: bool) !void { +fn appendCcExe( + args: *std.array_list.Managed([]const u8), + skip_cc_env_var: bool, + env_map: *const std.process.Environ.Map, +) !void { const default_cc_exe = if (is_windows) "cc.exe" else "cc"; try args.ensureUnusedCapacity(1); if (skip_cc_env_var) { args.appendAssumeCapacity(default_cc_exe); return; } - const cc_env_var = std.zig.EnvVar.CC.getPosix() orelse { + const cc_env_var = std.zig.EnvVar.CC.get(env_map) orelse { args.appendAssumeCapacity(default_cc_exe); return; }; diff --git a/lib/std/zig/WindowsSdk.zig b/lib/std/zig/WindowsSdk.zig index 1b172e4358eb195549526a727b7d9046189c2d55..d9c4f1b868520ab6ccddf341349dcdab66099934 100644 --- a/lib/std/zig/WindowsSdk.zig +++ b/lib/std/zig/WindowsSdk.zig @@ -951,15 +951,14 @@ const MsvcLibDir = struct { return msvc_dir; } - fn findViaVs7Key(gpa: Allocator, io: Io, arch: std.Target.Cpu.Arch) error{ OutOfMemory, PathNotFound }![]const u8 { + fn findViaVs7Key( + gpa: Allocator, + io: Io, + arch: std.Target.Cpu.Arch, + env_map: *const std.process.Environ.Map, + ) error{ OutOfMemory, PathNotFound }![]const u8 { var base_path: std.array_list.Managed(u8) = base_path: { try_env: { - var env_map = std.process.getEnvMap(gpa) catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - else => break :try_env, - }; - defer env_map.deinit(); - if (env_map.get("VS140COMNTOOLS")) |VS140COMNTOOLS| { if (VS140COMNTOOLS.len < "C:\\Common7\\Tools".len) break :try_env; if (!Dir.path.isAbsolute(VS140COMNTOOLS)) break :try_env; diff --git a/lib/std/zig/system/NativePaths.zig b/lib/std/zig/system/NativePaths.zig index bf66f912ae360e1798a8981a21c4a68ccc2181f0..02f59d4b03dcb8846446874879868c854b03c807 100644 --- a/lib/std/zig/system/NativePaths.zig +++ b/lib/std/zig/system/NativePaths.zig @@ -14,10 +14,16 @@ framework_dirs: std.ArrayList([]const u8) = .empty, rpaths: std.ArrayList([]const u8) = .empty, warnings: std.ArrayList([]const u8) = .empty, -pub fn detect(arena: Allocator, io: Io, native_target: *const std.Target) !NativePaths { +pub fn detect( + arena: Allocator, + io: Io, + native_target: *const std.Target, + env_map: *process.Environ.Map, +) !NativePaths { var self: NativePaths = .{ .arena = arena }; var is_nix = false; - if (process.getEnvVarOwned(arena, "NIX_CFLAGS_COMPILE")) |nix_cflags_compile| { + + if (std.zig.EnvVar.NIX_CFLAGS_COMPILE.get(env_map)) |nix_cflags_compile| { is_nix = true; var it = mem.tokenizeScalar(u8, nix_cflags_compile, ' '); while (true) { @@ -41,12 +47,9 @@ pub fn detect(arena: Allocator, io: Io, native_target: *const std.Target) !Nativ try self.addWarningFmt("Unrecognized C flag from NIX_CFLAGS_COMPILE: {s}", .{word}); } } - } else |err| switch (err) { - error.InvalidWtf8 => unreachable, - error.EnvironmentVariableNotFound => {}, - error.OutOfMemory => |e| return e, } - if (process.getEnvVarOwned(arena, "NIX_LDFLAGS")) |nix_ldflags| { + + if (std.zig.EnvVar.NIX_LDFLAGS.get(env_map)) |nix_ldflags| { is_nix = true; var it = mem.tokenizeScalar(u8, nix_ldflags, ' '); while (true) { @@ -73,12 +76,9 @@ pub fn detect(arena: Allocator, io: Io, native_target: *const std.Target) !Nativ break; } } - } else |err| switch (err) { - error.InvalidWtf8 => unreachable, - error.EnvironmentVariableNotFound => {}, - error.OutOfMemory => |e| return e, } - if (process.getEnvVarOwned(arena, "NIX_CFLAGS_LINK")) |nix_cflags_link| { + + if (std.zig.EnvVar.NIX_CFLAGS_LINK.get(env_map)) |nix_cflags_link| { is_nix = true; var it = mem.tokenizeScalar(u8, nix_cflags_link, ' '); while (true) { @@ -105,11 +105,8 @@ pub fn detect(arena: Allocator, io: Io, native_target: *const std.Target) !Nativ break; } } - } else |err| switch (err) { - error.InvalidWtf8 => unreachable, - error.EnvironmentVariableNotFound => {}, - error.OutOfMemory => |e| return e, } + if (is_nix) { return self; } @@ -182,21 +179,21 @@ pub fn detect(arena: Allocator, io: Io, native_target: *const std.Target) !Nativ // variables to search for headers and libraries. // We use os.getenv here since this part won't be executed on // windows, to get rid of unnecessary error handling. - if (std.posix.getenv("C_INCLUDE_PATH")) |c_include_path| { + if (std.zig.EnvVar.C_INCLUDE_PATH.get(env_map)) |c_include_path| { var it = mem.tokenizeScalar(u8, c_include_path, ':'); while (it.next()) |dir| { try self.addIncludeDir(dir); } } - if (std.posix.getenv("CPLUS_INCLUDE_PATH")) |cplus_include_path| { + if (std.zig.EnvVar.CPLUS_INCLUDE_PATH.get(env_map)) |cplus_include_path| { var it = mem.tokenizeScalar(u8, cplus_include_path, ':'); while (it.next()) |dir| { try self.addIncludeDir(dir); } } - if (std.posix.getenv("LIBRARY_PATH")) |library_path| { + if (std.zig.EnvVar.LIBRARY_PATH.get(env_map)) |library_path| { var it = mem.tokenizeScalar(u8, library_path, ':'); while (it.next()) |dir| { try self.addLibDir(dir); diff --git a/src/Compilation.zig b/src/Compilation.zig index b3751b55828b25d1a828da2acd8355866867061b..bf35be165c26a1ba4e9c0643dc8e5056e8b302c5 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -54,6 +54,7 @@ gpa: Allocator, /// threads at once. arena: Allocator, io: Io, +environ_map: *std.process.Environ.Map, thread_limit: usize, /// Not every Compilation compiles .zig code! For example you could do `zig build-exe foo.o`. zcu: ?*Zcu, @@ -761,6 +762,7 @@ pub const Directories = struct { .wasi => void, else => []const u8, }, + env_map: *std.process.Environ.Map, ) Directories { const wasi = builtin.target.os.tag == .wasi; @@ -779,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"); - const path = introspect.resolveGlobalCacheDir(arena) catch |err| { + const path = introspect.resolveGlobalCacheDir(arena, env_map) catch |err| { fatal("unable to resolve zig cache directory: {t}", .{err}); }; break :d openUnresolved(arena, io, cwd, path, .@"global cache"); @@ -1797,6 +1799,8 @@ pub const CreateOptions = struct { parent_whole_cache: ?ParentWholeCache = null, + environ_map: *std.process.Environ.Map, + pub const Entry = link.File.OpenOptions.Entry; /// Which fields are valid depends on the `cache_mode` given. @@ -1967,6 +1971,7 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic, options.root_mod.resolved_target.is_native_abi, link_libc, options.libc_installation, + options.environ_map, ) catch |err| switch (err) { error.OutOfMemory => |e| return e, // Every other error is specifically related to finding the native installation @@ -2306,6 +2311,7 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic, .emit_llvm_ir = try options.emit_llvm_ir.resolve(arena, &options, .llvm_ir), .emit_llvm_bc = try options.emit_llvm_bc.resolve(arena, &options, .llvm_bc), .emit_docs = try options.emit_docs.resolve(arena, &options, .docs), + .environ_map = options.environ_map, }; errdefer { @@ -5503,6 +5509,7 @@ fn workerDocsWasmFallible(comp: *Compilation, prog_node: std.Progress.Node) SubU .verbose_llvm_bc = comp.verbose_llvm_bc, .verbose_cimport = comp.verbose_cimport, .verbose_llvm_cpu_features = comp.verbose_llvm_cpu_features, + .environ_map = comp.environ_map, }) catch |err| switch (err) { error.CreateFail => { comp.lockAndSetMiscFailure(.docs_wasm, "sub-compilation of docs_wasm failed: {f}", .{sub_create_diag}); @@ -5705,6 +5712,7 @@ pub fn translateC( translated_basename: []const u8, owner_mod: *Package.Module, prog_node: std.Progress.Node, + env_map: *std.process.Environ.Map, ) !CImportResult { dev.check(.translate_c_command); @@ -5774,7 +5782,7 @@ pub fn translateC( } var stdout: []u8 = undefined; - try @import("main.zig").translateC(gpa, arena, io, argv.items, prog_node, &stdout); + try @import("main.zig").translateC(gpa, arena, io, argv.items, env_map, prog_node, &stdout); if (out_dep_path) |dep_file_path| add_deps: { if (comp.verbose_cimport) log.info("processing dep file at {s}", .{dep_file_path}); @@ -5861,7 +5869,8 @@ pub fn cImport( defer arena_allocator.deinit(); const arena = arena_allocator.allocator(); - break :result try comp.translateC( + break :result try translateC( + comp, arena, &man, .c, @@ -5869,6 +5878,7 @@ pub fn cImport( translated_basename, owner_mod, prog_node, + comp.environ_map, ); }; @@ -6741,15 +6751,16 @@ fn spawnZigRc( var node_name: std.ArrayList(u8) = .empty; defer node_name.deinit(arena); - var child = std.process.Child.init(argv, arena); - child.stdin_behavior = .ignore; - child.stdout_behavior = .pipe; - child.stderr_behavior = .pipe; - child.progress_node = child_progress_node; - - child.spawn(io) catch |err| { - return comp.failWin32Resource(win32_resource, "unable to spawn {s} rc: {t}", .{ argv[0], err }); - }; + var child = std.process.spawn(io, .{ + .argv = argv, + .stdin = .ignore, + .stdout = .pipe, + .stderr = .pipe, + .progress_node = child_progress_node, + }) catch |err| return comp.failWin32Resource(win32_resource, "unable to spawn {s} rc: {t}", .{ + argv[0], err, + }); + defer child.kill(io); var poller = std.Io.poll(comp.gpa, enum { stdout, stderr }, .{ .stdout = child.stdout.?, @@ -6781,7 +6792,7 @@ fn spawnZigRc( const stderr = poller.reader(.stderr); const term = child.wait(io) catch |err| { - return comp.failWin32Resource(win32_resource, "unable to wait for {s} rc: {s}", .{ argv[0], @errorName(err) }); + return comp.failWin32Resource(win32_resource, "unable to wait for {s} rc: {t}", .{ argv[0], err }); }; switch (term) { @@ -7963,6 +7974,7 @@ fn buildOutputFromZig( .verbose_llvm_cpu_features = comp.verbose_llvm_cpu_features, .clang_passthrough_mode = comp.clang_passthrough_mode, .skip_linker_dependencies = true, + .environ_map = comp.environ_map, }) catch |err| switch (err) { error.CreateFail => { comp.lockAndSetMiscFailure(misc_task_tag, "sub-compilation of {t} failed: {f}", .{ misc_task_tag, sub_create_diag }); diff --git a/src/introspect.zig b/src/introspect.zig index 9481201c3bfdab5691ff5805733ca71a7b342f89..d30b4ba067bf3bdd02b90519006120ff12873165 100644 --- a/src/introspect.zig +++ b/src/introspect.zig @@ -101,31 +101,27 @@ pub fn findZigLibDirFromSelfExe( return error.FileNotFound; } -/// Caller owns returned memory. -pub fn resolveGlobalCacheDir(gpa: Allocator) ![]u8 { - if (try std.zig.EnvVar.ZIG_GLOBAL_CACHE_DIR.get(gpa)) |value| return value; +pub fn resolveGlobalCacheDir(arena: Allocator, env_map: *std.process.Environ.Map) ![]const u8 { + if (std.zig.EnvVar.ZIG_GLOBAL_CACHE_DIR.get(env_map)) |value| return value; const app_name = "zig"; switch (builtin.os.tag) { .wasi => @compileError("on WASI the global cache dir must be resolved with preopens"), .windows => { - const local_app_data_dir = (std.zig.EnvVar.LOCALAPPDATA.get(gpa) catch |err| switch (err) { - error.OutOfMemory => |e| return e, - error.InvalidWtf8 => return error.AppDataDirUnavailable, - }) orelse return error.AppDataDirUnavailable; - defer gpa.free(local_app_data_dir); - return Dir.path.join(gpa, &.{ local_app_data_dir, app_name }); + const local_app_data_dir = std.zig.EnvVar.LOCALAPPDATA.get(env_map) orelse + return error.AppDataDirUnavailable; + return Dir.path.join(arena, &.{ local_app_data_dir, app_name }); }, else => { - if (std.zig.EnvVar.XDG_CACHE_HOME.getPosix()) |cache_root| { + if (std.zig.EnvVar.XDG_CACHE_HOME.get(env_map)) |cache_root| { if (cache_root.len > 0) { - return Dir.path.join(gpa, &.{ cache_root, app_name }); + return Dir.path.join(arena, &.{ cache_root, app_name }); } } - if (std.zig.EnvVar.HOME.getPosix()) |home| { + if (std.zig.EnvVar.HOME.get(env_map)) |home| { if (home.len > 0) { - return Dir.path.join(gpa, &.{ home, ".cache", app_name }); + return Dir.path.join(arena, &.{ home, ".cache", app_name }); } } return error.AppDataDirUnavailable; diff --git a/src/link/Lld.zig b/src/link/Lld.zig index d1d2ebf07a885b02587efdd85493599d6e948379..174777ac2964851620adc09c6efc279182c048ad 100644 --- a/src/link/Lld.zig +++ b/src/link/Lld.zig @@ -1604,19 +1604,24 @@ fn spawnLld(comp: *Compilation, arena: Allocator, argv: []const []const u8) !voi var stderr: []u8 = &.{}; defer gpa.free(stderr); - var child = std.process.Child.init(argv, arena); + // TODO rework this awkward logic to call child.kill() in the failure case const term = (if (comp.clang_passthrough_mode) term: { - child.stdin_behavior = .inherit; - child.stdout_behavior = .inherit; - child.stderr_behavior = .inherit; + var child = std.process.spawn(io, .{ + .argv = argv, + .stdin = .inherit, + .stdout = .inherit, + .stderr = .inherit, + }) catch |err| break :term err; - break :term child.spawnAndWait(io); + break :term child.wait(io); } else term: { - child.stdin_behavior = .ignore; - child.stdout_behavior = .ignore; - child.stderr_behavior = .pipe; + var child = std.process.spawn(io, .{ + .argv = argv, + .stdin = .ignore, + .stdout = .ignore, + .stderr = .pipe, + }) catch |err| break :term err; - child.spawn(io) catch |err| break :term err; var stderr_reader = child.stderr.?.readerStreaming(io, &.{}); stderr = try stderr_reader.interface.allocRemaining(gpa, .unlimited); break :term child.wait(io); @@ -1650,23 +1655,21 @@ fn spawnLld(comp: *Compilation, arena: Allocator, argv: []const []const u8) !voi try rsp_writer.flush(); } - var rsp_child = std.process.Child.init(&.{ argv[0], argv[1], try std.fmt.allocPrint( - arena, - "@{s}", - .{try comp.dirs.local_cache.join(arena, &.{rsp_path})}, - ) }, arena); + var rsp_child = std.process.spawn(io, .{ + .argv = &.{ + argv[0], + argv[1], + try std.fmt.allocPrint(arena, "@{s}", .{ + try comp.dirs.local_cache.join(arena, &.{rsp_path}), + }), + }, + .stdin = if (comp.clang_passthrough_mode) .inherit else .ignore, + .stdout = if (comp.clang_passthrough_mode) .inherit else .ignore, + .stderr = if (comp.clang_passthrough_mode) .inherit else .pipe, + }) catch |err| break :err err; if (comp.clang_passthrough_mode) { - rsp_child.stdin_behavior = .inherit; - rsp_child.stdout_behavior = .inherit; - rsp_child.stderr_behavior = .inherit; - - break :term rsp_child.spawnAndWait(io) catch |err| break :err err; + break :term rsp_child.wait(io) catch |err| break :err err; } else { - rsp_child.stdin_behavior = .ignore; - rsp_child.stdout_behavior = .ignore; - rsp_child.stderr_behavior = .pipe; - - rsp_child.spawn(io) catch |err| break :err err; var stderr_reader = rsp_child.stderr.?.readerStreaming(io, &.{}); stderr = try stderr_reader.interface.allocRemaining(gpa, .unlimited); break :term rsp_child.wait(io) catch |err| break :err err; @@ -1674,7 +1677,7 @@ fn spawnLld(comp: *Compilation, arena: Allocator, argv: []const []const u8) !voi }, else => first_err, }; - log.err("unable to spawn LLD {s}: {s}", .{ argv[0], @errorName(err) }); + log.err("unable to spawn LLD {s}: {t}", .{ argv[0], err }); return error.UnableToSpawnSelf; }; diff --git a/src/main.zig b/src/main.zig index c3d69081f1dad2f54c187b8a3f3ee75429b3b04b..a265b22512c50ed91c475cd0e1453042f4799eb9 100644 --- a/src/main.zig +++ b/src/main.zig @@ -42,7 +42,6 @@ test { const thread_stack_size = 60 << 20; pub const std_options: std.Options = .{ - .wasiCwd = wasi_cwd, .logFn = log, .log_level = switch (builtin.mode) { @@ -51,6 +50,7 @@ pub const std_options: std.Options = .{ .ReleaseSmall => .err, }, }; +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; @@ -208,7 +208,15 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const [:0]const u8, env_ma fatal("expected command argument", .{}); } - if (process.can_replace and std.zig.EnvVar.ZIG_IS_DETECTING_LIBC_PATHS.isSet(env_map)) { + var threaded: Io.Threaded = .init(gpa, .{ + .argv0 = if (@hasField(Io.Threaded.Argv0, "value")) .{ .value = args[0] } else .{}, + }); + defer threaded.deinit(); + threaded_impl_ptr = &threaded; + threaded.stack_size = thread_stack_size; + const io = threaded.io(); + + if (process.can_replace and EnvVar.ZIG_IS_DETECTING_LIBC_PATHS.isSet(env_map)) { dev.check(.cc_command); // In this case we have accidentally invoked ourselves as "the system C compiler" // to figure out where libc is installed. This is essentially infinite recursion @@ -217,7 +225,7 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const [:0]const u8, env_ma // However it's possible Zig is installed as *that* C compiler as well, which is // why we have this additional environment variable here to check. - const inf_loop_env_key: std.zig.EnvVar = .ZIG_IS_TRYING_TO_NOT_CALL_ITSELF; + const inf_loop_env_key: EnvVar = .ZIG_IS_TRYING_TO_NOT_CALL_ITSELF; if (inf_loop_env_key.isSet(env_map)) { fatal("{s}", .{ "The compilation links against libc, but Zig is unable to provide a libc " ++ @@ -233,42 +241,34 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const [:0]const u8, env_ma // CC environment variable. We detect and support this scenario here because of // the ZIG_IS_DETECTING_LIBC_PATHS environment variable. if (mem.eql(u8, args[1], "cc")) { - return process.replace(.{ .argv = args[1..], .env_map = env_map }); + return process.replace(io, .{ .argv = args[1..], .env_map = env_map }); } else { const modified_args = try arena.dupe([]const u8, args); modified_args[0] = "cc"; - return process.replace(.{ .argv = modified_args, .env_map = env_map }); + return process.replace(io, .{ .argv = modified_args, .env_map = env_map }); } } - var threaded: Io.Threaded = .init(gpa, .{ - .argv0 = if (@hasField(Io.Threaded.Argv0, "value")) .{ .value = args[0] } else .{}, - }); - defer threaded.deinit(); - threaded_impl_ptr = &threaded; - threaded.stack_size = thread_stack_size; - const io = threaded.io(); - const cmd = args[1]; const cmd_args = args[2..]; if (mem.eql(u8, cmd, "build-exe")) { dev.check(.build_exe_command); - return buildOutputType(gpa, arena, io, args, .{ .build = .Exe }); + return buildOutputType(gpa, arena, io, args, .{ .build = .Exe }, env_map); } else if (mem.eql(u8, cmd, "build-lib")) { dev.check(.build_lib_command); - return buildOutputType(gpa, arena, io, args, .{ .build = .Lib }); + return buildOutputType(gpa, arena, io, args, .{ .build = .Lib }, env_map); } else if (mem.eql(u8, cmd, "build-obj")) { dev.check(.build_obj_command); - return buildOutputType(gpa, arena, io, args, .{ .build = .Obj }); + return buildOutputType(gpa, arena, io, args, .{ .build = .Obj }, env_map); } else if (mem.eql(u8, cmd, "test")) { dev.check(.test_command); - return buildOutputType(gpa, arena, io, args, .zig_test); + return buildOutputType(gpa, arena, io, args, .zig_test, env_map); } else if (mem.eql(u8, cmd, "test-obj")) { dev.check(.test_command); - return buildOutputType(gpa, arena, io, args, .zig_test_obj); + return buildOutputType(gpa, arena, io, args, .zig_test_obj, env_map); } else if (mem.eql(u8, cmd, "run")) { dev.check(.run_command); - return buildOutputType(gpa, arena, io, args, .run); + return buildOutputType(gpa, arena, io, args, .run, env_map); } else if (mem.eql(u8, cmd, "dlltool") or mem.eql(u8, cmd, "ranlib") or mem.eql(u8, cmd, "lib") or @@ -278,7 +278,7 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const [:0]const u8, env_ma return process.exit(try llvmArMain(arena, args)); } else if (mem.eql(u8, cmd, "build")) { dev.check(.build_command); - return cmdBuild(gpa, arena, io, cmd_args); + return cmdBuild(gpa, arena, io, cmd_args, env_map); } else if (mem.eql(u8, cmd, "clang") or mem.eql(u8, cmd, "-cc1") or mem.eql(u8, cmd, "-cc1as")) { @@ -292,16 +292,16 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const [:0]const u8, env_ma return process.exit(try lldMain(arena, args, true)); } else if (mem.eql(u8, cmd, "cc")) { dev.check(.cc_command); - return buildOutputType(gpa, arena, io, args, .cc); + return buildOutputType(gpa, arena, io, args, .cc, env_map); } else if (mem.eql(u8, cmd, "c++")) { dev.check(.cc_command); - return buildOutputType(gpa, arena, io, args, .cpp); + return buildOutputType(gpa, arena, io, args, .cpp, env_map); } else if (mem.eql(u8, cmd, "translate-c")) { dev.check(.translate_c_command); - return buildOutputType(gpa, arena, io, args, .translate_c); + return buildOutputType(gpa, arena, io, args, .translate_c, env_map); } else if (mem.eql(u8, cmd, "rc")) { const use_server = cmd_args.len > 0 and std.mem.eql(u8, cmd_args[0], "--zig-integration"); - return jitCmd(gpa, arena, io, cmd_args, .{ + return jitCmd(gpa, arena, io, cmd_args, env_map, .{ .cmd_name = "resinator", .root_src_path = "resinator/main.zig", .depend_on_aro = true, @@ -312,20 +312,20 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const [:0]const u8, env_ma dev.check(.fmt_command); return @import("fmt.zig").run(gpa, arena, io, cmd_args); } else if (mem.eql(u8, cmd, "objcopy")) { - return jitCmd(gpa, arena, io, cmd_args, .{ + return jitCmd(gpa, arena, io, cmd_args, env_map, .{ .cmd_name = "objcopy", .root_src_path = "objcopy.zig", }); } else if (mem.eql(u8, cmd, "fetch")) { - return cmdFetch(gpa, arena, io, cmd_args); + return cmdFetch(gpa, arena, io, cmd_args, env_map); } else if (mem.eql(u8, cmd, "libc")) { - return jitCmd(gpa, arena, io, cmd_args, .{ + return jitCmd(gpa, arena, io, cmd_args, env_map, .{ .cmd_name = "libc", .root_src_path = "libc.zig", .prepend_zig_lib_dir_path = true, }); } else if (mem.eql(u8, cmd, "std")) { - return jitCmd(gpa, arena, io, cmd_args, .{ + return jitCmd(gpa, arena, io, cmd_args, env_map, .{ .cmd_name = "std", .root_src_path = "std-docs.zig", .prepend_zig_lib_dir_path = true, @@ -355,10 +355,11 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const [:0]const u8, env_ma args, if (native_os == .wasi) wasi_preopens, &host, + env_map, ); return stdout_writer.interface.flush(); } else if (mem.eql(u8, cmd, "reduce")) { - return jitCmd(gpa, arena, io, cmd_args, .{ + return jitCmd(gpa, arena, io, cmd_args, env_map, .{ .cmd_name = "reduce", .root_src_path = "reduce.zig", }); @@ -803,6 +804,7 @@ fn buildOutputType( io: Io, all_args: []const []const u8, arg_mode: ArgMode, + env_map: *process.Environ.Map, ) !void { var provided_name: ?[]const u8 = null; var root_src_file: ?[]const u8 = null; @@ -815,9 +817,9 @@ fn buildOutputType( var debug_compile_errors = false; var debug_incremental = false; var verbose_link = (native_os != .wasi or builtin.link_libc) and - EnvVar.ZIG_VERBOSE_LINK.isSet(); + EnvVar.ZIG_VERBOSE_LINK.isSet(env_map); var verbose_cc = (native_os != .wasi or builtin.link_libc) and - EnvVar.ZIG_VERBOSE_CC.isSet(); + EnvVar.ZIG_VERBOSE_CC.isSet(env_map); var verbose_air = false; var verbose_intern_pool = false; var verbose_generic_instances = false; @@ -889,9 +891,9 @@ fn buildOutputType( var runtime_args_start: ?usize = null; var test_filters: std.ArrayList([]const u8) = .empty; var test_runner_path: ?[]const u8 = null; - var override_local_cache_dir: ?[]const u8 = try EnvVar.ZIG_LOCAL_CACHE_DIR.get(arena); - var override_global_cache_dir: ?[]const u8 = try EnvVar.ZIG_GLOBAL_CACHE_DIR.get(arena); - var override_lib_dir: ?[]const u8 = try EnvVar.ZIG_LIB_DIR.get(arena); + var override_local_cache_dir: ?[]const u8 = EnvVar.ZIG_LOCAL_CACHE_DIR.get(env_map); + var override_global_cache_dir: ?[]const u8 = EnvVar.ZIG_GLOBAL_CACHE_DIR.get(env_map); + var override_lib_dir: ?[]const u8 = EnvVar.ZIG_LIB_DIR.get(env_map); var clang_preprocessor_mode: Compilation.ClangPreprocessorMode = .no; var subsystem: ?std.zig.Subsystem = null; var major_subsystem_version: ?u16 = null; @@ -988,7 +990,7 @@ fn buildOutputType( .framework_dirs = .{}, .rpath_list = .{}, .each_lib_rpath = null, - .libc_paths_file = try EnvVar.ZIG_LIBC.get(arena), + .libc_paths_file = EnvVar.ZIG_LIBC.get(env_map), .native_system_include_paths = &.{}, }; defer create_module.link_inputs.deinit(gpa); @@ -997,9 +999,9 @@ fn buildOutputType( // if set, default the color setting to .off or .on, respectively // explicit --color arguments will still override this setting. // Disable color on WASI per https://github.com/WebAssembly/WASI/issues/162 - var color: Color = if (native_os == .wasi or EnvVar.NO_COLOR.isSet()) + var color: Color = if (native_os == .wasi or EnvVar.NO_COLOR.isSet(env_map)) .off - else if (EnvVar.CLICOLOR_FORCE.isSet()) + else if (EnvVar.CLICOLOR_FORCE.isSet(env_map)) .on else .auto; @@ -3097,6 +3099,7 @@ fn buildOutputType( }, if (native_os == .wasi) wasi_preopens, self_exe_path, + env_map, ); defer dirs.deinit(io); @@ -3108,7 +3111,7 @@ fn buildOutputType( create_module.opts.emit_bin = emit_bin != .no; create_module.opts.any_c_source_files = create_module.c_source_files.items.len != 0; - const main_mod = try createModule(gpa, arena, io, &create_module, 0, null, color); + const main_mod = try createModule(gpa, arena, io, &create_module, 0, null, color, env_map); for (create_module.modules.keys(), create_module.modules.values()) |key, cli_mod| { if (cli_mod.resolved == null) fatal("module '{s}' declared but not used", .{key}); @@ -3585,6 +3588,7 @@ fn buildOutputType( .global_cc_argv = try cc_argv.toOwnedSlice(arena), .file_system_inputs = &file_system_inputs, .debug_compiler_runtime_libs = debug_compiler_runtime_libs, + .environ_map = env_map, }) catch |err| switch (err) { error.CreateFail => switch (create_diag) { .cross_libc_unavailable => { @@ -3648,6 +3652,7 @@ fn buildOutputType( arg_mode, all_args, runtime_args_start, + env_map, ); return cleanExit(io); }, @@ -3674,6 +3679,7 @@ fn buildOutputType( arg_mode, all_args, runtime_args_start, + env_map, ); return cleanExit(io); }, @@ -3686,7 +3692,7 @@ fn buildOutputType( defer root_prog_node.end(); if (arg_mode == .translate_c) { - return cmdTranslateC(comp, arena, null, null, root_prog_node); + return cmdTranslateC(comp, arena, null, null, root_prog_node, env_map); } updateModule(comp, color, root_prog_node) catch |err| switch (err) { @@ -3754,6 +3760,7 @@ fn buildOutputType( all_args, runtime_args_start, create_module.resolved_options.link_libc, + env_map, ); } @@ -3809,6 +3816,7 @@ fn createModule( index: usize, parent: ?*Package.Module, color: std.zig.Color, + env_map: *process.Environ.Map, ) Allocator.Error!*Package.Module { const cli_mod = &create_module.modules.values()[index]; if (cli_mod.resolved) |m| return m; @@ -3988,7 +3996,7 @@ fn createModule( resolved_target.is_native_os and resolved_target.is_native_abi and create_module.want_native_include_dirs) { - var paths = std.zig.system.NativePaths.detect(arena, io, target) catch |err| + var paths = std.zig.system.NativePaths.detect(arena, io, target, env_map) catch |err| fatal("unable to detect native system paths: {t}", .{err}); for (paths.warnings.items) |warning| { warn("{s}", .{warning}); @@ -4015,6 +4023,7 @@ fn createModule( create_module.libc_installation = LibCInstallation.findNative(arena, io, .{ .verbose = true, .target = target, + .env_map = env_map, }) catch |err| { fatal("unable to find native libc installation: {t}", .{err}); }; @@ -4119,7 +4128,7 @@ fn createModule( for (cli_mod.deps) |dep| { const dep_index = create_module.modules.getIndex(dep.value) orelse fatal("module '{s}' depends on non-existent module '{s}'", .{ name, dep.key }); - const dep_mod = try createModule(gpa, arena, io, create_module, dep_index, mod, color); + const dep_mod = try createModule(gpa, arena, io, create_module, dep_index, mod, color, env_map); try mod.deps.put(arena, dep.key, dep_mod); } @@ -4128,9 +4137,7 @@ fn createModule( fn saveState(comp: *Compilation, incremental: bool) void { if (incremental) { - comp.saveState() catch |err| { - warn("unable to save incremental compilation state: {s}", .{@errorName(err)}); - }; + comp.saveState() catch |err| warn("unable to save incremental compilation state: {t}", .{err}); } } @@ -4143,6 +4150,7 @@ fn serve( arg_mode: ArgMode, all_args: []const []const u8, runtime_args_start: ?usize, + env_map: *process.Environ.Map, ) !void { const gpa = comp.gpa; const io = comp.io; @@ -4190,7 +4198,7 @@ fn serve( defer arena_instance.deinit(); const arena = arena_instance.allocator(); var output: Compilation.CImportResult = undefined; - try cmdTranslateC(comp, arena, &output, file_system_inputs, main_progress_node); + try cmdTranslateC(comp, arena, &output, file_system_inputs, main_progress_node, env_map); defer output.deinit(gpa); if (file_system_inputs.items.len != 0) { @@ -4390,6 +4398,7 @@ fn runOrTest( all_args: []const []const u8, runtime_args_start: ?usize, link_libc: bool, + env_map: *process.Environ.Map, ) !void { const raw_emit_bin = comp.emit_bin orelse return; const exe_path = switch (comp.cache_use) { @@ -4426,77 +4435,90 @@ fn runOrTest( if (runtime_args_start) |i| { try argv.appendSlice(all_args[i..]); } - var env_map = try process.getEnvMap(arena); try env_map.put("ZIG_EXE", self_exe_path); // We do not execve for tests because if the test fails we want to print // the error message and invocation below. if (process.can_replace and arg_mode == .run) { - // execv releases the locks; no need to destroy the Compilation here. + // process replacement releases the locks; no need to destroy the Compilation here. _ = try io.lockStderr(&.{}, .no_color); - const err = process.execve(gpa, argv.items, &env_map); + const err = process.replace(io, .{ .argv = argv.items, .env_map = env_map }); io.unlockStderr(); try warnAboutForeignBinaries(io, arena, arg_mode, target, link_libc); const cmd = try std.mem.join(arena, " ", argv.items); fatal("the following command failed to execve with '{t}':\n{s}", .{ err, cmd }); - } else if (process.can_spawn) { - var child = std.process.Child.init(argv.items, gpa); - child.env_map = &env_map; - child.stdin_behavior = .inherit; - child.stdout_behavior = .inherit; - child.stderr_behavior = .inherit; - + } else if (!process.can_spawn) { + const cmd = try std.mem.join(arena, " ", argv.items); + fatal("the following command cannot be executed ({t} does not support spawning a child process):\n{s}", .{ + native_os, cmd, + }); + } + const term_result = (term: { // Here we release all the locks associated with the Compilation so // that whatever this child process wants to do won't deadlock. comp.destroy(); comp_destroyed.* = true; - const term_result = t: { - _ = try io.lockStderr(&.{}, .no_color); - defer io.unlockStderr(); - break :t child.spawnAndWait(io); - }; - const term = term_result catch |err| { - try warnAboutForeignBinaries(io, arena, arg_mode, target, link_libc); - const cmd = try std.mem.join(arena, " ", argv.items); - fatal("the following command failed with '{s}':\n{s}", .{ @errorName(err), cmd }); - }; - switch (arg_mode) { - .run, .build => { - switch (term) { - .Exited => |code| { - if (code == 0) { - return cleanExit(io); - } else { - process.exit(code); - } - }, - else => { - process.exit(1); - }, - } - }, - .zig_test => { - switch (term) { - .Exited => |code| { - if (code == 0) { - return cleanExit(io); - } else { - const cmd = try std.mem.join(arena, " ", argv.items); - fatal("the following test command failed with exit code {d}:\n{s}", .{ code, cmd }); - } - }, - else => { + _ = try io.lockStderr(&.{}, .no_color); + defer io.unlockStderr(); + + var child = std.process.spawn(io, .{ + .argv = argv.items, + .env_map = env_map, + .stdin = .inherit, + .stdout = .inherit, + .stderr = .inherit, + }) catch |err| break :term err; + defer child.kill(io); + + break :term child.wait(io); + }); + + const term = term_result catch |err| { + try warnAboutForeignBinaries(io, arena, arg_mode, target, link_libc); + const cmd = try std.mem.join(arena, " ", argv.items); + fatal("the following command failed with {t}:\n{s}", .{ err, cmd }); + }; + switch (arg_mode) { + .run, .build => { + switch (term) { + .exited => |code| { + if (code == 0) { + return cleanExit(io); + } else { + process.exit(code); + } + }, + .signal => |sig| { + const cmd = try std.mem.join(arena, " ", argv.items); + fatal("the following command terminated with signal {t}:\n{s}", .{ sig, cmd }); + }, + else => { + process.exit(1); + }, + } + }, + .zig_test => { + switch (term) { + .exited => |code| { + if (code == 0) { + return cleanExit(io); + } else { const cmd = try std.mem.join(arena, " ", argv.items); - fatal("the following test command crashed:\n{s}", .{cmd}); - }, - } - }, - else => unreachable, - } - } else { - const cmd = try std.mem.join(arena, " ", argv.items); - fatal("the following command cannot be executed ({s} does not support spawning a child process):\n{s}", .{ @tagName(native_os), cmd }); + fatal("the following test command failed with exit code {d}:\n{s}", .{ code, cmd }); + } + }, + .signal => |sig| { + const cmd = try std.mem.join(arena, " ", argv.items); + fatal("the following test command terminated with signal {t}:\n{s}", .{ sig, cmd }); + }, + else => { + const cmd = try std.mem.join(arena, " ", argv.items); + fatal("the following test command crashed:\n{s}", .{cmd}); + }, + } + }, + else => unreachable, } } @@ -4559,13 +4581,13 @@ fn runOrTestHotSwap( try argv.appendSlice(all_args[i..]); } - var child = try std.process.spwan(io, .{ + var child = try std.process.spawn(io, .{ .argv = argv.items, .stdin = .inherit, .stdout = .inherit, .stderr = .inherit, }); - return child.id; + return child.id.?; } const UpdateModuleError = Compilation.UpdateError || error{ @@ -4597,6 +4619,7 @@ fn cmdTranslateC( fancy_output: ?*Compilation.CImportResult, file_system_inputs: ?*std.ArrayList(u8), prog_node: std.Progress.Node, + env_map: *process.Environ.Map, ) !void { dev.check(.translate_c_command); @@ -4630,6 +4653,7 @@ fn cmdTranslateC( translated_basename, comp.root_mod, prog_node, + env_map, ); if (result.errors.errorMessageCount() != 0) { @@ -4677,10 +4701,11 @@ pub fn translateC( arena: Allocator, io: Io, argv: []const []const u8, + env_map: *process.Environ.Map, prog_node: std.Progress.Node, capture: ?*[]u8, ) !void { - try jitCmd(gpa, arena, io, argv, .{ + try jitCmd(gpa, arena, io, argv, env_map, .{ .cmd_name = "translate-c", .root_src_path = "translate-c/main.zig", .depend_on_aro = true, @@ -4837,21 +4862,21 @@ test sanitizeExampleName { try std.testing.expectEqualStrings("test_project", try sanitizeExampleName(arena, "test project")); } -fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) !void { +fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8, env_map: *process.Environ.Map) !void { dev.check(.build_command); var build_file: ?[]const u8 = null; - var override_lib_dir: ?[]const u8 = try EnvVar.ZIG_LIB_DIR.get(arena); - var override_global_cache_dir: ?[]const u8 = try EnvVar.ZIG_GLOBAL_CACHE_DIR.get(arena); - var override_local_cache_dir: ?[]const u8 = try EnvVar.ZIG_LOCAL_CACHE_DIR.get(arena); - var override_build_runner: ?[]const u8 = try EnvVar.ZIG_BUILD_RUNNER.get(arena); + var override_lib_dir: ?[]const u8 = EnvVar.ZIG_LIB_DIR.get(env_map); + var override_global_cache_dir: ?[]const u8 = EnvVar.ZIG_GLOBAL_CACHE_DIR.get(env_map); + var override_local_cache_dir: ?[]const u8 = EnvVar.ZIG_LOCAL_CACHE_DIR.get(env_map); + var override_build_runner: ?[]const u8 = EnvVar.ZIG_BUILD_RUNNER.get(env_map); var child_argv = std.array_list.Managed([]const u8).init(arena); var reference_trace: ?u32 = null; var debug_compile_errors = false; var verbose_link = (native_os != .wasi or builtin.link_libc) and - EnvVar.ZIG_VERBOSE_LINK.isSet(); + EnvVar.ZIG_VERBOSE_LINK.isSet(env_map); var verbose_cc = (native_os != .wasi or builtin.link_libc) and - EnvVar.ZIG_VERBOSE_CC.isSet(); + EnvVar.ZIG_VERBOSE_CC.isSet(env_map); var verbose_air = false; var verbose_intern_pool = false; var verbose_generic_instances = false; @@ -5048,7 +5073,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) } const work_around_btrfs_bug = native_os == .linux and - EnvVar.ZIG_BTRFS_WORKAROUND.isSet(); + EnvVar.ZIG_BTRFS_WORKAROUND.isSet(env_map); const root_prog_node = std.Progress.start(io, .{ .disable_printing = (color == .off), .root_name = "Compile Build Script", @@ -5108,6 +5133,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) } }, {}, self_exe_path, + env_map, ); defer dirs.deinit(io); @@ -5210,7 +5236,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) job_queue.read_only = true; cleanup_build_dir = job_queue.global_cache.handle; } else { - try http_client.initDefaultProxies(arena); + try http_client.initDefaultProxies(arena, env_map); } try job_queue.all_fetches.ensureUnusedCapacity(gpa, 1); @@ -5364,6 +5390,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) .cache_mode = .whole, .reference_trace = reference_trace, .debug_compile_errors = debug_compile_errors, + .environ_map = env_map, }) catch |err| switch (err) { error.CreateFail => fatal("failed to create compilation: {f}", .{create_diag}), else => fatal("failed to create compilation: {s}", .{@errorName(err)}), @@ -5385,81 +5412,81 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) }); } - if (process.can_spawn) { - var child = std.process.Child.init(child_argv.items, gpa); - child.stdin_behavior = .inherit; - child.stdout_behavior = .inherit; - child.stderr_behavior = .inherit; - - const term = t: { - _ = try io.lockStderr(&.{}, .no_color); - defer io.unlockStderr(); - break :t child.spawnAndWait(io) catch |err| - fatal("failed to spawn build runner {s}: {t}", .{ child_argv.items[0], err }); - }; - - switch (term) { - .Exited => |code| { - if (code == 0) return cleanExit(io); - // Indicates that the build runner has reported compile errors - // and this parent process does not need to report any further - // diagnostics. - if (code == 2) process.exit(2); - - if (code == 3) { - if (!dev.env.supports(.fetch_command)) process.exit(3); - // Indicates the configure phase failed due to missing lazy - // dependencies and stdout contains the hashes of the ones - // that are missing. - const s = fs.path.sep_str; - const tmp_sub_path = "tmp" ++ s ++ results_tmp_file_nonce; - const stdout = dirs.local_cache.handle.readFileAlloc(io, tmp_sub_path, arena, .limited(50 * 1024 * 1024)) catch |err| { - fatal("unable to read results of configure phase from '{f}{s}': {s}", .{ - dirs.local_cache, tmp_sub_path, @errorName(err), - }); - }; - dirs.local_cache.handle.deleteFile(io, tmp_sub_path) catch {}; - - var it = mem.splitScalar(u8, stdout, '\n'); - var any_errors = false; - while (it.next()) |hash| { - if (hash.len == 0) continue; - if (hash.len > Package.Hash.max_len) { - std.log.err("invalid digest (length {d} exceeds maximum): '{s}'", .{ - hash.len, hash, - }); - any_errors = true; - continue; - } - try unlazy_set.put(arena, .fromSlice(hash), {}); - } - if (any_errors) process.exit(3); - if (system_pkg_dir_path) |p| { - // In this mode, the system needs to provide these packages; they - // cannot be fetched by Zig. - for (unlazy_set.keys()) |*hash| { - std.log.err("lazy dependency package not found: {s}" ++ s ++ "{s}", .{ - p, hash.toSlice(), - }); - } - std.log.info("remote package fetching disabled due to --system mode", .{}); - std.log.info("dependencies might be avoidable depending on build configuration", .{}); - process.exit(3); - } - continue; - } - - const cmd = try std.mem.join(arena, " ", child_argv.items); - fatal("the following build command failed with exit code {d}:\n{s}", .{ code, cmd }); - }, - else => { - const cmd = try std.mem.join(arena, " ", child_argv.items); - fatal("the following build command crashed:\n{s}", .{cmd}); - }, - } - } else { + if (!process.can_spawn) { const cmd = try std.mem.join(arena, " ", child_argv.items); - fatal("the following command cannot be executed ({s} does not support spawning a child process):\n{s}", .{ @tagName(native_os), cmd }); + fatal("the following command cannot be executed ({t} does not support spawning a child process):\n{s}", .{ native_os, cmd }); + } + switch (term: { + _ = try io.lockStderr(&.{}, .no_color); + defer io.unlockStderr(); + var child = std.process.spawn(io, .{ + .argv = child_argv.items, + }) catch |err| fatal("failed to spawn build runner {s}: {t}", .{ child_argv.items[0], err }); + defer child.kill(io); + break :term child.wait(io) catch |err| + fatal("failed to wait build runner {s}: {t}", .{ child_argv.items[0], err }); + }) { + .exited => |code| { + if (code == 0) return cleanExit(io); + // Indicates that the build runner has reported compile errors + // and this parent process does not need to report any further + // diagnostics. + if (code == 2) process.exit(2); + + if (code == 3) { + if (!dev.env.supports(.fetch_command)) process.exit(3); + // Indicates the configure phase failed due to missing lazy + // dependencies and stdout contains the hashes of the ones + // that are missing. + const s = fs.path.sep_str; + const tmp_sub_path = "tmp" ++ s ++ results_tmp_file_nonce; + const stdout = dirs.local_cache.handle.readFileAlloc(io, tmp_sub_path, arena, .limited(50 * 1024 * 1024)) catch |err| { + fatal("unable to read results of configure phase from '{f}{s}': {t}", .{ + dirs.local_cache, tmp_sub_path, err, + }); + }; + dirs.local_cache.handle.deleteFile(io, tmp_sub_path) catch {}; + + var it = mem.splitScalar(u8, stdout, '\n'); + var any_errors = false; + while (it.next()) |hash| { + if (hash.len == 0) continue; + if (hash.len > Package.Hash.max_len) { + std.log.err("invalid digest (length {d} exceeds maximum): '{s}'", .{ + hash.len, hash, + }); + any_errors = true; + continue; + } + try unlazy_set.put(arena, .fromSlice(hash), {}); + } + if (any_errors) process.exit(3); + if (system_pkg_dir_path) |p| { + // In this mode, the system needs to provide these packages; they + // cannot be fetched by Zig. + for (unlazy_set.keys()) |*hash| { + std.log.err("lazy dependency package not found: {s}" ++ s ++ "{s}", .{ + p, hash.toSlice(), + }); + } + std.log.info("remote package fetching disabled due to --system mode", .{}); + std.log.info("dependencies might be avoidable depending on build configuration", .{}); + process.exit(3); + } + continue; + } + + const cmd = try std.mem.join(arena, " ", child_argv.items); + fatal("the following build command failed with exit code {d}:\n{s}", .{ code, cmd }); + }, + .signal => |sig| { + const cmd = try std.mem.join(arena, " ", child_argv.items); + fatal("the following build command terminated with signal {t}:\n{s}", .{ sig, cmd }); + }, + else => { + const cmd = try std.mem.join(arena, " ", child_argv.items); + fatal("the following build command crashed:\n{s}", .{cmd}); + }, } } } @@ -5482,6 +5509,7 @@ fn jitCmd( arena: Allocator, io: Io, args: []const []const u8, + env_map: *process.Environ.Map, options: JitCmdOptions, ) !void { dev.check(.jit_command); @@ -5503,13 +5531,13 @@ fn jitCmd( const self_exe_path = process.executablePathAlloc(io, arena) catch |err| fatal("unable to find self exe path: {t}", .{err}); - const optimize_mode: std.builtin.OptimizeMode = if (EnvVar.ZIG_DEBUG_CMD.isSet()) + const optimize_mode: std.builtin.OptimizeMode = if (EnvVar.ZIG_DEBUG_CMD.isSet(env_map)) .Debug else .ReleaseFast; const strip = optimize_mode != .Debug; - const override_lib_dir: ?[]const u8 = try EnvVar.ZIG_LIB_DIR.get(arena); - const override_global_cache_dir: ?[]const u8 = try EnvVar.ZIG_GLOBAL_CACHE_DIR.get(arena); + const override_lib_dir: ?[]const u8 = EnvVar.ZIG_LIB_DIR.get(env_map); + const override_global_cache_dir: ?[]const u8 = EnvVar.ZIG_GLOBAL_CACHE_DIR.get(env_map); // This `init` calls `fatal` on error. var dirs: Compilation.Directories = .init( @@ -5520,6 +5548,7 @@ fn jitCmd( .global, if (native_os == .wasi) wasi_preopens, self_exe_path, + env_map, ); defer dirs.deinit(io); @@ -5593,6 +5622,7 @@ fn jitCmd( .self_exe_path = self_exe_path, .thread_limit = thread_limit, .cache_mode = .whole, + .environ_map = env_map, }) catch |err| switch (err) { error.CreateFail => fatal("failed to create compilation: {f}", .{create_diag}), else => fatal("failed to create compilation: {s}", .{@errorName(err)}), @@ -5639,31 +5669,33 @@ fn jitCmd( child_argv.appendSliceAssumeCapacity(args); if (process.can_replace and options.capture == null) { - if (EnvVar.ZIG_DEBUG_CMD.isSet()) { + if (EnvVar.ZIG_DEBUG_CMD.isSet(env_map)) { const cmd = try std.mem.join(arena, " ", child_argv.items); std.debug.print("{s}\n", .{cmd}); } - const err = process.execv(gpa, child_argv.items); + const err = process.replace(io, .{ .argv = child_argv.items, .env_map = env_map }); const cmd = try std.mem.join(arena, " ", child_argv.items); fatal("the following command failed to execve with '{t}':\n{s}", .{ err, cmd }); } if (!process.can_spawn) { const cmd = try std.mem.join(arena, " ", child_argv.items); - fatal("the following command cannot be executed ({s} does not support spawning a child process):\n{s}", .{ - @tagName(native_os), cmd, + fatal("the following command cannot be executed ({t} does not support spawning a child process):\n{s}", .{ + native_os, cmd, }); } - var child = std.process.Child.init(child_argv.items, gpa); - child.stdin_behavior = .inherit; - child.stdout_behavior = if (options.capture == null) .inherit else .pipe; - child.stderr_behavior = .inherit; - - const term = t: { + switch (t: { _ = try io.lockStderr(&.{}, .no_color); defer io.unlockStderr(); - try child.spawn(io); + + var child = std.process.spawn(io, .{ + .argv = child_argv.items, + .stdin = .inherit, + .stdout = if (options.capture == null) .inherit else .pipe, + .stderr = .inherit, + }) catch |err| fatal("failed to spawn {s}: {t}", .{ child_argv.items[0], err }); + defer child.kill(io); if (options.capture) |ptr| { var stdout_reader = child.stdout.?.readerStreaming(io, &.{}); @@ -5671,9 +5703,8 @@ fn jitCmd( } break :t try child.wait(io); - }; - switch (term) { - .Exited => |code| { + }) { + .exited => |code| { if (code == 0) { if (options.capture != null) return; return cleanExit(io); @@ -5681,6 +5712,10 @@ fn jitCmd( const cmd = try std.mem.join(arena, " ", child_argv.items); fatal("the following build command failed with exit code {d}:\n{s}", .{ code, cmd }); }, + .signal => |sig| { + const cmd = try std.mem.join(arena, " ", child_argv.items); + fatal("the following build command terminated with signal {t}:\n{s}", .{ sig, cmd }); + }, else => { const cmd = try std.mem.join(arena, " ", child_argv.items); fatal("the following build command crashed:\n{s}", .{cmd}); @@ -5796,7 +5831,7 @@ pub fn lldMain( return @intFromBool(!ok); } -const ArgIteratorResponseFile = process.ArgIteratorGeneral(.{ .comments = true, .single_quotes = true }); +const ArgIteratorResponseFile = process.Args.IteratorGeneral(.{ .comments = true, .single_quotes = true }); /// Initialize the arguments from a Response File. "*.rsp" fn initArgIteratorResponseFile(allocator: Allocator, io: Io, resp_file_path: []const u8) !ArgIteratorResponseFile { @@ -6872,14 +6907,15 @@ fn cmdFetch( arena: Allocator, io: Io, args: []const []const u8, + env_map: *process.Environ.Map, ) !void { dev.check(.fetch_command); const color: Color = .auto; const work_around_btrfs_bug = native_os == .linux and - EnvVar.ZIG_BTRFS_WORKAROUND.isSet(); + EnvVar.ZIG_BTRFS_WORKAROUND.isSet(env_map); var opt_path_or_url: ?[]const u8 = null; - var override_global_cache_dir: ?[]const u8 = try EnvVar.ZIG_GLOBAL_CACHE_DIR.get(arena); + var override_global_cache_dir: ?[]const u8 = EnvVar.ZIG_GLOBAL_CACHE_DIR.get(env_map); var debug_hash: bool = false; var save: union(enum) { no, @@ -6925,7 +6961,7 @@ fn cmdFetch( var http_client: std.http.Client = .{ .allocator = gpa, .io = io }; defer http_client.deinit(); - try http_client.initDefaultProxies(arena); + try http_client.initDefaultProxies(arena, env_map); var root_prog_node = std.Progress.start(io, .{ .root_name = "Fetch", @@ -6933,7 +6969,7 @@ fn cmdFetch( defer root_prog_node.end(); var global_cache_directory: Directory = l: { - const p = override_global_cache_dir orelse try introspect.resolveGlobalCacheDir(arena); + const p = override_global_cache_dir orelse try introspect.resolveGlobalCacheDir(arena, env_map); break :l .{ .handle = try Io.Dir.cwd().createDirPathOpen(io, p, .{}), .path = p, diff --git a/src/print_env.zig b/src/print_env.zig index 3540a58d188605726c41338ccffeb4a6bc382ca1..3f9bf1033efc82ee478d2a0e62422429e58fdb95 100644 --- a/src/print_env.zig +++ b/src/print_env.zig @@ -19,9 +19,10 @@ pub fn cmdEnv( else => void, }, host: *const std.Target, + env_map: *std.process.Environ.Map, ) !void { - const override_lib_dir: ?[]const u8 = try EnvVar.ZIG_LIB_DIR.get(arena); - const override_global_cache_dir: ?[]const u8 = try EnvVar.ZIG_GLOBAL_CACHE_DIR.get(arena); + const override_lib_dir: ?[]const u8 = EnvVar.ZIG_LIB_DIR.get(env_map); + const override_global_cache_dir: ?[]const u8 = EnvVar.ZIG_GLOBAL_CACHE_DIR.get(env_map); const self_exe_path = switch (builtin.target.os.tag) { .wasi => args[0], @@ -38,6 +39,7 @@ pub fn cmdEnv( .global, if (builtin.target.os.tag == .wasi) wasi_preopens, if (builtin.target.os.tag != .wasi) self_exe_path, + env_map, ); defer dirs.deinit(io); @@ -56,8 +58,8 @@ pub fn cmdEnv( try root.field("version", build_options.version, .{}); try root.field("target", triple, .{}); var env = try root.beginStructField("env", .{}); - inline for (@typeInfo(std.zig.EnvVar).@"enum".fields) |field| { - try env.field(field.name, try @field(std.zig.EnvVar, field.name).get(arena), .{}); + inline for (@typeInfo(EnvVar).@"enum".fields) |field| { + try env.field(field.name, @field(EnvVar, field.name).get(env_map), .{}); } try env.end(); try root.end(); -- 2.54.0 From 9009ab2495a6f6c7eaa990d87986f32c85360667 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 31 Dec 2025 18:06:48 -0800 Subject: [PATCH 10/60] std.Io.Threaded: make environ init non-optional and argv0 on systems that need it too. fixes surprising behavior for applications that forget to initialize the environment field. --- lib/compiler/build_runner.zig | 5 +++- lib/compiler/test_runner.zig | 14 +++++++----- lib/std/Build.zig | 16 ++++--------- lib/std/Io/Threaded.zig | 25 ++++++++++++++++---- lib/std/Io/Threaded/test.zig | 25 ++++++++++++++++---- lib/std/process/Environ.zig | 7 ++++++ src/main.zig | 43 ++++++++++++++++++++--------------- 7 files changed, 88 insertions(+), 47 deletions(-) diff --git a/lib/compiler/build_runner.zig b/lib/compiler/build_runner.zig index 91e9d20dbb668f2bbff19bf9852d2ba54863a903..f83456e93d50ed31798002c97e1ed52fb30f745f 100644 --- a/lib/compiler/build_runner.zig +++ b/lib/compiler/build_runner.zig @@ -39,7 +39,10 @@ pub fn main(init: process.Init.Minimal) !void { const args = try init.args.toSlice(arena); - var threaded: std.Io.Threaded = .init(gpa, .{}); + var threaded: std.Io.Threaded = .init(gpa, .{ + .environ = init.environ, + .argv0 = .init(init.args), + }); defer threaded.deinit(); const io = threaded.io(); diff --git a/lib/compiler/test_runner.zig b/lib/compiler/test_runner.zig index 9219c0040b9c0f9bb189e674e82b3496a9177c6c..f0971cfa39579608cda24cfa7b65cc07cdf318c1 100644 --- a/lib/compiler/test_runner.zig +++ b/lib/compiler/test_runner.zig @@ -65,13 +65,13 @@ pub fn main(init: std.process.Init.Minimal) void { } if (listen) { - return mainServer(args) catch @panic("internal test runner failure"); + return mainServer(init) catch @panic("internal test runner failure"); } else { - return mainTerminal(args); + return mainTerminal(init); } } -fn mainServer(args: []const [:0]const u8) !void { +fn mainServer(init: std.process.Init.Minimal) !void { @disableInstrumentation(); var stdin_reader = Io.File.stdin().readerStreaming(runner_threaded_io, &stdin_buffer); var stdout_writer = Io.File.stdout().writerStreaming(runner_threaded_io, &stdout_buffer); @@ -131,7 +131,8 @@ fn mainServer(args: []const [:0]const u8) !void { .run_test => { testing.allocator_instance = .{}; testing.io_instance = .init(testing.allocator, .{ - .argv0 = if (@hasField(Io.Threaded.Argv0, "value")) .{ .value = args[0] } else .{}, + .argv0 = .init(init.args), + .environ = init.environ, }); log_err_count = 0; const index = try server.receiveBody_u32(); @@ -216,7 +217,7 @@ fn mainServer(args: []const [:0]const u8) !void { } } -fn mainTerminal(args: []const [:0]const u8) void { +fn mainTerminal(init: std.process.Init.Minimal) void { @disableInstrumentation(); if (builtin.fuzz) @panic("fuzz test requires server"); @@ -235,7 +236,8 @@ fn mainTerminal(args: []const [:0]const u8) void { for (test_fn_list, 0..) |test_fn, i| { testing.allocator_instance = .{}; testing.io_instance = .init(testing.allocator, .{ - .argv0 = if (@hasField(Io.Threaded.Argv0, "value")) .{ .value = args[0] } else .{}, + .argv0 = .init(init.args), + .environ = init.environ, }); defer { testing.io_instance.deinit(); diff --git a/lib/std/Build.zig b/lib/std/Build.zig index 3ade95ae060d691bb1ab9084250aab3d14e309ab..7e8c782dbdde1a0a1e366eee768a9a8e98b75d69 100644 --- a/lib/std/Build.zig +++ b/lib/std/Build.zig @@ -1881,19 +1881,11 @@ pub fn runAllowFail( /// inside step make() functions. If any errors occur, it fails the build with /// a helpful message. pub fn run(b: *Build, argv: []const []const u8) []u8 { - if (!process.can_spawn) { - std.debug.print("unable to spawn the following command: cannot spawn child process\n{s}\n", .{ - try Step.allocPrintCmd(b.allocator, null, null, argv), - }); - process.exit(1); - } - var code: u8 = undefined; - return b.runAllowFail(argv, &code, .inherit) catch |err| { - const printed_cmd = Step.allocPrintCmd(b.allocator, null, null, argv) catch @panic("OOM"); - std.debug.print("unable to spawn the following command: {t}\n{s}\n", .{ err, printed_cmd }); - process.exit(1); - }; + return b.runAllowFail(argv, &code, .inherit) catch |err| process.fatal( + "the following command failed with {t}:\n{s}", + .{ err, Step.allocPrintCmd(b.allocator, null, null, argv) catch @panic("OOM") }, + ); } pub fn addSearchPrefix(b: *Build, search_prefix: []const u8) void { diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index a94b20ab79e91dad3e28fef0dc7db9d191d7ce03..08c12a565394be0999442e40a27adfb71e568fa9 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -66,12 +66,25 @@ environ: Environ, pub const Argv0 = switch (native_os) { .openbsd, .haiku => struct { - value: ?[*:0]const u8 = null, + value: ?[*:0]const u8, + + pub const empty: Argv0 = .{ .value = null }; + + pub fn init(args: process.Args) Argv0 { + return .{ .value = args.value[0] }; + } + }, + else => struct { + pub const empty: Argv0 = .{}; + + pub fn init(args: process.Args) Argv0 { + _ = args; + return .{}; + } }, - else => struct {}, }; -pub const Environ = struct { +const Environ = struct { /// Unmodified data directly from the OS. block: process.Environ.Block = &.{}, /// Protected by `mutex`. Determines whether the other fields have been @@ -1141,7 +1154,8 @@ pub const InitOptions = struct { /// Affects the following operations: /// * `fileIsTty` /// * `processExecutablePath` on OpenBSD and Haiku (observes "PATH"). - environ: Environ = .{}, + /// * `processSpawn`, `processSpawnPath`, `processReplace`, `processReplacePath` + environ: process.Environ, }; /// Related: @@ -1171,8 +1185,9 @@ pub fn init( .old_sig_pipe = undefined, .have_signal_handler = false, .argv0 = options.argv0, - .environ = options.environ, .worker_threads = .init(null), + .environ = .{ .block = options.environ.block }, + .robust_cancel = options.robust_cancel, }; if (posix.Sigaction != void) { diff --git a/lib/std/Io/Threaded/test.zig b/lib/std/Io/Threaded/test.zig index 74bba51b90a01a2ab8e55d0bda2775dfec1cf9ef..f41493653fb62769323a24c61b2fe7102bb6e753 100644 --- a/lib/std/Io/Threaded/test.zig +++ b/lib/std/Io/Threaded/test.zig @@ -13,7 +13,10 @@ test "concurrent vs main prevents deadlock via oversubscription" { return error.SkipZigTest; } - var threaded: Io.Threaded = .init(std.testing.allocator, .{}); + var threaded: Io.Threaded = .init(std.testing.allocator, .{ + .argv0 = .empty, + .environ = .empty, + }); defer threaded.deinit(); const io = threaded.io(); @@ -46,7 +49,10 @@ test "concurrent vs concurrent prevents deadlock via oversubscription" { return error.SkipZigTest; } - var threaded: Io.Threaded = .init(std.testing.allocator, .{}); + var threaded: Io.Threaded = .init(std.testing.allocator, .{ + .argv0 = .empty, + .environ = .empty, + }); defer threaded.deinit(); const io = threaded.io(); @@ -80,7 +86,10 @@ test "async/concurrent context and result alignment" { var buffer: [2048]u8 align(@alignOf(ByteArray512)) = undefined; var fba: std.heap.FixedBufferAllocator = .init(&buffer); - var threaded: std.Io.Threaded = .init(fba.allocator(), .{}); + var threaded: std.Io.Threaded = .init(fba.allocator(), .{ + .argv0 = .empty, + .environ = .empty, + }); defer threaded.deinit(); const io = threaded.io(); @@ -113,7 +122,10 @@ test "Group.async context alignment" { var buffer: [2048]u8 align(@alignOf(ByteArray512)) = undefined; var fba: std.heap.FixedBufferAllocator = .init(&buffer); - var threaded: std.Io.Threaded = .init(fba.allocator(), .{}); + var threaded: std.Io.Threaded = .init(fba.allocator(), .{ + .argv0 = .empty, + .environ = .empty, + }); defer threaded.deinit(); const io = threaded.io(); @@ -133,7 +145,10 @@ fn returnArray() [32]u8 { } test "async with array return type" { - var threaded: std.Io.Threaded = .init(std.testing.allocator, .{}); + var threaded: std.Io.Threaded = .init(std.testing.allocator, .{ + .argv0 = .empty, + .environ = .empty, + }); defer threaded.deinit(); const io = threaded.io(); diff --git a/lib/std/process/Environ.zig b/lib/std/process/Environ.zig index 3581634516613cde402ad908490633d86e04f9ac..2556ae558eaa79bb659a511b8f949ad0c9a697ee 100644 --- a/lib/std/process/Environ.zig +++ b/lib/std/process/Environ.zig @@ -19,6 +19,13 @@ const mem = std.mem; /// queried and heap-allocated at runtime. block: Block, +pub const empty: Environ = .{ + .block = switch (@TypeOf(Block)) { + void => {}, + else => &.{}, + }, +}; + pub const Block = switch (native_os) { .windows => [*:0]const u16, .wasi => switch (builtin.link_libc) { diff --git a/src/main.zig b/src/main.zig index a265b22512c50ed91c475cd0e1453042f4799eb9..a7b6efa33fe4f53d5823ae44b8a32ceecda81140 100644 --- a/src/main.zig +++ b/src/main.zig @@ -186,36 +186,43 @@ pub fn main(init: std.process.Init.Minimal) anyerror!void { if (args.len > 0) crash_report.zig_argv0 = args[0]; - var env_map = init.environ.createMap(arena) catch |err| fatal("failed to parse environment: {t}", .{err}); - - if (tracy.enable_allocation) { - var gpa_tracy = tracy.tracyAllocator(gpa); - return mainArgs(gpa_tracy.allocator(), arena, args, &env_map); - } - - if (native_os == .wasi) { - wasi_preopens = try fs.wasi.preopensAlloc(arena); - } - - return mainArgs(gpa, arena, args, &env_map); -} - -fn mainArgs(gpa: Allocator, arena: Allocator, args: []const [:0]const u8, env_map: *process.Environ.Map) !void { - Compilation.setMainThread(); - if (args.len <= 1) { std.log.info("{s}", .{usage}); fatal("expected command argument", .{}); } + var env_map = init.environ.createMap(arena) catch |err| fatal("failed to parse environment: {t}", .{err}); + + Compilation.setMainThread(); + var threaded: Io.Threaded = .init(gpa, .{ - .argv0 = if (@hasField(Io.Threaded.Argv0, "value")) .{ .value = args[0] } else .{}, + .argv0 = .init(init.args), + .environ = init.environ, }); defer threaded.deinit(); threaded_impl_ptr = &threaded; threaded.stack_size = thread_stack_size; const io = threaded.io(); + if (tracy.enable_allocation) { + var gpa_tracy = tracy.tracyAllocator(gpa); + return mainArgs(gpa_tracy.allocator(), arena, io, args, &env_map); + } + + if (native_os == .wasi) { + wasi_preopens = try fs.wasi.preopensAlloc(arena); + } + + return mainArgs(gpa, arena, io, args, &env_map); +} + +fn mainArgs( + gpa: Allocator, + arena: Allocator, + io: Io, + args: []const [:0]const u8, + env_map: *process.Environ.Map, +) !void { if (process.can_replace and EnvVar.ZIG_IS_DETECTING_LIBC_PATHS.isSet(env_map)) { dev.check(.cc_command); // In this case we have accidentally invoked ourselves as "the system C compiler" -- 2.54.0 From f9585ad01fb1c5709fe448128b1a75cdf3597575 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 31 Dec 2025 18:28:51 -0800 Subject: [PATCH 11/60] update tests and tools to new main API --- test/standalone/dirname/exists_in.zig | 12 ++---------- test/standalone/dirname/has_basename.zig | 12 ++---------- test/standalone/dirname/touch.zig | 12 ++---------- tools/dump-cov.zig | 19 +++++-------------- tools/fetch_them_macos_headers.zig | 9 +++------ tools/gen_macos_headers_c.zig | 16 ++++------------ tools/gen_outline_atomics.zig | 11 +++-------- tools/generate_JSONTestSuite.zig | 10 +++------- tools/generate_c_size_and_align_checks.zig | 13 +++---------- tools/generate_linux_syscalls.zig | 11 +++-------- tools/incr-check.zig | 14 +++----------- 11 files changed, 33 insertions(+), 106 deletions(-) diff --git a/test/standalone/dirname/exists_in.zig b/test/standalone/dirname/exists_in.zig index ba2de2777fe2c05fb3d6c61e366a127543337915..1b6dedc0f6e6201b2a5e754dec3964696ac4e80b 100644 --- a/test/standalone/dirname/exists_in.zig +++ b/test/standalone/dirname/exists_in.zig @@ -11,16 +11,8 @@ const std = @import("std"); -pub fn main() !void { - var arena_state = std.heap.ArenaAllocator.init(std.heap.page_allocator); - const arena = arena_state.allocator(); - defer arena_state.deinit(); - - try run(arena); -} - -fn run(allocator: std.mem.Allocator) !void { - var args = try std.process.argsWithAllocator(allocator); +pub fn main(init: std.process.Init) !void { + var args = try init.args.iterateAllocator(init.arena); defer args.deinit(); _ = args.next() orelse unreachable; // skip binary name diff --git a/test/standalone/dirname/has_basename.zig b/test/standalone/dirname/has_basename.zig index 49eefa3b48e9b79b5794c17a1334602465a07090..c8b769d9172fa6fa2b35ea594904c969ad5965f3 100644 --- a/test/standalone/dirname/has_basename.zig +++ b/test/standalone/dirname/has_basename.zig @@ -13,16 +13,8 @@ const std = @import("std"); -pub fn main() !void { - var arena_state = std.heap.ArenaAllocator.init(std.heap.page_allocator); - const arena = arena_state.allocator(); - defer arena_state.deinit(); - - try run(arena); -} - -fn run(allocator: std.mem.Allocator) !void { - var args = try std.process.argsWithAllocator(allocator); +pub fn main(init: std.process.Init) !void { + var args = try init.args.iterateAllocator(init.arena); defer args.deinit(); _ = args.next() orelse unreachable; // skip binary name diff --git a/test/standalone/dirname/touch.zig b/test/standalone/dirname/touch.zig index 134d53d2fc75ae6919ac091125e7c9c92968a133..bd47a95089da61508ae1eef96ad888a2b14da185 100644 --- a/test/standalone/dirname/touch.zig +++ b/test/standalone/dirname/touch.zig @@ -8,16 +8,8 @@ const std = @import("std"); -pub fn main() !void { - var arena_state = std.heap.ArenaAllocator.init(std.heap.page_allocator); - const arena = arena_state.allocator(); - defer arena_state.deinit(); - - try run(arena); -} - -fn run(allocator: std.mem.Allocator) !void { - var args = try std.process.argsWithAllocator(allocator); +pub fn main(init: std.process.Init) !void { + var args = try init.args.iterateAllocator(init.arena); defer args.deinit(); _ = args.next() orelse unreachable; // skip binary name diff --git a/tools/dump-cov.zig b/tools/dump-cov.zig index 1a8ebb324e57becbb8a60a42333086e2ce1d529c..b264f8d0322bdd6ee7e8c5222b3af900ca59a0a8 100644 --- a/tools/dump-cov.zig +++ b/tools/dump-cov.zig @@ -8,20 +8,11 @@ const Path = std.Build.Cache.Path; const assert = std.debug.assert; const SeenPcsHeader = std.Build.abi.fuzz.SeenPcsHeader; -pub fn main() !void { - var debug_allocator: std.heap.DebugAllocator(.{}) = .init; - defer _ = debug_allocator.deinit(); - const gpa = debug_allocator.allocator(); - - var arena_instance: std.heap.ArenaAllocator = .init(gpa); - defer arena_instance.deinit(); - const arena = arena_instance.allocator(); - - var threaded: Io.Threaded = .init(gpa, .{}); - defer threaded.deinit(); - const io = threaded.io(); - - const args = try std.process.argsAlloc(arena); +pub fn main(init: std.process.Init) !void { + const gpa = init.gpa; + const arena = init.arena; + const io = init.io; + const args = try init.args.toSlice(arena); const target_query_str = switch (args.len) { 3 => "native", diff --git a/tools/fetch_them_macos_headers.zig b/tools/fetch_them_macos_headers.zig index c55a569e9fdbac6ceb1ee05f42ce7e84a6f7ab32..d52ac9173a42b39a72fad971739be61093a33984 100644 --- a/tools/fetch_them_macos_headers.zig +++ b/tools/fetch_them_macos_headers.zig @@ -66,12 +66,9 @@ const usage = \\-h, --help Print this help and exit ; -pub fn main() anyerror!void { - var arena = std.heap.ArenaAllocator.init(gpa); - defer arena.deinit(); - const allocator = arena.allocator(); - - const args = try std.process.argsAlloc(allocator); +pub fn main(init: std.process.Init) !void { + const allocator = init.arena; + const args = try init.args.toSlice(allocator); var argv = std.array_list.Managed([]const u8).init(allocator); var sysroot: ?[]const u8 = null; diff --git a/tools/gen_macos_headers_c.zig b/tools/gen_macos_headers_c.zig index 95880fe3424b1be2949ba05de16a83918c8b2ee1..960b480b15d4896d079721de3b79f7cd1592239d 100644 --- a/tools/gen_macos_headers_c.zig +++ b/tools/gen_macos_headers_c.zig @@ -6,9 +6,6 @@ const info = std.log.info; const fatal = std.process.fatal; const Allocator = std.mem.Allocator; -var general_purpose_allocator = std.heap.GeneralPurposeAllocator(.{}){}; -const gpa = general_purpose_allocator.allocator(); - const usage = \\gen_macos_headers_c [dir] \\ @@ -16,16 +13,11 @@ const usage = \\-h, --help Print this help and exit ; -pub fn main() anyerror!void { - var arena_allocator = std.heap.ArenaAllocator.init(gpa); - defer arena_allocator.deinit(); - const arena = arena_allocator.allocator(); +pub fn main(init: std.process.Init) !void { + const arena = init.arena; + const io = init.io; - var threaded: Io.Threaded = .init(gpa, .{}); - defer threaded.deinit(); - const io = threaded.io(); - - const args = try std.process.argsAlloc(arena); + const args = try init.args.toSlice(arena); if (args.len == 1) fatal("no command or option specified", .{}); var positionals = std.array_list.Managed([]const u8).init(arena); diff --git a/tools/gen_outline_atomics.zig b/tools/gen_outline_atomics.zig index 4d87e531bd823c3cc2c64ba269f15507ea3b2390..3a33c333d5f1c162df9dfb2e6136e1e5a15d6acd 100644 --- a/tools/gen_outline_atomics.zig +++ b/tools/gen_outline_atomics.zig @@ -11,14 +11,9 @@ const AtomicOp = enum { ldset, }; -pub fn main() !void { - var arena_instance = std.heap.ArenaAllocator.init(std.heap.page_allocator); - defer arena_instance.deinit(); - const arena = arena_instance.allocator(); - - var threaded: std.Io.Threaded = .init(arena, .{}); - defer threaded.deinit(); - const io = threaded.io(); +pub fn main(init: std.process.Init) !void { + const arena = init.arena; + const io = init.io; //const args = try std.process.argsAlloc(arena); diff --git a/tools/generate_JSONTestSuite.zig b/tools/generate_JSONTestSuite.zig index e445a1badf150b56b490861e625becd8233a494d..d1bdd2e5cee24a61428ae2bd51013fe5abf68b27 100644 --- a/tools/generate_JSONTestSuite.zig +++ b/tools/generate_JSONTestSuite.zig @@ -3,13 +3,9 @@ const std = @import("std"); const Io = std.Io; -pub fn main() !void { - var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init; - var allocator = gpa.allocator(); - - var threaded: std.Io.Threaded = .init(allocator, .{}); - defer threaded.deinit(); - const io = threaded.io(); +pub fn main(init: std.process.Init) !void { + const allocator = init.gpa; + const io = init.io; var stdout_buffer: [2000]u8 = undefined; var stdout_writer = Io.File.stdout().writerStreaming(io, &stdout_buffer); diff --git a/tools/generate_c_size_and_align_checks.zig b/tools/generate_c_size_and_align_checks.zig index 833fa50f5c21a044283e245166e285e766d012ee..48c35f07d94f1db54ed61f5783f60caab5c93436 100644 --- a/tools/generate_c_size_and_align_checks.zig +++ b/tools/generate_c_size_and_align_checks.zig @@ -28,22 +28,15 @@ fn cName(ty: std.Target.CType) []const u8 { var general_purpose_allocator: std.heap.GeneralPurposeAllocator(.{}) = .init; -pub fn main() !void { - const gpa = general_purpose_allocator.allocator(); - defer std.debug.assert(general_purpose_allocator.deinit() == .ok); - - const args = try std.process.argsAlloc(gpa); - defer std.process.argsFree(gpa, args); +pub fn main(init: std.process.Init) !void { + const args = try init.args.toSlice(init.arena); + const io = init.io; if (args.len != 2) { std.debug.print("Usage: {s} [target_triple]\n", .{args[0]}); std.process.exit(1); } - var threaded: std.Io.Threaded = .init(gpa, .{}); - defer threaded.deinit(); - const io = threaded.io(); - const query = try std.Target.Query.parse(.{ .arch_os_abi = args[1] }); const target = try std.zig.system.resolveTargetQuery(io, query); diff --git a/tools/generate_linux_syscalls.zig b/tools/generate_linux_syscalls.zig index fd632a6c97bd44eb7f9786da2d3047006d37bc92..c9740a7f70e71d308416f0fd71ded045e79b0279 100644 --- a/tools/generate_linux_syscalls.zig +++ b/tools/generate_linux_syscalls.zig @@ -170,14 +170,9 @@ const architectures: []const Arch = &.{ // .{ .@"var" = "Microblaze", .table = .{ .specific = "arch/microblaze/kernel/syscalls/syscall.tbl" } }, }; -pub fn main() !void { - var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); - defer arena.deinit(); - const gpa = arena.allocator(); - - var threaded: Io.Threaded = .init(gpa, .{}); - defer threaded.deinit(); - const io = threaded.io(); +pub fn main(init: std.process.Init) !void { + const gpa = init.gpa; + const io = init.io; const args = try std.process.argsAlloc(gpa); if (args.len < 2 or mem.eql(u8, args[1], "--help")) { diff --git a/tools/incr-check.zig b/tools/incr-check.zig index 05a6afc8b846818deb52877fe19e1c02f838541e..06b6daa648f352eb5487a014d59e88a8925a5cbb 100644 --- a/tools/incr-check.zig +++ b/tools/incr-check.zig @@ -27,18 +27,10 @@ fn logImpl( ); } -pub fn main() !void { +pub fn main(init: std.process.Init) !void { const fatal = std.process.fatal; - - var arena_instance = std.heap.ArenaAllocator.init(std.heap.page_allocator); - defer arena_instance.deinit(); - const arena = arena_instance.allocator(); - - const gpa = arena; - - var threaded: Io.Threaded = .init(gpa, .{}); - defer threaded.deinit(); - const io = threaded.io(); + const arena = init.arena; + const io = init.io; var opt_zig_exe: ?[]const u8 = null; var opt_input_file_name: ?[]const u8 = null; -- 2.54.0 From b64491f2d6e7b6d6d643ce1eff6d24873e414f08 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 1 Jan 2026 13:44:36 -0800 Subject: [PATCH 12/60] compiler_rt: ensure no std.Io used when not testing --- lib/compiler_rt.zig | 18 +++++++++++++++++- lib/compiler_rt/absvdi2.zig | 2 -- lib/compiler_rt/absvsi2.zig | 2 -- lib/compiler_rt/absvti2.zig | 2 -- lib/compiler_rt/adddf3.zig | 2 -- lib/compiler_rt/addhf3.zig | 2 -- lib/compiler_rt/addsf3.zig | 2 -- lib/compiler_rt/addtf3.zig | 2 -- lib/compiler_rt/addvdi3.zig | 2 -- lib/compiler_rt/addvsi3.zig | 2 -- lib/compiler_rt/addxf3.zig | 2 -- lib/compiler_rt/arm.zig | 2 -- lib/compiler_rt/atomics.zig | 1 - lib/compiler_rt/aulldiv.zig | 2 -- lib/compiler_rt/aullrem.zig | 2 -- lib/compiler_rt/bitreverse.zig | 2 -- lib/compiler_rt/bswap.zig | 2 -- lib/compiler_rt/ceil.zig | 2 -- lib/compiler_rt/clear_cache.zig | 1 - lib/compiler_rt/cmp.zig | 2 -- lib/compiler_rt/cmpdf2.zig | 2 -- lib/compiler_rt/cmphf2.zig | 2 -- lib/compiler_rt/cmpsf2.zig | 2 -- lib/compiler_rt/cmptf2.zig | 2 -- lib/compiler_rt/cmpxf2.zig | 2 -- lib/compiler_rt/common.zig | 4 ---- lib/compiler_rt/cos.zig | 2 -- lib/compiler_rt/count0bits.zig | 2 -- lib/compiler_rt/divdf3.zig | 2 -- lib/compiler_rt/divsf3.zig | 2 -- lib/compiler_rt/divtf3.zig | 2 -- lib/compiler_rt/divti3.zig | 2 -- lib/compiler_rt/divxf3.zig | 2 -- lib/compiler_rt/emutls.zig | 2 -- lib/compiler_rt/exp.zig | 2 -- lib/compiler_rt/exp2.zig | 2 -- lib/compiler_rt/extenddftf2.zig | 2 -- lib/compiler_rt/extenddfxf2.zig | 2 -- lib/compiler_rt/extendhfdf2.zig | 2 -- lib/compiler_rt/extendhfsf2.zig | 2 -- lib/compiler_rt/extendhftf2.zig | 2 -- lib/compiler_rt/extendhfxf2.zig | 2 -- lib/compiler_rt/extendsfdf2.zig | 2 -- lib/compiler_rt/extendsftf2.zig | 2 -- lib/compiler_rt/extendsfxf2.zig | 2 -- lib/compiler_rt/extendxftf2.zig | 2 -- lib/compiler_rt/fabs.zig | 2 -- lib/compiler_rt/fixdfdi.zig | 2 -- lib/compiler_rt/fixdfei.zig | 2 -- lib/compiler_rt/fixdfsi.zig | 2 -- lib/compiler_rt/fixdfti.zig | 2 -- lib/compiler_rt/fixhfdi.zig | 2 -- lib/compiler_rt/fixhfei.zig | 2 -- lib/compiler_rt/fixhfsi.zig | 2 -- lib/compiler_rt/fixhfti.zig | 2 -- lib/compiler_rt/fixsfdi.zig | 2 -- lib/compiler_rt/fixsfei.zig | 2 -- lib/compiler_rt/fixsfsi.zig | 2 -- lib/compiler_rt/fixsfti.zig | 2 -- lib/compiler_rt/fixtfdi.zig | 2 -- lib/compiler_rt/fixtfei.zig | 2 -- lib/compiler_rt/fixtfsi.zig | 2 -- lib/compiler_rt/fixtfti.zig | 2 -- lib/compiler_rt/fixunsdfdi.zig | 2 -- lib/compiler_rt/fixunsdfei.zig | 2 -- lib/compiler_rt/fixunsdfsi.zig | 2 -- lib/compiler_rt/fixunsdfti.zig | 2 -- lib/compiler_rt/fixunshfdi.zig | 2 -- lib/compiler_rt/fixunshfei.zig | 2 -- lib/compiler_rt/fixunshfsi.zig | 2 -- lib/compiler_rt/fixunshfti.zig | 2 -- lib/compiler_rt/fixunssfdi.zig | 2 -- lib/compiler_rt/fixunssfei.zig | 2 -- lib/compiler_rt/fixunssfsi.zig | 2 -- lib/compiler_rt/fixunssfti.zig | 2 -- lib/compiler_rt/fixunstfdi.zig | 2 -- lib/compiler_rt/fixunstfei.zig | 2 -- lib/compiler_rt/fixunstfsi.zig | 2 -- lib/compiler_rt/fixunstfti.zig | 2 -- lib/compiler_rt/fixunsxfdi.zig | 2 -- lib/compiler_rt/fixunsxfei.zig | 2 -- lib/compiler_rt/fixunsxfsi.zig | 2 -- lib/compiler_rt/fixunsxfti.zig | 2 -- lib/compiler_rt/fixxfdi.zig | 2 -- lib/compiler_rt/fixxfei.zig | 2 -- lib/compiler_rt/fixxfsi.zig | 2 -- lib/compiler_rt/fixxfti.zig | 2 -- lib/compiler_rt/floatdidf.zig | 2 -- lib/compiler_rt/floatdihf.zig | 2 -- lib/compiler_rt/floatdisf.zig | 2 -- lib/compiler_rt/floatditf.zig | 2 -- lib/compiler_rt/floatdixf.zig | 2 -- lib/compiler_rt/floateidf.zig | 2 -- lib/compiler_rt/floateihf.zig | 2 -- lib/compiler_rt/floateisf.zig | 2 -- lib/compiler_rt/floateitf.zig | 2 -- lib/compiler_rt/floateixf.zig | 2 -- lib/compiler_rt/floatsidf.zig | 2 -- lib/compiler_rt/floatsihf.zig | 2 -- lib/compiler_rt/floatsisf.zig | 2 -- lib/compiler_rt/floatsitf.zig | 2 -- lib/compiler_rt/floatsixf.zig | 2 -- lib/compiler_rt/floattidf.zig | 2 -- lib/compiler_rt/floattihf.zig | 2 -- lib/compiler_rt/floattisf.zig | 2 -- lib/compiler_rt/floattitf.zig | 2 -- lib/compiler_rt/floattixf.zig | 2 -- lib/compiler_rt/floatundidf.zig | 2 -- lib/compiler_rt/floatundihf.zig | 2 -- lib/compiler_rt/floatundisf.zig | 2 -- lib/compiler_rt/floatunditf.zig | 2 -- lib/compiler_rt/floatundixf.zig | 2 -- lib/compiler_rt/floatuneidf.zig | 2 -- lib/compiler_rt/floatuneihf.zig | 2 -- lib/compiler_rt/floatuneisf.zig | 2 -- lib/compiler_rt/floatuneitf.zig | 2 -- lib/compiler_rt/floatuneixf.zig | 2 -- lib/compiler_rt/floatunsidf.zig | 2 -- lib/compiler_rt/floatunsihf.zig | 2 -- lib/compiler_rt/floatunsisf.zig | 2 -- lib/compiler_rt/floatunsitf.zig | 2 -- lib/compiler_rt/floatunsixf.zig | 2 -- lib/compiler_rt/floatuntidf.zig | 2 -- lib/compiler_rt/floatuntihf.zig | 2 -- lib/compiler_rt/floatuntisf.zig | 2 -- lib/compiler_rt/floatuntitf.zig | 2 -- lib/compiler_rt/floatuntixf.zig | 2 -- lib/compiler_rt/floor.zig | 2 -- lib/compiler_rt/fma.zig | 2 -- lib/compiler_rt/fmax.zig | 2 -- lib/compiler_rt/fmin.zig | 2 -- lib/compiler_rt/fmod.zig | 2 -- lib/compiler_rt/gedf2.zig | 2 -- lib/compiler_rt/gehf2.zig | 2 -- lib/compiler_rt/gesf2.zig | 2 -- lib/compiler_rt/getf2.zig | 2 -- lib/compiler_rt/gexf2.zig | 2 -- lib/compiler_rt/int.zig | 2 -- lib/compiler_rt/log.zig | 2 -- lib/compiler_rt/log10.zig | 2 -- lib/compiler_rt/log2.zig | 2 -- lib/compiler_rt/modti3.zig | 2 -- lib/compiler_rt/mulXi3.zig | 2 -- lib/compiler_rt/muldc3.zig | 2 -- lib/compiler_rt/muldf3.zig | 2 -- lib/compiler_rt/mulhc3.zig | 2 -- lib/compiler_rt/mulhf3.zig | 2 -- lib/compiler_rt/mulo.zig | 2 -- lib/compiler_rt/mulsc3.zig | 2 -- lib/compiler_rt/mulsf3.zig | 2 -- lib/compiler_rt/multc3.zig | 2 -- lib/compiler_rt/multf3.zig | 2 -- lib/compiler_rt/mulvsi3.zig | 2 -- lib/compiler_rt/mulxc3.zig | 2 -- lib/compiler_rt/mulxf3.zig | 2 -- lib/compiler_rt/negXi2.zig | 2 -- lib/compiler_rt/negdf2.zig | 2 -- lib/compiler_rt/neghf2.zig | 2 -- lib/compiler_rt/negsf2.zig | 2 -- lib/compiler_rt/negtf2.zig | 2 -- lib/compiler_rt/negv.zig | 2 -- lib/compiler_rt/negxf2.zig | 2 -- lib/compiler_rt/parity.zig | 2 -- lib/compiler_rt/popcount.zig | 2 -- lib/compiler_rt/powiXf2.zig | 2 -- lib/compiler_rt/round.zig | 2 -- lib/compiler_rt/shift.zig | 2 -- lib/compiler_rt/sin.zig | 2 -- lib/compiler_rt/sincos.zig | 2 -- lib/compiler_rt/sqrt.zig | 2 -- lib/compiler_rt/stack_probe.zig | 2 -- lib/compiler_rt/subdf3.zig | 2 -- lib/compiler_rt/subhf3.zig | 2 -- lib/compiler_rt/subsf3.zig | 2 -- lib/compiler_rt/subtf3.zig | 2 -- lib/compiler_rt/subvdi3.zig | 2 -- lib/compiler_rt/subvsi3.zig | 2 -- lib/compiler_rt/subxf3.zig | 2 -- lib/compiler_rt/tan.zig | 2 -- lib/compiler_rt/trunc.zig | 2 -- lib/compiler_rt/truncdfhf2.zig | 2 -- lib/compiler_rt/truncdfsf2.zig | 2 -- lib/compiler_rt/truncsfhf2.zig | 2 -- lib/compiler_rt/trunctfdf2.zig | 2 -- lib/compiler_rt/trunctfhf2.zig | 2 -- lib/compiler_rt/trunctfsf2.zig | 2 -- lib/compiler_rt/trunctfxf2.zig | 2 -- lib/compiler_rt/truncxfdf2.zig | 2 -- lib/compiler_rt/truncxfhf2.zig | 2 -- lib/compiler_rt/truncxfsf2.zig | 2 -- lib/compiler_rt/udivmodti4.zig | 2 -- lib/compiler_rt/udivti3.zig | 2 -- lib/compiler_rt/umodti3.zig | 2 -- lib/compiler_rt/unorddf2.zig | 2 -- lib/compiler_rt/unordhf2.zig | 2 -- lib/compiler_rt/unordsf2.zig | 2 -- lib/compiler_rt/unordtf2.zig | 2 -- lib/compiler_rt/unordxf2.zig | 2 -- 198 files changed, 17 insertions(+), 395 deletions(-) diff --git a/lib/compiler_rt.zig b/lib/compiler_rt.zig index 040d2c6c411fa32fa244080edaf3a44424d46503..5a21cf7b34a070b26f19dd51171c8a0efa3daab0 100644 --- a/lib/compiler_rt.zig +++ b/lib/compiler_rt.zig @@ -1,7 +1,23 @@ +const std = @import("std"); const builtin = @import("builtin"); const common = @import("compiler_rt/common.zig"); -pub const panic = common.panic; +/// Avoid dragging in the runtime safety mechanisms into this .o file, unless +/// we're trying to test compiler-rt. +pub const panic = if (common.test_safety) + std.debug.FullPanic(std.debug.defaultPanic) +else + std.debug.no_panic; + +pub const std_options_debug_threaded_io: ?*std.Io.Threaded = if (builtin.is_test) + std.Io.Threaded.global_single_threaded +else + null; + +pub const std_options_debug_io: std.Io = if (builtin.is_test) + std.Io.Threaded.global_single_threaded.ioBasic() +else + unreachable; comptime { // Integer routines diff --git a/lib/compiler_rt/absvdi2.zig b/lib/compiler_rt/absvdi2.zig index 8b5e5d8a5de6b4781781ab0c029dfffb3f925ce0..add32608e8a389c6a62139410f356b0c24072981 100644 --- a/lib/compiler_rt/absvdi2.zig +++ b/lib/compiler_rt/absvdi2.zig @@ -1,8 +1,6 @@ const common = @import("./common.zig"); const absv = @import("./absv.zig").absv; -pub const panic = common.panic; - comptime { @export(&__absvdi2, .{ .name = "__absvdi2", .linkage = common.linkage, .visibility = common.visibility }); } diff --git a/lib/compiler_rt/absvsi2.zig b/lib/compiler_rt/absvsi2.zig index 0030813db83c337d0a5d75b5812d52b6d54b461f..fb527cd997f6a16a001683effd5bb789d0080625 100644 --- a/lib/compiler_rt/absvsi2.zig +++ b/lib/compiler_rt/absvsi2.zig @@ -1,8 +1,6 @@ const common = @import("./common.zig"); const absv = @import("./absv.zig").absv; -pub const panic = common.panic; - comptime { @export(&__absvsi2, .{ .name = "__absvsi2", .linkage = common.linkage, .visibility = common.visibility }); } diff --git a/lib/compiler_rt/absvti2.zig b/lib/compiler_rt/absvti2.zig index be232ece2ce0b446a92ef29120f98cbe406a1b3e..1b2ddaaefac6ad7fa139a331fe569bcf11f27cc9 100644 --- a/lib/compiler_rt/absvti2.zig +++ b/lib/compiler_rt/absvti2.zig @@ -1,8 +1,6 @@ const common = @import("./common.zig"); const absv = @import("./absv.zig").absv; -pub const panic = common.panic; - comptime { @export(&__absvti2, .{ .name = "__absvti2", .linkage = common.linkage, .visibility = common.visibility }); } diff --git a/lib/compiler_rt/adddf3.zig b/lib/compiler_rt/adddf3.zig index 3a10f555203ac0bc1630a0e4cc34969d5918a0f6..98fba79bbd5116617489d61e32c0d02122f1a579 100644 --- a/lib/compiler_rt/adddf3.zig +++ b/lib/compiler_rt/adddf3.zig @@ -1,8 +1,6 @@ const common = @import("./common.zig"); const addf3 = @import("./addf3.zig").addf3; -pub const panic = common.panic; - comptime { if (common.want_aeabi) { @export(&__aeabi_dadd, .{ .name = "__aeabi_dadd", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/addhf3.zig b/lib/compiler_rt/addhf3.zig index 48a6328836a26a684473155d20d92afabf208fca..842c88f5d4268c9b3b2030096ba52bb296106dcd 100644 --- a/lib/compiler_rt/addhf3.zig +++ b/lib/compiler_rt/addhf3.zig @@ -1,8 +1,6 @@ const common = @import("./common.zig"); const addf3 = @import("./addf3.zig").addf3; -pub const panic = common.panic; - comptime { @export(&__addhf3, .{ .name = "__addhf3", .linkage = common.linkage, .visibility = common.visibility }); } diff --git a/lib/compiler_rt/addsf3.zig b/lib/compiler_rt/addsf3.zig index c4b7773eb0f0e28801fda6611f42b870cd881111..ef1627ae7ae05d9a83716fa53178b9d50971e040 100644 --- a/lib/compiler_rt/addsf3.zig +++ b/lib/compiler_rt/addsf3.zig @@ -1,8 +1,6 @@ const common = @import("./common.zig"); const addf3 = @import("./addf3.zig").addf3; -pub const panic = common.panic; - comptime { if (common.want_aeabi) { @export(&__aeabi_fadd, .{ .name = "__aeabi_fadd", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/addtf3.zig b/lib/compiler_rt/addtf3.zig index 34e5bdf7afd5d1cf72932c949dc9d2faf4513cb4..d4011f923805cce7f58da56beeadf5c7700b9001 100644 --- a/lib/compiler_rt/addtf3.zig +++ b/lib/compiler_rt/addtf3.zig @@ -1,8 +1,6 @@ const common = @import("./common.zig"); const addf3 = @import("./addf3.zig").addf3; -pub const panic = common.panic; - comptime { if (common.want_ppc_abi) { @export(&__addtf3, .{ .name = "__addkf3", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/addvdi3.zig b/lib/compiler_rt/addvdi3.zig index 03aa9b91c717fb791569dda4f75ddc3dfbf20b57..e7346b717ff87faada5d780b02d0b42e1e30d7b6 100644 --- a/lib/compiler_rt/addvdi3.zig +++ b/lib/compiler_rt/addvdi3.zig @@ -1,8 +1,6 @@ const common = @import("./common.zig"); const testing = @import("std").testing; -pub const panic = common.panic; - comptime { @export(&__addvdi3, .{ .name = "__addvdi3", .linkage = common.linkage, .visibility = common.visibility }); } diff --git a/lib/compiler_rt/addvsi3.zig b/lib/compiler_rt/addvsi3.zig index e688fdba58441d8147fbbede01015e72f71f607c..468047eda1c5bf9440291928d5648ea216be9816 100644 --- a/lib/compiler_rt/addvsi3.zig +++ b/lib/compiler_rt/addvsi3.zig @@ -1,8 +1,6 @@ const common = @import("./common.zig"); const testing = @import("std").testing; -pub const panic = common.panic; - comptime { @export(&__addvsi3, .{ .name = "__addvsi3", .linkage = common.linkage, .visibility = common.visibility }); } diff --git a/lib/compiler_rt/addxf3.zig b/lib/compiler_rt/addxf3.zig index 1aac736750210b626ae8a8e759042a13de71a9a8..e1509f04f5f69f703f243e7a7fc043e2b5dda4c8 100644 --- a/lib/compiler_rt/addxf3.zig +++ b/lib/compiler_rt/addxf3.zig @@ -1,8 +1,6 @@ const common = @import("./common.zig"); const addf3 = @import("./addf3.zig").addf3; -pub const panic = common.panic; - comptime { @export(&__addxf3, .{ .name = "__addxf3", .linkage = common.linkage, .visibility = common.visibility }); } diff --git a/lib/compiler_rt/arm.zig b/lib/compiler_rt/arm.zig index 01775ab06fc9f41e81283a5b992a1c74edbe1249..aa31e855dc13e8b2901bb67602a72e67d71625a6 100644 --- a/lib/compiler_rt/arm.zig +++ b/lib/compiler_rt/arm.zig @@ -6,8 +6,6 @@ const target = builtin.target; const arch = builtin.cpu.arch; const common = @import("common.zig"); -pub const panic = common.panic; - comptime { if (!builtin.is_test) { if (arch.isArm()) { diff --git a/lib/compiler_rt/atomics.zig b/lib/compiler_rt/atomics.zig index 09fd1c2c906c513037ec10909c5907d783482e09..b87b7a6315252a277ed7b6d8f6b001f833885ed9 100644 --- a/lib/compiler_rt/atomics.zig +++ b/lib/compiler_rt/atomics.zig @@ -5,7 +5,6 @@ const cpu = builtin.cpu; const arch = cpu.arch; const linkage = common.linkage; const visibility = common.visibility; -pub const panic = common.panic; // This parameter is true iff the target architecture supports the bare minimum // to implement the atomic load/store intrinsics. diff --git a/lib/compiler_rt/aulldiv.zig b/lib/compiler_rt/aulldiv.zig index 5a202fec00dcda541da5526c7dccb3dfc3ce2687..fbfa18dfd195c295c91514d21d9abce04ba9bea2 100644 --- a/lib/compiler_rt/aulldiv.zig +++ b/lib/compiler_rt/aulldiv.zig @@ -5,8 +5,6 @@ const os = builtin.os.tag; const abi = builtin.abi; const common = @import("common.zig"); -pub const panic = common.panic; - comptime { if (common.want_windows_x86_msvc_abi) { // Don't let LLVM apply the stdcall name mangling on those MSVC builtins diff --git a/lib/compiler_rt/aullrem.zig b/lib/compiler_rt/aullrem.zig index d4365265be254e3c4a6f3a0bc4aad1a87a3180ec..ea12c16a0620c16d27711831bfb753d791403e5d 100644 --- a/lib/compiler_rt/aullrem.zig +++ b/lib/compiler_rt/aullrem.zig @@ -5,8 +5,6 @@ const os = builtin.os.tag; const abi = builtin.abi; const common = @import("common.zig"); -pub const panic = common.panic; - comptime { if (common.want_windows_x86_msvc_abi) { // Don't let LLVM apply the stdcall name mangling on those MSVC builtins diff --git a/lib/compiler_rt/bitreverse.zig b/lib/compiler_rt/bitreverse.zig index fe6cf8988da638d177ca05277a03ed7bb8498f0c..6dbeac77d9e196c8363a4fd8faf5888131c9d858 100644 --- a/lib/compiler_rt/bitreverse.zig +++ b/lib/compiler_rt/bitreverse.zig @@ -2,8 +2,6 @@ const std = @import("std"); const builtin = @import("builtin"); const common = @import("common.zig"); -pub const panic = common.panic; - comptime { @export(&__bitreversesi2, .{ .name = "__bitreversesi2", .linkage = common.linkage, .visibility = common.visibility }); @export(&__bitreversedi2, .{ .name = "__bitreversedi2", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/bswap.zig b/lib/compiler_rt/bswap.zig index 2d6df339dfc22fb7a597e9d8cb4f9dfafe06d478..e6f634c66fb787de4d0671ca622d696c2e930e7f 100644 --- a/lib/compiler_rt/bswap.zig +++ b/lib/compiler_rt/bswap.zig @@ -2,8 +2,6 @@ const std = @import("std"); const builtin = @import("builtin"); const common = @import("common.zig"); -pub const panic = common.panic; - comptime { @export(&__bswapsi2, .{ .name = "__bswapsi2", .linkage = common.linkage, .visibility = common.visibility }); @export(&__bswapdi2, .{ .name = "__bswapdi2", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/ceil.zig b/lib/compiler_rt/ceil.zig index 1900496be97f6eab3f2b20673ffdfbb6ee2549f6..9f797346f6bffa8f0d3b881c0a5369fac7890906 100644 --- a/lib/compiler_rt/ceil.zig +++ b/lib/compiler_rt/ceil.zig @@ -13,8 +13,6 @@ const mem = std.mem; const expect = std.testing.expect; const common = @import("common.zig"); -pub const panic = common.panic; - comptime { @export(&__ceilh, .{ .name = "__ceilh", .linkage = common.linkage, .visibility = common.visibility }); @export(&ceilf, .{ .name = "ceilf", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/clear_cache.zig b/lib/compiler_rt/clear_cache.zig index 9435f66c84fc442aca5781720cda0ec0dfd1a041..0e3b5d371f93e8b8374ff83537ee8aed10357fa5 100644 --- a/lib/compiler_rt/clear_cache.zig +++ b/lib/compiler_rt/clear_cache.zig @@ -3,7 +3,6 @@ const builtin = @import("builtin"); const arch = builtin.cpu.arch; const os = builtin.os.tag; const common = @import("common.zig"); -pub const panic = common.panic; // Ported from llvm-project d32170dbd5b0d54436537b6b75beaf44324e0c28 diff --git a/lib/compiler_rt/cmp.zig b/lib/compiler_rt/cmp.zig index 67cb5b0938fa27c41902bf86111a27ca3a8f436c..cac23a7508f1546d8fe372582bd03e526062868a 100644 --- a/lib/compiler_rt/cmp.zig +++ b/lib/compiler_rt/cmp.zig @@ -2,8 +2,6 @@ const std = @import("std"); const builtin = @import("builtin"); const common = @import("common.zig"); -pub const panic = common.panic; - comptime { @export(&__cmpsi2, .{ .name = "__cmpsi2", .linkage = common.linkage, .visibility = common.visibility }); @export(&__cmpdi2, .{ .name = "__cmpdi2", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/cmpdf2.zig b/lib/compiler_rt/cmpdf2.zig index a0338c7a17920286889650c88b02da7ab021597c..ebec596f270d739e689f3f735f94800a96ed95dc 100644 --- a/lib/compiler_rt/cmpdf2.zig +++ b/lib/compiler_rt/cmpdf2.zig @@ -3,8 +3,6 @@ const common = @import("./common.zig"); const comparef = @import("./comparef.zig"); -pub const panic = common.panic; - comptime { if (common.want_aeabi) { @export(&__aeabi_dcmpeq, .{ .name = "__aeabi_dcmpeq", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/cmphf2.zig b/lib/compiler_rt/cmphf2.zig index 9fcc1e24d53eb7c52425643ee4d8842efb56e085..ea70e71e161fe6131e48632d5a455862455257f7 100644 --- a/lib/compiler_rt/cmphf2.zig +++ b/lib/compiler_rt/cmphf2.zig @@ -3,8 +3,6 @@ const common = @import("./common.zig"); const comparef = @import("./comparef.zig"); -pub const panic = common.panic; - comptime { @export(&__eqhf2, .{ .name = "__eqhf2", .linkage = common.linkage, .visibility = common.visibility }); @export(&__nehf2, .{ .name = "__nehf2", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/cmpsf2.zig b/lib/compiler_rt/cmpsf2.zig index 371319b072ab263938c32a9574b2b135582c49fd..4b200198e662fcdab160b1f5ab79b1914b6c4d65 100644 --- a/lib/compiler_rt/cmpsf2.zig +++ b/lib/compiler_rt/cmpsf2.zig @@ -3,8 +3,6 @@ const common = @import("./common.zig"); const comparef = @import("./comparef.zig"); -pub const panic = common.panic; - comptime { if (common.want_aeabi) { @export(&__aeabi_fcmpeq, .{ .name = "__aeabi_fcmpeq", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/cmptf2.zig b/lib/compiler_rt/cmptf2.zig index d5ee7970d4753d24181572330a67631c67793bce..3188ecf90ec5f180fc500f39530a432354135673 100644 --- a/lib/compiler_rt/cmptf2.zig +++ b/lib/compiler_rt/cmptf2.zig @@ -3,8 +3,6 @@ const common = @import("./common.zig"); const comparef = @import("./comparef.zig"); -pub const panic = common.panic; - comptime { if (common.want_ppc_abi) { @export(&__eqtf2, .{ .name = "__eqkf2", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/cmpxf2.zig b/lib/compiler_rt/cmpxf2.zig index dc90f87c340e98ac0f1bafd480ce8cbbfdab6264..f70de9c0b617eacdb8a9d1a751280c980b07f79c 100644 --- a/lib/compiler_rt/cmpxf2.zig +++ b/lib/compiler_rt/cmpxf2.zig @@ -3,8 +3,6 @@ const common = @import("./common.zig"); const comparef = @import("./comparef.zig"); -pub const panic = common.panic; - comptime { @export(&__eqxf2, .{ .name = "__eqxf2", .linkage = common.linkage, .visibility = common.visibility }); @export(&__nexf2, .{ .name = "__nexf2", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/common.zig b/lib/compiler_rt/common.zig index d74eab18d8fb24bfb89146358675920cdfe9f6cb..a9b4c2296c5974415af57e3d2dc1aba2941f96cf 100644 --- a/lib/compiler_rt/common.zig +++ b/lib/compiler_rt/common.zig @@ -126,10 +126,6 @@ pub const test_safety = switch (builtin.zig_backend) { else => builtin.is_test, }; -// Avoid dragging in the runtime safety mechanisms into this .o file, unless -// we're trying to test compiler-rt. -pub const panic = if (test_safety) std.debug.FullPanic(std.debug.defaultPanic) else std.debug.no_panic; - /// This seems to mostly correspond to `clang::TargetInfo::HasFloat16`. pub fn F16T(comptime OtherType: type) type { return switch (builtin.cpu.arch) { diff --git a/lib/compiler_rt/cos.zig b/lib/compiler_rt/cos.zig index 01a5196232aba4a591957e9f0b083064e7479e4f..3384887385f0faeb4ad3c1b0b5f35e7a189ceb31 100644 --- a/lib/compiler_rt/cos.zig +++ b/lib/compiler_rt/cos.zig @@ -4,8 +4,6 @@ const mem = std.mem; const expect = std.testing.expect; const common = @import("common.zig"); -pub const panic = common.panic; - const trig = @import("trig.zig"); const rem_pio2 = @import("rem_pio2.zig").rem_pio2; const rem_pio2f = @import("rem_pio2f.zig").rem_pio2f; diff --git a/lib/compiler_rt/count0bits.zig b/lib/compiler_rt/count0bits.zig index 874604eb2c60c240e1d4aee1721c7ecbdb62bf0b..3696c899447e5328f1b13b369a0f9e41f1924ebe 100644 --- a/lib/compiler_rt/count0bits.zig +++ b/lib/compiler_rt/count0bits.zig @@ -2,8 +2,6 @@ const std = @import("std"); const builtin = @import("builtin"); const common = @import("common.zig"); -pub const panic = common.panic; - comptime { @export(&__clzsi2, .{ .name = "__clzsi2", .linkage = common.linkage, .visibility = common.visibility }); @export(&__clzdi2, .{ .name = "__clzdi2", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/divdf3.zig b/lib/compiler_rt/divdf3.zig index 7b47cd3a703c0e55e3b630bad6b132383cb812ab..a94544165bdfc11531e877a7c919e6085e60fb39 100644 --- a/lib/compiler_rt/divdf3.zig +++ b/lib/compiler_rt/divdf3.zig @@ -10,8 +10,6 @@ const common = @import("common.zig"); const normalize = common.normalize; const wideMultiply = common.wideMultiply; -pub const panic = common.panic; - comptime { if (common.want_aeabi) { @export(&__aeabi_ddiv, .{ .name = "__aeabi_ddiv", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/divsf3.zig b/lib/compiler_rt/divsf3.zig index a74d3f7abdba7e3b9e205b2e6aa56e1bc7433c62..dca1d49ebe0cdd0f8e1c66fd758ba8f91ea10a09 100644 --- a/lib/compiler_rt/divsf3.zig +++ b/lib/compiler_rt/divsf3.zig @@ -9,8 +9,6 @@ const arch = builtin.cpu.arch; const common = @import("common.zig"); const normalize = common.normalize; -pub const panic = common.panic; - comptime { if (common.want_aeabi) { @export(&__aeabi_fdiv, .{ .name = "__aeabi_fdiv", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/divtf3.zig b/lib/compiler_rt/divtf3.zig index 0bab96de3996960269b80bc0e5a471c3ebe4cba7..6606d0de3808cfc36917c3dc55348106d851eaa1 100644 --- a/lib/compiler_rt/divtf3.zig +++ b/lib/compiler_rt/divtf3.zig @@ -5,8 +5,6 @@ const common = @import("common.zig"); const normalize = common.normalize; const wideMultiply = common.wideMultiply; -pub const panic = common.panic; - comptime { if (common.want_ppc_abi) { @export(&__divtf3, .{ .name = "__divkf3", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/divti3.zig b/lib/compiler_rt/divti3.zig index 5f24d196d721a700ae1c65e3fcea8130f9206573..9eb8dda18741793935b231748f1994b0d19809b0 100644 --- a/lib/compiler_rt/divti3.zig +++ b/lib/compiler_rt/divti3.zig @@ -4,8 +4,6 @@ const udivmod = @import("udivmod.zig").udivmod; const arch = builtin.cpu.arch; const common = @import("common.zig"); -pub const panic = common.panic; - comptime { if (common.want_windows_v2u64_abi) { @export(&__divti3_windows_x86_64, .{ .name = "__divti3", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/divxf3.zig b/lib/compiler_rt/divxf3.zig index 1579db226cc6f93c7183ea0b2d650df311e3b110..069510a2b6872f33db80117c6c9ba873d6b0927d 100644 --- a/lib/compiler_rt/divxf3.zig +++ b/lib/compiler_rt/divxf3.zig @@ -6,8 +6,6 @@ const common = @import("common.zig"); const normalize = common.normalize; const wideMultiply = common.wideMultiply; -pub const panic = common.panic; - comptime { @export(&__divxf3, .{ .name = "__divxf3", .linkage = common.linkage, .visibility = common.visibility }); } diff --git a/lib/compiler_rt/emutls.zig b/lib/compiler_rt/emutls.zig index d081aa92943e7f60401b79dda53c8810c963e497..c52ce020edddb269e4f0f52e789e6381092384cd 100644 --- a/lib/compiler_rt/emutls.zig +++ b/lib/compiler_rt/emutls.zig @@ -15,8 +15,6 @@ const expect = std.testing.expect; /// typedef unsigned int gcc_word __attribute__((mode(word))); const gcc_word = usize; -pub const panic = common.panic; - comptime { if (builtin.link_libc and (builtin.abi.isAndroid() or builtin.abi.isOpenHarmony() or builtin.os.tag == .openbsd)) { @export(&__emutls_get_address, .{ .name = "__emutls_get_address", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/exp.zig b/lib/compiler_rt/exp.zig index fa4356a3366f330b8320913aec2c6230815625c8..f5c55369302434b3bfd42f66ec686543d6b27617 100644 --- a/lib/compiler_rt/exp.zig +++ b/lib/compiler_rt/exp.zig @@ -13,8 +13,6 @@ const expect = std.testing.expect; const expectEqual = std.testing.expectEqual; const common = @import("common.zig"); -pub const panic = common.panic; - comptime { @export(&__exph, .{ .name = "__exph", .linkage = common.linkage, .visibility = common.visibility }); @export(&expf, .{ .name = "expf", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/exp2.zig b/lib/compiler_rt/exp2.zig index ce79dff497481bc03553c6366bfa34eb05c30180..a0a4f757e623b4bbd5d6c5052353d745caf86617 100644 --- a/lib/compiler_rt/exp2.zig +++ b/lib/compiler_rt/exp2.zig @@ -13,8 +13,6 @@ const expect = std.testing.expect; const expectEqual = std.testing.expectEqual; const common = @import("common.zig"); -pub const panic = common.panic; - comptime { @export(&__exp2h, .{ .name = "__exp2h", .linkage = common.linkage, .visibility = common.visibility }); @export(&exp2f, .{ .name = "exp2f", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/extenddftf2.zig b/lib/compiler_rt/extenddftf2.zig index 457a64522a9a9490a27a2e75ff5c6bdf6d0200f8..9ef552c2c55032bcca12bb99c7f3dbbf3f121e5e 100644 --- a/lib/compiler_rt/extenddftf2.zig +++ b/lib/compiler_rt/extenddftf2.zig @@ -1,8 +1,6 @@ const common = @import("./common.zig"); const extendf = @import("./extendf.zig").extendf; -pub const panic = common.panic; - comptime { if (common.want_ppc_abi) { @export(&__extenddftf2, .{ .name = "__extenddfkf2", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/extenddfxf2.zig b/lib/compiler_rt/extenddfxf2.zig index 55a1df56623842f377c37fc1d7a30258fbd98209..1d7bc998d76d2f65c738fa6cc22dcd7f2faf09c4 100644 --- a/lib/compiler_rt/extenddfxf2.zig +++ b/lib/compiler_rt/extenddfxf2.zig @@ -1,8 +1,6 @@ const common = @import("./common.zig"); const extend_f80 = @import("./extendf.zig").extend_f80; -pub const panic = common.panic; - comptime { @export(&__extenddfxf2, .{ .name = "__extenddfxf2", .linkage = common.linkage, .visibility = common.visibility }); } diff --git a/lib/compiler_rt/extendhfdf2.zig b/lib/compiler_rt/extendhfdf2.zig index 7b1a7929f7edf4a578876f3573befe42f43bafcb..3b3b65ec114ceb3e11d743fa664b794c3bc2b773 100644 --- a/lib/compiler_rt/extendhfdf2.zig +++ b/lib/compiler_rt/extendhfdf2.zig @@ -1,8 +1,6 @@ const common = @import("./common.zig"); const extendf = @import("./extendf.zig").extendf; -pub const panic = common.panic; - comptime { @export(&__extendhfdf2, .{ .name = "__extendhfdf2", .linkage = common.linkage, .visibility = common.visibility }); } diff --git a/lib/compiler_rt/extendhfsf2.zig b/lib/compiler_rt/extendhfsf2.zig index 37f3d79c7af55ad1837b44df7a03469ac9110b11..f4f8be4305c846f3f5d9d748c9b7c2b383aa5768 100644 --- a/lib/compiler_rt/extendhfsf2.zig +++ b/lib/compiler_rt/extendhfsf2.zig @@ -1,8 +1,6 @@ const common = @import("./common.zig"); const extendf = @import("./extendf.zig").extendf; -pub const panic = common.panic; - comptime { if (common.gnu_f16_abi) { @export(&__gnu_h2f_ieee, .{ .name = "__gnu_h2f_ieee", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/extendhftf2.zig b/lib/compiler_rt/extendhftf2.zig index 3b1f257fa9aaaea09633ebfb2dbda751dee21ee7..a94b849a5a2c3416e326bfe9d3e5bd041a468174 100644 --- a/lib/compiler_rt/extendhftf2.zig +++ b/lib/compiler_rt/extendhftf2.zig @@ -1,8 +1,6 @@ const common = @import("./common.zig"); const extendf = @import("./extendf.zig").extendf; -pub const panic = common.panic; - comptime { @export(&__extendhftf2, .{ .name = "__extendhftf2", .linkage = common.linkage, .visibility = common.visibility }); } diff --git a/lib/compiler_rt/extendhfxf2.zig b/lib/compiler_rt/extendhfxf2.zig index 74e4fb64cc3c29049684fe4e5234b4a5b5fbc25f..5e205b1f888808ed29d7c8e189c8213ed70817b8 100644 --- a/lib/compiler_rt/extendhfxf2.zig +++ b/lib/compiler_rt/extendhfxf2.zig @@ -1,8 +1,6 @@ const common = @import("./common.zig"); const extend_f80 = @import("./extendf.zig").extend_f80; -pub const panic = common.panic; - comptime { @export(&__extendhfxf2, .{ .name = "__extendhfxf2", .linkage = common.linkage, .visibility = common.visibility }); } diff --git a/lib/compiler_rt/extendsfdf2.zig b/lib/compiler_rt/extendsfdf2.zig index c87b721cdd4014c85be1f41cb1f284fbf2ac00cd..9cc0a436633d5b3910bc2e8b4c790393395cbd10 100644 --- a/lib/compiler_rt/extendsfdf2.zig +++ b/lib/compiler_rt/extendsfdf2.zig @@ -1,8 +1,6 @@ const common = @import("./common.zig"); const extendf = @import("./extendf.zig").extendf; -pub const panic = common.panic; - comptime { if (common.want_aeabi) { @export(&__aeabi_f2d, .{ .name = "__aeabi_f2d", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/extendsftf2.zig b/lib/compiler_rt/extendsftf2.zig index 4e45f85029705509d1d9d1320cbd412eb94c83e5..3690a0733c87db737d1ae6c7508a9c5decf0a0a9 100644 --- a/lib/compiler_rt/extendsftf2.zig +++ b/lib/compiler_rt/extendsftf2.zig @@ -1,8 +1,6 @@ const common = @import("./common.zig"); const extendf = @import("./extendf.zig").extendf; -pub const panic = common.panic; - comptime { if (common.want_ppc_abi) { @export(&__extendsftf2, .{ .name = "__extendsfkf2", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/extendsfxf2.zig b/lib/compiler_rt/extendsfxf2.zig index b883034d4eb2477d10e05432d4d51e652a544b81..8665c445f770c60c7c9d2af732c8c46f0a9ddf50 100644 --- a/lib/compiler_rt/extendsfxf2.zig +++ b/lib/compiler_rt/extendsfxf2.zig @@ -1,8 +1,6 @@ const common = @import("./common.zig"); const extend_f80 = @import("./extendf.zig").extend_f80; -pub const panic = common.panic; - comptime { @export(&__extendsfxf2, .{ .name = "__extendsfxf2", .linkage = common.linkage, .visibility = common.visibility }); } diff --git a/lib/compiler_rt/extendxftf2.zig b/lib/compiler_rt/extendxftf2.zig index 39d66d399ca73f0390183b3270df9fea9a3af64d..cec1fa1f7ce9891734177d7baab3776307bd4657 100644 --- a/lib/compiler_rt/extendxftf2.zig +++ b/lib/compiler_rt/extendxftf2.zig @@ -1,8 +1,6 @@ const std = @import("std"); const common = @import("./common.zig"); -pub const panic = common.panic; - comptime { @export(&__extendxftf2, .{ .name = "__extendxftf2", .linkage = common.linkage, .visibility = common.visibility }); } diff --git a/lib/compiler_rt/fabs.zig b/lib/compiler_rt/fabs.zig index 31c6cb97936255c01ed0a004ad14cf4811ffe82d..9a10f4ce617a62e3e0e0c8a3f78f55053855d6de 100644 --- a/lib/compiler_rt/fabs.zig +++ b/lib/compiler_rt/fabs.zig @@ -3,8 +3,6 @@ const builtin = @import("builtin"); const arch = builtin.cpu.arch; const common = @import("common.zig"); -pub const panic = common.panic; - comptime { @export(&__fabsh, .{ .name = "__fabsh", .linkage = common.linkage, .visibility = common.visibility }); @export(&fabsf, .{ .name = "fabsf", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/fixdfdi.zig b/lib/compiler_rt/fixdfdi.zig index a92c94504389835f53fa1c0f4ca215bc5103a961..97f3be044e2be76206a67d30083b0f54584c66af 100644 --- a/lib/compiler_rt/fixdfdi.zig +++ b/lib/compiler_rt/fixdfdi.zig @@ -2,8 +2,6 @@ const builtin = @import("builtin"); const common = @import("./common.zig"); const intFromFloat = @import("./int_from_float.zig").intFromFloat; -pub const panic = common.panic; - comptime { if (common.want_aeabi) { @export(&__aeabi_d2lz, .{ .name = "__aeabi_d2lz", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/fixdfei.zig b/lib/compiler_rt/fixdfei.zig index 31c7994c58c59062bf8c67cbfbe856eddbb7f503..3565cb45ba2bf2d692cd66273af486c70268611a 100644 --- a/lib/compiler_rt/fixdfei.zig +++ b/lib/compiler_rt/fixdfei.zig @@ -3,8 +3,6 @@ const builtin = @import("builtin"); const common = @import("common.zig"); const bigIntFromFloat = @import("int_from_float.zig").bigIntFromFloat; -pub const panic = common.panic; - comptime { @export(&__fixdfei, .{ .name = "__fixdfei", .linkage = common.linkage, .visibility = common.visibility }); } diff --git a/lib/compiler_rt/fixdfsi.zig b/lib/compiler_rt/fixdfsi.zig index 5076cdc4c07da8f8cb77a3efaa554fab774c1608..b1b26867b9c1899c73efe201c43f04ee95da3a58 100644 --- a/lib/compiler_rt/fixdfsi.zig +++ b/lib/compiler_rt/fixdfsi.zig @@ -1,8 +1,6 @@ const common = @import("./common.zig"); const intFromFloat = @import("./int_from_float.zig").intFromFloat; -pub const panic = common.panic; - comptime { if (common.want_aeabi) { @export(&__aeabi_d2iz, .{ .name = "__aeabi_d2iz", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/fixdfti.zig b/lib/compiler_rt/fixdfti.zig index 46b17505fc5fcab49723c0d17b12426067725011..499811c5511a30c9724aca2fab257efe4b470465 100644 --- a/lib/compiler_rt/fixdfti.zig +++ b/lib/compiler_rt/fixdfti.zig @@ -2,8 +2,6 @@ const builtin = @import("builtin"); const common = @import("./common.zig"); const intFromFloat = @import("./int_from_float.zig").intFromFloat; -pub const panic = common.panic; - comptime { if (common.want_windows_v2u64_abi) { @export(&__fixdfti_windows_x86_64, .{ .name = "__fixdfti", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/fixhfdi.zig b/lib/compiler_rt/fixhfdi.zig index 154bb501ac8a7615bac9638fa65e5c0cf98ee09a..3bcb848972d90eb84012ba83b47e19d4bee78585 100644 --- a/lib/compiler_rt/fixhfdi.zig +++ b/lib/compiler_rt/fixhfdi.zig @@ -1,8 +1,6 @@ const common = @import("./common.zig"); const intFromFloat = @import("./int_from_float.zig").intFromFloat; -pub const panic = common.panic; - comptime { @export(&__fixhfdi, .{ .name = "__fixhfdi", .linkage = common.linkage, .visibility = common.visibility }); } diff --git a/lib/compiler_rt/fixhfei.zig b/lib/compiler_rt/fixhfei.zig index c7f30055c561bb319b83341298eb06ecdd654395..1eef56073fc6331fc74c3c19cd69e95290e4070c 100644 --- a/lib/compiler_rt/fixhfei.zig +++ b/lib/compiler_rt/fixhfei.zig @@ -3,8 +3,6 @@ const builtin = @import("builtin"); const common = @import("common.zig"); const bigIntFromFloat = @import("int_from_float.zig").bigIntFromFloat; -pub const panic = common.panic; - comptime { @export(&__fixhfei, .{ .name = "__fixhfei", .linkage = common.linkage, .visibility = common.visibility }); } diff --git a/lib/compiler_rt/fixhfsi.zig b/lib/compiler_rt/fixhfsi.zig index 253e7b64501bf28dd7da1ab57987284216a647f6..658f13bb71903e92f400bd7a01e2c8b91c3ee703 100644 --- a/lib/compiler_rt/fixhfsi.zig +++ b/lib/compiler_rt/fixhfsi.zig @@ -1,8 +1,6 @@ const common = @import("./common.zig"); const intFromFloat = @import("./int_from_float.zig").intFromFloat; -pub const panic = common.panic; - comptime { @export(&__fixhfsi, .{ .name = "__fixhfsi", .linkage = common.linkage, .visibility = common.visibility }); } diff --git a/lib/compiler_rt/fixhfti.zig b/lib/compiler_rt/fixhfti.zig index 01586578fef9b9f5772304367c01f2c398227c74..1c51ebc056482625c0301cd75d6a552e9702a594 100644 --- a/lib/compiler_rt/fixhfti.zig +++ b/lib/compiler_rt/fixhfti.zig @@ -2,8 +2,6 @@ const builtin = @import("builtin"); const common = @import("./common.zig"); const intFromFloat = @import("./int_from_float.zig").intFromFloat; -pub const panic = common.panic; - comptime { if (common.want_windows_v2u64_abi) { @export(&__fixhfti_windows_x86_64, .{ .name = "__fixhfti", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/fixsfdi.zig b/lib/compiler_rt/fixsfdi.zig index f0f7bb01e3fe50a39a44fe0233406dc265be6448..894d5a0c1353b6a1890decd86d4ba7d41f2366b8 100644 --- a/lib/compiler_rt/fixsfdi.zig +++ b/lib/compiler_rt/fixsfdi.zig @@ -2,8 +2,6 @@ const builtin = @import("builtin"); const common = @import("./common.zig"); const intFromFloat = @import("./int_from_float.zig").intFromFloat; -pub const panic = common.panic; - comptime { if (common.want_aeabi) { @export(&__aeabi_f2lz, .{ .name = "__aeabi_f2lz", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/fixsfei.zig b/lib/compiler_rt/fixsfei.zig index c72e002fb1ffb40e8d66435d6d75f96e9f69953c..efbccfa29956506c7111750aa8f2a972f19bf8a3 100644 --- a/lib/compiler_rt/fixsfei.zig +++ b/lib/compiler_rt/fixsfei.zig @@ -3,8 +3,6 @@ const builtin = @import("builtin"); const common = @import("common.zig"); const bigIntFromFloat = @import("int_from_float.zig").bigIntFromFloat; -pub const panic = common.panic; - comptime { @export(&__fixsfei, .{ .name = "__fixsfei", .linkage = common.linkage, .visibility = common.visibility }); } diff --git a/lib/compiler_rt/fixsfsi.zig b/lib/compiler_rt/fixsfsi.zig index fb83d6922793a831b3db44c1410eb3923c743c74..abb856436985d3cd99d51fcfe02f830dca4ad84b 100644 --- a/lib/compiler_rt/fixsfsi.zig +++ b/lib/compiler_rt/fixsfsi.zig @@ -1,8 +1,6 @@ const common = @import("./common.zig"); const intFromFloat = @import("./int_from_float.zig").intFromFloat; -pub const panic = common.panic; - comptime { if (common.want_aeabi) { @export(&__aeabi_f2iz, .{ .name = "__aeabi_f2iz", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/fixsfti.zig b/lib/compiler_rt/fixsfti.zig index e1d4e7188caf8dfc9545afe265533e977cb4e673..82b1db70dc9409cab195e2dc0a19117047033bf8 100644 --- a/lib/compiler_rt/fixsfti.zig +++ b/lib/compiler_rt/fixsfti.zig @@ -2,8 +2,6 @@ const builtin = @import("builtin"); const common = @import("./common.zig"); const intFromFloat = @import("./int_from_float.zig").intFromFloat; -pub const panic = common.panic; - comptime { if (common.want_windows_v2u64_abi) { @export(&__fixsfti_windows_x86_64, .{ .name = "__fixsfti", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/fixtfdi.zig b/lib/compiler_rt/fixtfdi.zig index ebb7484c625731075ebe83ee409b73cf8d7b4b33..5c41f56595e5bdcddb55fdc63caab5836f0a22f3 100644 --- a/lib/compiler_rt/fixtfdi.zig +++ b/lib/compiler_rt/fixtfdi.zig @@ -1,8 +1,6 @@ const common = @import("./common.zig"); const intFromFloat = @import("./int_from_float.zig").intFromFloat; -pub const panic = common.panic; - comptime { if (common.want_ppc_abi) { @export(&__fixtfdi, .{ .name = "__fixkfdi", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/fixtfei.zig b/lib/compiler_rt/fixtfei.zig index 8daf3754f77e991d9253fe2a06a81e14b8f783a8..980db4c614f89bdef90ad03ea892861724e334ea 100644 --- a/lib/compiler_rt/fixtfei.zig +++ b/lib/compiler_rt/fixtfei.zig @@ -3,8 +3,6 @@ const builtin = @import("builtin"); const common = @import("common.zig"); const bigIntFromFloat = @import("int_from_float.zig").bigIntFromFloat; -pub const panic = common.panic; - comptime { @export(&__fixtfei, .{ .name = "__fixtfei", .linkage = common.linkage, .visibility = common.visibility }); } diff --git a/lib/compiler_rt/fixtfsi.zig b/lib/compiler_rt/fixtfsi.zig index a6d2daf00c189445a53b06b4aa300d82153d6dc0..0d525cf8c4c442175fa7ebf39af2711823f74243 100644 --- a/lib/compiler_rt/fixtfsi.zig +++ b/lib/compiler_rt/fixtfsi.zig @@ -1,8 +1,6 @@ const common = @import("./common.zig"); const intFromFloat = @import("./int_from_float.zig").intFromFloat; -pub const panic = common.panic; - comptime { if (common.want_ppc_abi) { @export(&__fixtfsi, .{ .name = "__fixkfsi", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/fixtfti.zig b/lib/compiler_rt/fixtfti.zig index 44f88e8dd060dd267cc758c37d5451201c3db565..fe71a0f08906d929771ff367b97921b6044a05b8 100644 --- a/lib/compiler_rt/fixtfti.zig +++ b/lib/compiler_rt/fixtfti.zig @@ -2,8 +2,6 @@ const builtin = @import("builtin"); const common = @import("./common.zig"); const intFromFloat = @import("./int_from_float.zig").intFromFloat; -pub const panic = common.panic; - comptime { if (common.want_windows_v2u64_abi) { @export(&__fixtfti_windows_x86_64, .{ .name = "__fixtfti", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/fixunsdfdi.zig b/lib/compiler_rt/fixunsdfdi.zig index 52577361a6889c489a0bab81ab5701fb087a70a9..211963bf3e6f3db00d5db224a7483a044ee44b54 100644 --- a/lib/compiler_rt/fixunsdfdi.zig +++ b/lib/compiler_rt/fixunsdfdi.zig @@ -2,8 +2,6 @@ const builtin = @import("builtin"); const common = @import("./common.zig"); const intFromFloat = @import("./int_from_float.zig").intFromFloat; -pub const panic = common.panic; - comptime { if (common.want_aeabi) { @export(&__aeabi_d2ulz, .{ .name = "__aeabi_d2ulz", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/fixunsdfei.zig b/lib/compiler_rt/fixunsdfei.zig index 886feab66aa5cc5ac1e46068ec599672950f8de7..be5bbf0a8a7a4c51c72609fe76e93b76ff81012f 100644 --- a/lib/compiler_rt/fixunsdfei.zig +++ b/lib/compiler_rt/fixunsdfei.zig @@ -3,8 +3,6 @@ const builtin = @import("builtin"); const common = @import("common.zig"); const bigIntFromFloat = @import("int_from_float.zig").bigIntFromFloat; -pub const panic = common.panic; - comptime { @export(&__fixunsdfei, .{ .name = "__fixunsdfei", .linkage = common.linkage, .visibility = common.visibility }); } diff --git a/lib/compiler_rt/fixunsdfsi.zig b/lib/compiler_rt/fixunsdfsi.zig index 246cbf97eb2441031adc856bdae57c55102a7b22..a8e9197361a79ec78d5d051308155dea204fd60f 100644 --- a/lib/compiler_rt/fixunsdfsi.zig +++ b/lib/compiler_rt/fixunsdfsi.zig @@ -1,8 +1,6 @@ const common = @import("./common.zig"); const intFromFloat = @import("./int_from_float.zig").intFromFloat; -pub const panic = common.panic; - comptime { if (common.want_aeabi) { @export(&__aeabi_d2uiz, .{ .name = "__aeabi_d2uiz", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/fixunsdfti.zig b/lib/compiler_rt/fixunsdfti.zig index b4429a9d7622552c64b72795ad713d347ba58c78..28b79a0733b0cb8a881b4c5556d7ce32690d1069 100644 --- a/lib/compiler_rt/fixunsdfti.zig +++ b/lib/compiler_rt/fixunsdfti.zig @@ -2,8 +2,6 @@ const builtin = @import("builtin"); const common = @import("./common.zig"); const intFromFloat = @import("./int_from_float.zig").intFromFloat; -pub const panic = common.panic; - comptime { if (common.want_windows_v2u64_abi) { @export(&__fixunsdfti_windows_x86_64, .{ .name = "__fixunsdfti", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/fixunshfdi.zig b/lib/compiler_rt/fixunshfdi.zig index dca13e17c4e4c4507245f156ce977e279923ff9d..a949ee99045d07e3a007c074bf974fcb95c28fb6 100644 --- a/lib/compiler_rt/fixunshfdi.zig +++ b/lib/compiler_rt/fixunshfdi.zig @@ -1,8 +1,6 @@ const common = @import("./common.zig"); const intFromFloat = @import("./int_from_float.zig").intFromFloat; -pub const panic = common.panic; - comptime { @export(&__fixunshfdi, .{ .name = "__fixunshfdi", .linkage = common.linkage, .visibility = common.visibility }); } diff --git a/lib/compiler_rt/fixunshfei.zig b/lib/compiler_rt/fixunshfei.zig index 788605a6868e56e81b1cd94e3582d2cc02dcada0..4cb7ef2ba8da0c8d9862cd3ea0e9425429834fba 100644 --- a/lib/compiler_rt/fixunshfei.zig +++ b/lib/compiler_rt/fixunshfei.zig @@ -3,8 +3,6 @@ const builtin = @import("builtin"); const common = @import("common.zig"); const bigIntFromFloat = @import("int_from_float.zig").bigIntFromFloat; -pub const panic = common.panic; - comptime { @export(&__fixunshfei, .{ .name = "__fixunshfei", .linkage = common.linkage, .visibility = common.visibility }); } diff --git a/lib/compiler_rt/fixunshfsi.zig b/lib/compiler_rt/fixunshfsi.zig index 27db84357fae02da10ddfa6b04409796acb64dcf..fca40d1b0a33f07b577ac1bb40388e55b03dd5dd 100644 --- a/lib/compiler_rt/fixunshfsi.zig +++ b/lib/compiler_rt/fixunshfsi.zig @@ -1,8 +1,6 @@ const common = @import("./common.zig"); const intFromFloat = @import("./int_from_float.zig").intFromFloat; -pub const panic = common.panic; - comptime { @export(&__fixunshfsi, .{ .name = "__fixunshfsi", .linkage = common.linkage, .visibility = common.visibility }); } diff --git a/lib/compiler_rt/fixunshfti.zig b/lib/compiler_rt/fixunshfti.zig index c92039212d33682903b4d2a597f8b23e0a384cd8..c61be597e772b6abebe139712a00c0f251747c6b 100644 --- a/lib/compiler_rt/fixunshfti.zig +++ b/lib/compiler_rt/fixunshfti.zig @@ -2,8 +2,6 @@ const builtin = @import("builtin"); const common = @import("./common.zig"); const intFromFloat = @import("./int_from_float.zig").intFromFloat; -pub const panic = common.panic; - comptime { if (common.want_windows_v2u64_abi) { @export(&__fixunshfti_windows_x86_64, .{ .name = "__fixunshfti", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/fixunssfdi.zig b/lib/compiler_rt/fixunssfdi.zig index e05d0377e06f963c1c88c61d3a9e5611b6961b02..f12ec1cd5a9d93ca82e4f02e91c7550f90e44fe4 100644 --- a/lib/compiler_rt/fixunssfdi.zig +++ b/lib/compiler_rt/fixunssfdi.zig @@ -2,8 +2,6 @@ const builtin = @import("builtin"); const common = @import("./common.zig"); const intFromFloat = @import("./int_from_float.zig").intFromFloat; -pub const panic = common.panic; - comptime { if (common.want_aeabi) { @export(&__aeabi_f2ulz, .{ .name = "__aeabi_f2ulz", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/fixunssfei.zig b/lib/compiler_rt/fixunssfei.zig index bb89fc283deabb6debc74987169abfd777b45091..0fb957ed1f79ed31b5f27f5f1b368fec7b4655a6 100644 --- a/lib/compiler_rt/fixunssfei.zig +++ b/lib/compiler_rt/fixunssfei.zig @@ -3,8 +3,6 @@ const builtin = @import("builtin"); const common = @import("common.zig"); const bigIntFromFloat = @import("int_from_float.zig").bigIntFromFloat; -pub const panic = common.panic; - comptime { @export(&__fixunssfei, .{ .name = "__fixunssfei", .linkage = common.linkage, .visibility = common.visibility }); } diff --git a/lib/compiler_rt/fixunssfsi.zig b/lib/compiler_rt/fixunssfsi.zig index d0038add1bb440e34a17f8038562a97d7e22f408..4def4b867fea5cb931fa4af64b040a5d6446f31d 100644 --- a/lib/compiler_rt/fixunssfsi.zig +++ b/lib/compiler_rt/fixunssfsi.zig @@ -1,8 +1,6 @@ const common = @import("./common.zig"); const intFromFloat = @import("./int_from_float.zig").intFromFloat; -pub const panic = common.panic; - comptime { if (common.want_aeabi) { @export(&__aeabi_f2uiz, .{ .name = "__aeabi_f2uiz", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/fixunssfti.zig b/lib/compiler_rt/fixunssfti.zig index 3137fb3bc616a5f2deaa8fa84cdd3264cfbbcfb7..693442417c77cab0c74bff9135025ab86bcde86f 100644 --- a/lib/compiler_rt/fixunssfti.zig +++ b/lib/compiler_rt/fixunssfti.zig @@ -2,8 +2,6 @@ const builtin = @import("builtin"); const common = @import("./common.zig"); const intFromFloat = @import("./int_from_float.zig").intFromFloat; -pub const panic = common.panic; - comptime { if (common.want_windows_v2u64_abi) { @export(&__fixunssfti_windows_x86_64, .{ .name = "__fixunssfti", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/fixunstfdi.zig b/lib/compiler_rt/fixunstfdi.zig index 87a06bc37b2b1afd1f266fb5c0a5e33478a3aaef..f1e1822a311831e70993b892e3ecf8e417f5628f 100644 --- a/lib/compiler_rt/fixunstfdi.zig +++ b/lib/compiler_rt/fixunstfdi.zig @@ -1,8 +1,6 @@ const common = @import("./common.zig"); const intFromFloat = @import("./int_from_float.zig").intFromFloat; -pub const panic = common.panic; - comptime { if (common.want_ppc_abi) { @export(&__fixunstfdi, .{ .name = "__fixunskfdi", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/fixunstfei.zig b/lib/compiler_rt/fixunstfei.zig index 997a100afacc188790560cc5f9aeb4e8ce6ab565..84ac5010c96e9c3113eec67e9ca6f5291787f9e8 100644 --- a/lib/compiler_rt/fixunstfei.zig +++ b/lib/compiler_rt/fixunstfei.zig @@ -3,8 +3,6 @@ const builtin = @import("builtin"); const common = @import("common.zig"); const bigIntFromFloat = @import("int_from_float.zig").bigIntFromFloat; -pub const panic = common.panic; - comptime { @export(&__fixunstfei, .{ .name = "__fixunstfei", .linkage = common.linkage, .visibility = common.visibility }); } diff --git a/lib/compiler_rt/fixunstfsi.zig b/lib/compiler_rt/fixunstfsi.zig index 8a57e2dbc1071c33b37b1c2cbb0e0251a515948e..d3a9bfbed85fa07355dd69fceed197ae5ebfb0d9 100644 --- a/lib/compiler_rt/fixunstfsi.zig +++ b/lib/compiler_rt/fixunstfsi.zig @@ -1,8 +1,6 @@ const common = @import("./common.zig"); const intFromFloat = @import("./int_from_float.zig").intFromFloat; -pub const panic = common.panic; - comptime { if (common.want_ppc_abi) { @export(&__fixunstfsi, .{ .name = "__fixunskfsi", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/fixunstfti.zig b/lib/compiler_rt/fixunstfti.zig index 2c182e3a449f44668f24c82290ab9477ba50a018..92fcd569dc6d939305bbc0daf3c3fa6c52978853 100644 --- a/lib/compiler_rt/fixunstfti.zig +++ b/lib/compiler_rt/fixunstfti.zig @@ -2,8 +2,6 @@ const builtin = @import("builtin"); const common = @import("./common.zig"); const intFromFloat = @import("./int_from_float.zig").intFromFloat; -pub const panic = common.panic; - comptime { if (common.want_windows_v2u64_abi) { @export(&__fixunstfti_windows_x86_64, .{ .name = "__fixunstfti", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/fixunsxfdi.zig b/lib/compiler_rt/fixunsxfdi.zig index 1e10786fc879f918f49ac41c64a4b0423ee400aa..4dffd0253ee533db22eba53b5c5e6334cf0bf593 100644 --- a/lib/compiler_rt/fixunsxfdi.zig +++ b/lib/compiler_rt/fixunsxfdi.zig @@ -1,8 +1,6 @@ const common = @import("./common.zig"); const intFromFloat = @import("./int_from_float.zig").intFromFloat; -pub const panic = common.panic; - comptime { @export(&__fixunsxfdi, .{ .name = "__fixunsxfdi", .linkage = common.linkage, .visibility = common.visibility }); } diff --git a/lib/compiler_rt/fixunsxfei.zig b/lib/compiler_rt/fixunsxfei.zig index 88c7f1824388a68c41dc86cc94cb1adae965da65..dc795e74c691e6cb6c0927ef703163aa5481f5b6 100644 --- a/lib/compiler_rt/fixunsxfei.zig +++ b/lib/compiler_rt/fixunsxfei.zig @@ -3,8 +3,6 @@ const builtin = @import("builtin"); const common = @import("common.zig"); const bigIntFromFloat = @import("int_from_float.zig").bigIntFromFloat; -pub const panic = common.panic; - comptime { @export(&__fixunsxfei, .{ .name = "__fixunsxfei", .linkage = common.linkage, .visibility = common.visibility }); } diff --git a/lib/compiler_rt/fixunsxfsi.zig b/lib/compiler_rt/fixunsxfsi.zig index c876ea0d3e07cb1ee454da430e4950c847116354..8f900076c728c85d317746acd237c3003f184543 100644 --- a/lib/compiler_rt/fixunsxfsi.zig +++ b/lib/compiler_rt/fixunsxfsi.zig @@ -1,8 +1,6 @@ const common = @import("./common.zig"); const intFromFloat = @import("./int_from_float.zig").intFromFloat; -pub const panic = common.panic; - comptime { @export(&__fixunsxfsi, .{ .name = "__fixunsxfsi", .linkage = common.linkage, .visibility = common.visibility }); } diff --git a/lib/compiler_rt/fixunsxfti.zig b/lib/compiler_rt/fixunsxfti.zig index bfe8a2c6e2a632c492257e560dcbda98a1090d5b..39dde2ca1222f8f776f308fbfa1e4802f90c66ef 100644 --- a/lib/compiler_rt/fixunsxfti.zig +++ b/lib/compiler_rt/fixunsxfti.zig @@ -2,8 +2,6 @@ const builtin = @import("builtin"); const common = @import("./common.zig"); const intFromFloat = @import("./int_from_float.zig").intFromFloat; -pub const panic = common.panic; - comptime { if (common.want_windows_v2u64_abi) { @export(&__fixunsxfti_windows_x86_64, .{ .name = "__fixunsxfti", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/fixxfdi.zig b/lib/compiler_rt/fixxfdi.zig index 2477a4bb834d2c5b4fcb69fd9d39226323f7a7b4..155fead1bbe18c813a7a641700c23f3e6e41c431 100644 --- a/lib/compiler_rt/fixxfdi.zig +++ b/lib/compiler_rt/fixxfdi.zig @@ -1,8 +1,6 @@ const common = @import("./common.zig"); const intFromFloat = @import("./int_from_float.zig").intFromFloat; -pub const panic = common.panic; - comptime { @export(&__fixxfdi, .{ .name = "__fixxfdi", .linkage = common.linkage, .visibility = common.visibility }); } diff --git a/lib/compiler_rt/fixxfei.zig b/lib/compiler_rt/fixxfei.zig index 6f28153a66342017354df57144f3b301f26df948..75edfad035cbb30fea542e043343de9570a32f67 100644 --- a/lib/compiler_rt/fixxfei.zig +++ b/lib/compiler_rt/fixxfei.zig @@ -3,8 +3,6 @@ const builtin = @import("builtin"); const common = @import("common.zig"); const bigIntFromFloat = @import("int_from_float.zig").bigIntFromFloat; -pub const panic = common.panic; - comptime { @export(&__fixxfei, .{ .name = "__fixxfei", .linkage = common.linkage, .visibility = common.visibility }); } diff --git a/lib/compiler_rt/fixxfsi.zig b/lib/compiler_rt/fixxfsi.zig index 846df4d00667a09cb4aef42be0fcb76b19df9cd4..8a645bdae1fbe57c78398827589e4b070d563611 100644 --- a/lib/compiler_rt/fixxfsi.zig +++ b/lib/compiler_rt/fixxfsi.zig @@ -1,8 +1,6 @@ const common = @import("./common.zig"); const intFromFloat = @import("./int_from_float.zig").intFromFloat; -pub const panic = common.panic; - comptime { @export(&__fixxfsi, .{ .name = "__fixxfsi", .linkage = common.linkage, .visibility = common.visibility }); } diff --git a/lib/compiler_rt/fixxfti.zig b/lib/compiler_rt/fixxfti.zig index e1db47dc338c61fe938bf3c230c958aaf08427fa..292b9688e7fa1b627edf9943915dba087141bdc7 100644 --- a/lib/compiler_rt/fixxfti.zig +++ b/lib/compiler_rt/fixxfti.zig @@ -2,8 +2,6 @@ const builtin = @import("builtin"); const common = @import("./common.zig"); const intFromFloat = @import("./int_from_float.zig").intFromFloat; -pub const panic = common.panic; - comptime { if (common.want_windows_v2u64_abi) { @export(&__fixxfti_windows_x86_64, .{ .name = "__fixxfti", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/floatdidf.zig b/lib/compiler_rt/floatdidf.zig index 38890e35a4e4e7888f59e4005bc65d01a75345db..f614521478da14f27429c1a413d247d1c035c7e5 100644 --- a/lib/compiler_rt/floatdidf.zig +++ b/lib/compiler_rt/floatdidf.zig @@ -2,8 +2,6 @@ const builtin = @import("builtin"); const common = @import("./common.zig"); const floatFromInt = @import("./float_from_int.zig").floatFromInt; -pub const panic = common.panic; - comptime { if (common.want_aeabi) { @export(&__aeabi_l2d, .{ .name = "__aeabi_l2d", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/floatdihf.zig b/lib/compiler_rt/floatdihf.zig index 37a03e4ed35523bf98dba187b4c9c8be86c3a34a..fd534e5946202527e9b6ce1cef31e970230c384b 100644 --- a/lib/compiler_rt/floatdihf.zig +++ b/lib/compiler_rt/floatdihf.zig @@ -1,8 +1,6 @@ const common = @import("./common.zig"); const floatFromInt = @import("./float_from_int.zig").floatFromInt; -pub const panic = common.panic; - comptime { @export(&__floatdihf, .{ .name = "__floatdihf", .linkage = common.linkage, .visibility = common.visibility }); } diff --git a/lib/compiler_rt/floatdisf.zig b/lib/compiler_rt/floatdisf.zig index a942d7b0b1f6a46865671c570614f28c6931ad5c..1efd9c937ff2b2849db3be096696bff395d55f0a 100644 --- a/lib/compiler_rt/floatdisf.zig +++ b/lib/compiler_rt/floatdisf.zig @@ -2,8 +2,6 @@ const builtin = @import("builtin"); const common = @import("./common.zig"); const floatFromInt = @import("./float_from_int.zig").floatFromInt; -pub const panic = common.panic; - comptime { if (common.want_aeabi) { @export(&__aeabi_l2f, .{ .name = "__aeabi_l2f", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/floatditf.zig b/lib/compiler_rt/floatditf.zig index b5cc262fa35468c7f6975875aa3129349684f3c6..864361b7b3d567f959e1e6cde55e20b40a9d487c 100644 --- a/lib/compiler_rt/floatditf.zig +++ b/lib/compiler_rt/floatditf.zig @@ -1,8 +1,6 @@ const common = @import("./common.zig"); const floatFromInt = @import("./float_from_int.zig").floatFromInt; -pub const panic = common.panic; - comptime { if (common.want_ppc_abi) { @export(&__floatditf, .{ .name = "__floatdikf", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/floatdixf.zig b/lib/compiler_rt/floatdixf.zig index eba666c05394061fa184cd9e1181c376cca0834e..8ac6f06fb5929319ad28e387377b120ae3e95e37 100644 --- a/lib/compiler_rt/floatdixf.zig +++ b/lib/compiler_rt/floatdixf.zig @@ -1,8 +1,6 @@ const common = @import("./common.zig"); const floatFromInt = @import("./float_from_int.zig").floatFromInt; -pub const panic = common.panic; - comptime { @export(&__floatdixf, .{ .name = "__floatdixf", .linkage = common.linkage, .visibility = common.visibility }); } diff --git a/lib/compiler_rt/floateidf.zig b/lib/compiler_rt/floateidf.zig index 96411ae878cc1d5787926a91080795988f51ce4a..6434743939ec3895302925bca84937d42d6a2287 100644 --- a/lib/compiler_rt/floateidf.zig +++ b/lib/compiler_rt/floateidf.zig @@ -3,8 +3,6 @@ const builtin = @import("builtin"); const common = @import("common.zig"); const floatFromBigInt = @import("float_from_int.zig").floatFromBigInt; -pub const panic = common.panic; - comptime { @export(&__floateidf, .{ .name = "__floateidf", .linkage = common.linkage, .visibility = common.visibility }); } diff --git a/lib/compiler_rt/floateihf.zig b/lib/compiler_rt/floateihf.zig index ac9fd355c7f6094b99d9100646c5301290354fa1..013e6d2d79cb1e444e68c10b8a8e24aa42d8d182 100644 --- a/lib/compiler_rt/floateihf.zig +++ b/lib/compiler_rt/floateihf.zig @@ -3,8 +3,6 @@ const builtin = @import("builtin"); const common = @import("common.zig"); const floatFromBigInt = @import("float_from_int.zig").floatFromBigInt; -pub const panic = common.panic; - comptime { @export(&__floateihf, .{ .name = "__floateihf", .linkage = common.linkage, .visibility = common.visibility }); } diff --git a/lib/compiler_rt/floateisf.zig b/lib/compiler_rt/floateisf.zig index f53b75643f7325e507e6e34ab114229022e17c92..eb24b6aa43123cd73d5ea61e89777fa1f66ef507 100644 --- a/lib/compiler_rt/floateisf.zig +++ b/lib/compiler_rt/floateisf.zig @@ -3,8 +3,6 @@ const builtin = @import("builtin"); const common = @import("common.zig"); const floatFromBigInt = @import("float_from_int.zig").floatFromBigInt; -pub const panic = common.panic; - comptime { @export(&__floateisf, .{ .name = "__floateisf", .linkage = common.linkage, .visibility = common.visibility }); } diff --git a/lib/compiler_rt/floateitf.zig b/lib/compiler_rt/floateitf.zig index 3e07c6becbf840d54f71076d91cd4d935e5043e3..e5b50ab7a6b34c44d6d0fe7029037e21114fad70 100644 --- a/lib/compiler_rt/floateitf.zig +++ b/lib/compiler_rt/floateitf.zig @@ -3,8 +3,6 @@ const builtin = @import("builtin"); const common = @import("common.zig"); const floatFromBigInt = @import("float_from_int.zig").floatFromBigInt; -pub const panic = common.panic; - comptime { @export(&__floateitf, .{ .name = "__floateitf", .linkage = common.linkage, .visibility = common.visibility }); } diff --git a/lib/compiler_rt/floateixf.zig b/lib/compiler_rt/floateixf.zig index 65585e2ed30cd8a598f2126befb0fe581b026fda..ffb8445fd337ae8d7311233675435f7e7a3af1f7 100644 --- a/lib/compiler_rt/floateixf.zig +++ b/lib/compiler_rt/floateixf.zig @@ -3,8 +3,6 @@ const builtin = @import("builtin"); const common = @import("common.zig"); const floatFromBigInt = @import("float_from_int.zig").floatFromBigInt; -pub const panic = common.panic; - comptime { @export(&__floateixf, .{ .name = "__floateixf", .linkage = common.linkage, .visibility = common.visibility }); } diff --git a/lib/compiler_rt/floatsidf.zig b/lib/compiler_rt/floatsidf.zig index c3f249a4558a03905a9534c2eba924ecaf0bac3f..435c8db9a465184dc532cba1947a2dd7a4b8bb12 100644 --- a/lib/compiler_rt/floatsidf.zig +++ b/lib/compiler_rt/floatsidf.zig @@ -1,8 +1,6 @@ const common = @import("./common.zig"); const floatFromInt = @import("./float_from_int.zig").floatFromInt; -pub const panic = common.panic; - comptime { if (common.want_aeabi) { @export(&__aeabi_i2d, .{ .name = "__aeabi_i2d", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/floatsihf.zig b/lib/compiler_rt/floatsihf.zig index a5d5d2ba92e8d92dec6fa73974459feef95fbc23..5f2db12585d900e8cd9e36e3b760c2cabcad927b 100644 --- a/lib/compiler_rt/floatsihf.zig +++ b/lib/compiler_rt/floatsihf.zig @@ -1,8 +1,6 @@ const common = @import("./common.zig"); const floatFromInt = @import("./float_from_int.zig").floatFromInt; -pub const panic = common.panic; - comptime { @export(&__floatsihf, .{ .name = "__floatsihf", .linkage = common.linkage, .visibility = common.visibility }); } diff --git a/lib/compiler_rt/floatsisf.zig b/lib/compiler_rt/floatsisf.zig index 133bfaabbd3aee3ea3c52be7350955cb2035674f..9c36387c668c1d4bd7aa3dc371368dab2f47c53a 100644 --- a/lib/compiler_rt/floatsisf.zig +++ b/lib/compiler_rt/floatsisf.zig @@ -1,8 +1,6 @@ const common = @import("./common.zig"); const floatFromInt = @import("./float_from_int.zig").floatFromInt; -pub const panic = common.panic; - comptime { if (common.want_aeabi) { @export(&__aeabi_i2f, .{ .name = "__aeabi_i2f", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/floatsitf.zig b/lib/compiler_rt/floatsitf.zig index 6a5f81ae218ecabe6ddaa766c57836ba45246ec3..750633052dcee4880f17f52684c0b4ee0d36a4dd 100644 --- a/lib/compiler_rt/floatsitf.zig +++ b/lib/compiler_rt/floatsitf.zig @@ -1,8 +1,6 @@ const common = @import("./common.zig"); const floatFromInt = @import("./float_from_int.zig").floatFromInt; -pub const panic = common.panic; - comptime { if (common.want_ppc_abi) { @export(&__floatsitf, .{ .name = "__floatsikf", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/floatsixf.zig b/lib/compiler_rt/floatsixf.zig index 31791eb9cc89dd963d9c76bcab79cd4a131f832d..59bf7ddb99ffd827ab0fe52540ecf7af99a80477 100644 --- a/lib/compiler_rt/floatsixf.zig +++ b/lib/compiler_rt/floatsixf.zig @@ -1,8 +1,6 @@ const common = @import("./common.zig"); const floatFromInt = @import("./float_from_int.zig").floatFromInt; -pub const panic = common.panic; - comptime { @export(&__floatsixf, .{ .name = "__floatsixf", .linkage = common.linkage, .visibility = common.visibility }); } diff --git a/lib/compiler_rt/floattidf.zig b/lib/compiler_rt/floattidf.zig index 420ef9b201480e3062fd1b5f905bc28994c91836..2ac5726b5166ab233125ae44dcf74a4fe11eed5b 100644 --- a/lib/compiler_rt/floattidf.zig +++ b/lib/compiler_rt/floattidf.zig @@ -2,8 +2,6 @@ const builtin = @import("builtin"); const common = @import("./common.zig"); const floatFromInt = @import("./float_from_int.zig").floatFromInt; -pub const panic = common.panic; - comptime { if (common.want_windows_v2u64_abi) { @export(&__floattidf_windows_x86_64, .{ .name = "__floattidf", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/floattihf.zig b/lib/compiler_rt/floattihf.zig index 63f079b3a886eebf84b322c9daa71968608affa7..9ad705870e9eafbd49f2eb9c88d0d83bc7c5315a 100644 --- a/lib/compiler_rt/floattihf.zig +++ b/lib/compiler_rt/floattihf.zig @@ -2,8 +2,6 @@ const builtin = @import("builtin"); const common = @import("./common.zig"); const floatFromInt = @import("./float_from_int.zig").floatFromInt; -pub const panic = common.panic; - comptime { if (common.want_windows_v2u64_abi) { @export(&__floattihf_windows_x86_64, .{ .name = "__floattihf", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/floattisf.zig b/lib/compiler_rt/floattisf.zig index 284580c4b2cc683de00921cac6f8c332a2b19f47..953192f33115a103b27f48a56875ab7176c2af15 100644 --- a/lib/compiler_rt/floattisf.zig +++ b/lib/compiler_rt/floattisf.zig @@ -2,8 +2,6 @@ const builtin = @import("builtin"); const common = @import("./common.zig"); const floatFromInt = @import("./float_from_int.zig").floatFromInt; -pub const panic = common.panic; - comptime { if (common.want_windows_v2u64_abi) { @export(&__floattisf_windows_x86_64, .{ .name = "__floattisf", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/floattitf.zig b/lib/compiler_rt/floattitf.zig index c8d2090457fd63ee21b6eeef7e9bcc79c21bffc7..3973aa4ab294fea0b9f905194bebc914a9532cd5 100644 --- a/lib/compiler_rt/floattitf.zig +++ b/lib/compiler_rt/floattitf.zig @@ -2,8 +2,6 @@ const builtin = @import("builtin"); const common = @import("./common.zig"); const floatFromInt = @import("./float_from_int.zig").floatFromInt; -pub const panic = common.panic; - comptime { if (common.want_windows_v2u64_abi) { @export(&__floattitf_windows_x86_64, .{ .name = "__floattitf", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/floattixf.zig b/lib/compiler_rt/floattixf.zig index ebf83446d32243304c3ddbbacc5554896f036241..ac1df5ba32b359bbe414d6ec134744299d2e7786 100644 --- a/lib/compiler_rt/floattixf.zig +++ b/lib/compiler_rt/floattixf.zig @@ -2,8 +2,6 @@ const builtin = @import("builtin"); const common = @import("./common.zig"); const floatFromInt = @import("./float_from_int.zig").floatFromInt; -pub const panic = common.panic; - comptime { if (common.want_windows_v2u64_abi) { @export(&__floattixf_windows_x86_64, .{ .name = "__floattixf", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/floatundidf.zig b/lib/compiler_rt/floatundidf.zig index 887ef83215943431098b12a172b071049a42429c..7a9e4afe7af40701c85b9347ddedf36ead437f17 100644 --- a/lib/compiler_rt/floatundidf.zig +++ b/lib/compiler_rt/floatundidf.zig @@ -2,8 +2,6 @@ const builtin = @import("builtin"); const common = @import("./common.zig"); const floatFromInt = @import("./float_from_int.zig").floatFromInt; -pub const panic = common.panic; - comptime { if (common.want_aeabi) { @export(&__aeabi_ul2d, .{ .name = "__aeabi_ul2d", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/floatundihf.zig b/lib/compiler_rt/floatundihf.zig index 5fefedec1d83803e3b365203f36bd569616c81d3..e7a3f865f829ff28e7efaa805790d17a52f7d884 100644 --- a/lib/compiler_rt/floatundihf.zig +++ b/lib/compiler_rt/floatundihf.zig @@ -1,8 +1,6 @@ const common = @import("./common.zig"); const floatFromInt = @import("./float_from_int.zig").floatFromInt; -pub const panic = common.panic; - comptime { @export(&__floatundihf, .{ .name = "__floatundihf", .linkage = common.linkage, .visibility = common.visibility }); } diff --git a/lib/compiler_rt/floatundisf.zig b/lib/compiler_rt/floatundisf.zig index 8537779996ebc147a23f04ef92fd385161a14757..89e7fa1f4bf65848bcc6abbdb422e4dc77a737dc 100644 --- a/lib/compiler_rt/floatundisf.zig +++ b/lib/compiler_rt/floatundisf.zig @@ -2,8 +2,6 @@ const builtin = @import("builtin"); const common = @import("./common.zig"); const floatFromInt = @import("./float_from_int.zig").floatFromInt; -pub const panic = common.panic; - comptime { if (common.want_aeabi) { @export(&__aeabi_ul2f, .{ .name = "__aeabi_ul2f", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/floatunditf.zig b/lib/compiler_rt/floatunditf.zig index aadb25132810dbcf6f6e0fc557597fae355dfe0f..d313ff3d12a55290777a6af85ea4881e945eacf1 100644 --- a/lib/compiler_rt/floatunditf.zig +++ b/lib/compiler_rt/floatunditf.zig @@ -1,8 +1,6 @@ const common = @import("./common.zig"); const floatFromInt = @import("./float_from_int.zig").floatFromInt; -pub const panic = common.panic; - comptime { if (common.want_ppc_abi) { @export(&__floatunditf, .{ .name = "__floatundikf", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/floatundixf.zig b/lib/compiler_rt/floatundixf.zig index 7f801fd6ba0ca8cb3ba89ca0a5f876179b7099f0..b9691e91448866757cca2b6278768b6a6e8e3bd5 100644 --- a/lib/compiler_rt/floatundixf.zig +++ b/lib/compiler_rt/floatundixf.zig @@ -1,8 +1,6 @@ const common = @import("./common.zig"); const floatFromInt = @import("./float_from_int.zig").floatFromInt; -pub const panic = common.panic; - comptime { @export(&__floatundixf, .{ .name = "__floatundixf", .linkage = common.linkage, .visibility = common.visibility }); } diff --git a/lib/compiler_rt/floatuneidf.zig b/lib/compiler_rt/floatuneidf.zig index c7b1e032ea718474affc18b53886dd3484b947b9..bb8a6579cec5d9f48125d6c7cfe9bc8b1c7c51c7 100644 --- a/lib/compiler_rt/floatuneidf.zig +++ b/lib/compiler_rt/floatuneidf.zig @@ -3,8 +3,6 @@ const builtin = @import("builtin"); const common = @import("common.zig"); const floatFromBigInt = @import("float_from_int.zig").floatFromBigInt; -pub const panic = common.panic; - comptime { @export(&__floatuneidf, .{ .name = "__floatuneidf", .linkage = common.linkage, .visibility = common.visibility }); } diff --git a/lib/compiler_rt/floatuneihf.zig b/lib/compiler_rt/floatuneihf.zig index 00c53a19197cd15c7c97272f8ae0ba2ea34db49e..17b1e1d29059b1ad28302e94ad33b9335275c59b 100644 --- a/lib/compiler_rt/floatuneihf.zig +++ b/lib/compiler_rt/floatuneihf.zig @@ -3,8 +3,6 @@ const builtin = @import("builtin"); const common = @import("common.zig"); const floatFromBigInt = @import("float_from_int.zig").floatFromBigInt; -pub const panic = common.panic; - comptime { @export(&__floatuneihf, .{ .name = "__floatuneihf", .linkage = common.linkage, .visibility = common.visibility }); } diff --git a/lib/compiler_rt/floatuneisf.zig b/lib/compiler_rt/floatuneisf.zig index 4acb0fa7b234b8b66074b9ead9ac0fa811f5748d..bb43b6ee652c37bd7a2fbd6712edb636d70bde37 100644 --- a/lib/compiler_rt/floatuneisf.zig +++ b/lib/compiler_rt/floatuneisf.zig @@ -3,8 +3,6 @@ const builtin = @import("builtin"); const common = @import("common.zig"); const floatFromBigInt = @import("float_from_int.zig").floatFromBigInt; -pub const panic = common.panic; - comptime { @export(&__floatuneisf, .{ .name = "__floatuneisf", .linkage = common.linkage, .visibility = common.visibility }); } diff --git a/lib/compiler_rt/floatuneitf.zig b/lib/compiler_rt/floatuneitf.zig index 323f44611d23beed2266a90eef2abc6fb725a5c2..d84678ce115f88c3cb21cc5203fb162f6404a653 100644 --- a/lib/compiler_rt/floatuneitf.zig +++ b/lib/compiler_rt/floatuneitf.zig @@ -3,8 +3,6 @@ const builtin = @import("builtin"); const common = @import("common.zig"); const floatFromBigInt = @import("float_from_int.zig").floatFromBigInt; -pub const panic = common.panic; - comptime { @export(&__floatuneitf, .{ .name = "__floatuneitf", .linkage = common.linkage, .visibility = common.visibility }); } diff --git a/lib/compiler_rt/floatuneixf.zig b/lib/compiler_rt/floatuneixf.zig index 12157d20a34324791049f343f3ed212ddf113419..ad0b8f24de1db0f665ece481853fb5ba76f1e53a 100644 --- a/lib/compiler_rt/floatuneixf.zig +++ b/lib/compiler_rt/floatuneixf.zig @@ -3,8 +3,6 @@ const builtin = @import("builtin"); const common = @import("common.zig"); const floatFromBigInt = @import("float_from_int.zig").floatFromBigInt; -pub const panic = common.panic; - comptime { @export(&__floatuneixf, .{ .name = "__floatuneixf", .linkage = common.linkage, .visibility = common.visibility }); } diff --git a/lib/compiler_rt/floatunsidf.zig b/lib/compiler_rt/floatunsidf.zig index c712428ce24f9a5fb7cbe0b821d48e35c77a0808..fc50a5ad37db3cda3be420f0479f3aab09461c2a 100644 --- a/lib/compiler_rt/floatunsidf.zig +++ b/lib/compiler_rt/floatunsidf.zig @@ -1,8 +1,6 @@ const common = @import("./common.zig"); const floatFromInt = @import("./float_from_int.zig").floatFromInt; -pub const panic = common.panic; - comptime { if (common.want_aeabi) { @export(&__aeabi_ui2d, .{ .name = "__aeabi_ui2d", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/floatunsihf.zig b/lib/compiler_rt/floatunsihf.zig index 5c5778a460b18632c57ab2a2a5b0053e5afb38ef..123b76fcde1b1175e74902f26e2ff820a2d6c662 100644 --- a/lib/compiler_rt/floatunsihf.zig +++ b/lib/compiler_rt/floatunsihf.zig @@ -1,8 +1,6 @@ const common = @import("./common.zig"); const floatFromInt = @import("./float_from_int.zig").floatFromInt; -pub const panic = common.panic; - comptime { @export(&__floatunsihf, .{ .name = "__floatunsihf", .linkage = common.linkage, .visibility = common.visibility }); } diff --git a/lib/compiler_rt/floatunsisf.zig b/lib/compiler_rt/floatunsisf.zig index 9fa213ed227729bc4b33baa8a69b4e7733a42d80..4bcceba5c8c967a73312765468a98876a5b128c9 100644 --- a/lib/compiler_rt/floatunsisf.zig +++ b/lib/compiler_rt/floatunsisf.zig @@ -1,8 +1,6 @@ const common = @import("./common.zig"); const floatFromInt = @import("./float_from_int.zig").floatFromInt; -pub const panic = common.panic; - comptime { if (common.want_aeabi) { @export(&__aeabi_ui2f, .{ .name = "__aeabi_ui2f", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/floatunsitf.zig b/lib/compiler_rt/floatunsitf.zig index d09d31d05dc9c10f484c232bb0fd1af3a644bb3a..e491987003f054005b841a316f414412dfb9ecb8 100644 --- a/lib/compiler_rt/floatunsitf.zig +++ b/lib/compiler_rt/floatunsitf.zig @@ -1,8 +1,6 @@ const common = @import("./common.zig"); const floatFromInt = @import("./float_from_int.zig").floatFromInt; -pub const panic = common.panic; - comptime { if (common.want_ppc_abi) { @export(&__floatunsitf, .{ .name = "__floatunsikf", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/floatunsixf.zig b/lib/compiler_rt/floatunsixf.zig index 678085ad6fcf0cf4bf732ca615bb27dd0ddf929c..875d3476fa99d0a2d96faa89ccb6f7e99c27232f 100644 --- a/lib/compiler_rt/floatunsixf.zig +++ b/lib/compiler_rt/floatunsixf.zig @@ -1,8 +1,6 @@ const common = @import("./common.zig"); const floatFromInt = @import("./float_from_int.zig").floatFromInt; -pub const panic = common.panic; - comptime { @export(&__floatunsixf, .{ .name = "__floatunsixf", .linkage = common.linkage, .visibility = common.visibility }); } diff --git a/lib/compiler_rt/floatuntidf.zig b/lib/compiler_rt/floatuntidf.zig index cee154ed0bee1912506bc8fb50c9534b9b2279e9..284e50dd367bd6dfed766842fffa0f8fecd43815 100644 --- a/lib/compiler_rt/floatuntidf.zig +++ b/lib/compiler_rt/floatuntidf.zig @@ -2,8 +2,6 @@ const builtin = @import("builtin"); const common = @import("./common.zig"); const floatFromInt = @import("./float_from_int.zig").floatFromInt; -pub const panic = common.panic; - comptime { if (common.want_windows_v2u64_abi) { @export(&__floatuntidf_windows_x86_64, .{ .name = "__floatuntidf", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/floatuntihf.zig b/lib/compiler_rt/floatuntihf.zig index 63cfdb604facadb2e7e125d71d151819fe09a3d7..41da6f1bfb501f9b78ab65277cb4a49da40f6ad6 100644 --- a/lib/compiler_rt/floatuntihf.zig +++ b/lib/compiler_rt/floatuntihf.zig @@ -2,8 +2,6 @@ const builtin = @import("builtin"); const common = @import("./common.zig"); const floatFromInt = @import("./float_from_int.zig").floatFromInt; -pub const panic = common.panic; - comptime { if (common.want_windows_v2u64_abi) { @export(&__floatuntihf_windows_x86_64, .{ .name = "__floatuntihf", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/floatuntisf.zig b/lib/compiler_rt/floatuntisf.zig index 1e65576f063e137458fb08e8eea65a0cfa167f31..f79d2e5feda54382074c9ca12cc32696aa1f6228 100644 --- a/lib/compiler_rt/floatuntisf.zig +++ b/lib/compiler_rt/floatuntisf.zig @@ -2,8 +2,6 @@ const builtin = @import("builtin"); const common = @import("./common.zig"); const floatFromInt = @import("./float_from_int.zig").floatFromInt; -pub const panic = common.panic; - comptime { if (common.want_windows_v2u64_abi) { @export(&__floatuntisf_windows_x86_64, .{ .name = "__floatuntisf", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/floatuntitf.zig b/lib/compiler_rt/floatuntitf.zig index 41c48bee990f48498964b97d0e8fcd56666567e4..77dd22ed5978b6b86019d4c50ed7421003f7041a 100644 --- a/lib/compiler_rt/floatuntitf.zig +++ b/lib/compiler_rt/floatuntitf.zig @@ -2,8 +2,6 @@ const builtin = @import("builtin"); const common = @import("./common.zig"); const floatFromInt = @import("./float_from_int.zig").floatFromInt; -pub const panic = common.panic; - comptime { if (common.want_windows_v2u64_abi) { @export(&__floatuntitf_windows_x86_64, .{ .name = "__floatuntitf", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/floatuntixf.zig b/lib/compiler_rt/floatuntixf.zig index 353a3c3c0d15d85bba78c5ee079c0152581fda24..12a561b5732a2d8284ed81325c8e5304781fe1dd 100644 --- a/lib/compiler_rt/floatuntixf.zig +++ b/lib/compiler_rt/floatuntixf.zig @@ -2,8 +2,6 @@ const builtin = @import("builtin"); const common = @import("./common.zig"); const floatFromInt = @import("./float_from_int.zig").floatFromInt; -pub const panic = common.panic; - comptime { if (common.want_windows_v2u64_abi) { @export(&__floatuntixf_windows_x86_64, .{ .name = "__floatuntixf", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/floor.zig b/lib/compiler_rt/floor.zig index ba1869089aa9877afec47e851a3fe357fd7dc4d3..7510abed663e71fe4d1d601546670546d1cab6b3 100644 --- a/lib/compiler_rt/floor.zig +++ b/lib/compiler_rt/floor.zig @@ -13,8 +13,6 @@ const expect = std.testing.expect; const arch = builtin.cpu.arch; const common = @import("common.zig"); -pub const panic = common.panic; - comptime { @export(&__floorh, .{ .name = "__floorh", .linkage = common.linkage, .visibility = common.visibility }); @export(&floorf, .{ .name = "floorf", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/fma.zig b/lib/compiler_rt/fma.zig index 79a120d5a2ccdde4c037297e677420b065a24b04..cce9215f881d1d0aad575e1afa17c9e50a08fe6d 100644 --- a/lib/compiler_rt/fma.zig +++ b/lib/compiler_rt/fma.zig @@ -10,8 +10,6 @@ const math = std.math; const expect = std.testing.expect; const common = @import("common.zig"); -pub const panic = common.panic; - comptime { @export(&__fmah, .{ .name = "__fmah", .linkage = common.linkage, .visibility = common.visibility }); @export(&fmaf, .{ .name = "fmaf", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/fmax.zig b/lib/compiler_rt/fmax.zig index 02e7d1d75bbee6a5e966bebf68dba1b39c88e22b..19a7c3f6f7899021efddc3d458d39a52403c056e 100644 --- a/lib/compiler_rt/fmax.zig +++ b/lib/compiler_rt/fmax.zig @@ -4,8 +4,6 @@ const math = std.math; const arch = builtin.cpu.arch; const common = @import("common.zig"); -pub const panic = common.panic; - comptime { @export(&__fmaxh, .{ .name = "__fmaxh", .linkage = common.linkage, .visibility = common.visibility }); @export(&fmaxf, .{ .name = "fmaxf", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/fmin.zig b/lib/compiler_rt/fmin.zig index 71f9ef589a7640ccf0732e255418c609656465b3..aa54ba6e0b081a0bdcedceaf79c674abb1591d3c 100644 --- a/lib/compiler_rt/fmin.zig +++ b/lib/compiler_rt/fmin.zig @@ -4,8 +4,6 @@ const math = std.math; const arch = builtin.cpu.arch; const common = @import("common.zig"); -pub const panic = common.panic; - comptime { @export(&__fminh, .{ .name = "__fminh", .linkage = common.linkage, .visibility = common.visibility }); @export(&fminf, .{ .name = "fminf", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/fmod.zig b/lib/compiler_rt/fmod.zig index 5c055049e3ffb3b63bc9806ebca2d8af7e5acd34..ae5abaae65f68021638dec7948fc729d302bc589 100644 --- a/lib/compiler_rt/fmod.zig +++ b/lib/compiler_rt/fmod.zig @@ -6,8 +6,6 @@ const arch = builtin.cpu.arch; const common = @import("common.zig"); const normalize = common.normalize; -pub const panic = common.panic; - comptime { @export(&__fmodh, .{ .name = "__fmodh", .linkage = common.linkage, .visibility = common.visibility }); @export(&fmodf, .{ .name = "fmodf", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/gedf2.zig b/lib/compiler_rt/gedf2.zig index 33b4fa992609a3b08164037a1ee463c6db37ef6a..aeb08b4a8b9eae9979b99ba37db0db4047992a92 100644 --- a/lib/compiler_rt/gedf2.zig +++ b/lib/compiler_rt/gedf2.zig @@ -3,8 +3,6 @@ const common = @import("./common.zig"); const comparef = @import("./comparef.zig"); -pub const panic = common.panic; - comptime { if (common.want_aeabi) { @export(&__aeabi_dcmpge, .{ .name = "__aeabi_dcmpge", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/gehf2.zig b/lib/compiler_rt/gehf2.zig index 12eccf98d824905f72c9f3babb1f648e31056807..b46d4a1a24d0038abc38ce6e6ca5d5dda8324ef8 100644 --- a/lib/compiler_rt/gehf2.zig +++ b/lib/compiler_rt/gehf2.zig @@ -3,8 +3,6 @@ const common = @import("./common.zig"); const comparef = @import("./comparef.zig"); -pub const panic = common.panic; - comptime { @export(&__gehf2, .{ .name = "__gehf2", .linkage = common.linkage, .visibility = common.visibility }); @export(&__gthf2, .{ .name = "__gthf2", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/gesf2.zig b/lib/compiler_rt/gesf2.zig index 5f9aefef47958d31dc45f181d641756cf199b51f..d4b6a23c662db77423c5bf35c664ef0462fb4552 100644 --- a/lib/compiler_rt/gesf2.zig +++ b/lib/compiler_rt/gesf2.zig @@ -3,8 +3,6 @@ const common = @import("./common.zig"); const comparef = @import("./comparef.zig"); -pub const panic = common.panic; - comptime { if (common.want_aeabi) { @export(&__aeabi_fcmpge, .{ .name = "__aeabi_fcmpge", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/getf2.zig b/lib/compiler_rt/getf2.zig index 3a17843718c83435fdf89ece9b94951d6c5c6b54..a6014b365e56a2a68bb3bb9fb22bcfbad3f29ed2 100644 --- a/lib/compiler_rt/getf2.zig +++ b/lib/compiler_rt/getf2.zig @@ -3,8 +3,6 @@ const common = @import("./common.zig"); const comparef = @import("./comparef.zig"); -pub const panic = common.panic; - comptime { if (common.want_ppc_abi) { @export(&__getf2, .{ .name = "__gekf2", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/gexf2.zig b/lib/compiler_rt/gexf2.zig index 7ea0d6eebe5bee6fff210950a105e3d010acd061..365c298cc6161fd39f5360fedbbc0d70e6fa7c11 100644 --- a/lib/compiler_rt/gexf2.zig +++ b/lib/compiler_rt/gexf2.zig @@ -1,8 +1,6 @@ const common = @import("./common.zig"); const comparef = @import("./comparef.zig"); -pub const panic = common.panic; - comptime { @export(&__gexf2, .{ .name = "__gexf2", .linkage = common.linkage, .visibility = common.visibility }); @export(&__gtxf2, .{ .name = "__gtxf2", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/int.zig b/lib/compiler_rt/int.zig index 16c504ee66d1eb545d2629a8f54174da37c6a82d..3f38bad973d55ac78551fcda15deb1b2341e2c3c 100644 --- a/lib/compiler_rt/int.zig +++ b/lib/compiler_rt/int.zig @@ -10,8 +10,6 @@ const common = @import("common.zig"); const udivmod = @import("udivmod.zig").udivmod; const __divti3 = @import("divti3.zig").__divti3; -pub const panic = common.panic; - comptime { @export(&__divmodti4, .{ .name = "__divmodti4", .linkage = common.linkage, .visibility = common.visibility }); @export(&__udivmoddi4, .{ .name = "__udivmoddi4", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/log.zig b/lib/compiler_rt/log.zig index f8bbf430ace402be8ffe1452264ab1d3ecce15d6..d2644d65dbc3889c20063134b6f2f8103bf79af9 100644 --- a/lib/compiler_rt/log.zig +++ b/lib/compiler_rt/log.zig @@ -12,8 +12,6 @@ const expectEqual = std.testing.expectEqual; const arch = builtin.cpu.arch; const common = @import("common.zig"); -pub const panic = common.panic; - comptime { @export(&__logh, .{ .name = "__logh", .linkage = common.linkage, .visibility = common.visibility }); @export(&logf, .{ .name = "logf", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/log10.zig b/lib/compiler_rt/log10.zig index 1c2ce4bbcabf4f7c6a04646cc8e9b9a914099552..5c55bb3f51a27b3d3b44660673ee2c4aa665a05b 100644 --- a/lib/compiler_rt/log10.zig +++ b/lib/compiler_rt/log10.zig @@ -13,8 +13,6 @@ const maxInt = std.math.maxInt; const arch = builtin.cpu.arch; const common = @import("common.zig"); -pub const panic = common.panic; - comptime { @export(&__log10h, .{ .name = "__log10h", .linkage = common.linkage, .visibility = common.visibility }); @export(&log10f, .{ .name = "log10f", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/log2.zig b/lib/compiler_rt/log2.zig index 4cedcfe0c1d284e7a3c03c293be1ef4a6cdd4231..5773b3b210f8c6bad150af9218951712f4e093b4 100644 --- a/lib/compiler_rt/log2.zig +++ b/lib/compiler_rt/log2.zig @@ -13,8 +13,6 @@ const maxInt = std.math.maxInt; const arch = builtin.cpu.arch; const common = @import("common.zig"); -pub const panic = common.panic; - comptime { @export(&__log2h, .{ .name = "__log2h", .linkage = common.linkage, .visibility = common.visibility }); @export(&log2f, .{ .name = "log2f", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/modti3.zig b/lib/compiler_rt/modti3.zig index b73f3d05fa05c2a44bb7d3af4137e496cafdc26f..74e431a06166be0d1a4048400e8ebe3095345e98 100644 --- a/lib/compiler_rt/modti3.zig +++ b/lib/compiler_rt/modti3.zig @@ -7,8 +7,6 @@ const builtin = @import("builtin"); const udivmod = @import("udivmod.zig").udivmod; const common = @import("common.zig"); -pub const panic = common.panic; - comptime { if (common.want_windows_v2u64_abi) { @export(&__modti3_windows_x86_64, .{ .name = "__modti3", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/mulXi3.zig b/lib/compiler_rt/mulXi3.zig index a96ef135c4d3a1dbf3dbebb0200a7589b973b909..198d51c8883875c28134e48eb60e667699e05c49 100644 --- a/lib/compiler_rt/mulXi3.zig +++ b/lib/compiler_rt/mulXi3.zig @@ -4,8 +4,6 @@ const testing = std.testing; const common = @import("common.zig"); const native_endian = builtin.cpu.arch.endian(); -pub const panic = common.panic; - comptime { @export(&__mulsi3, .{ .name = "__mulsi3", .linkage = common.linkage, .visibility = common.visibility }); if (common.want_aeabi) { diff --git a/lib/compiler_rt/muldc3.zig b/lib/compiler_rt/muldc3.zig index 1c53b4d07397872fa2170e929f8afa92952f2514..9670c1312887c99a038540ed097bb22daede93f3 100644 --- a/lib/compiler_rt/muldc3.zig +++ b/lib/compiler_rt/muldc3.zig @@ -1,8 +1,6 @@ const common = @import("./common.zig"); const mulc3 = @import("./mulc3.zig"); -pub const panic = common.panic; - comptime { if (@import("builtin").zig_backend != .stage2_c) { @export(&__muldc3, .{ .name = "__muldc3", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/muldf3.zig b/lib/compiler_rt/muldf3.zig index 1fa4c9debbd2e5e7aab2fa917425327ffa3a43f5..cf67df0913ab624cb1f5a840f8da139e9d0d38a1 100644 --- a/lib/compiler_rt/muldf3.zig +++ b/lib/compiler_rt/muldf3.zig @@ -1,8 +1,6 @@ const common = @import("./common.zig"); const mulf3 = @import("./mulf3.zig").mulf3; -pub const panic = common.panic; - comptime { if (common.want_aeabi) { @export(&__aeabi_dmul, .{ .name = "__aeabi_dmul", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/mulhc3.zig b/lib/compiler_rt/mulhc3.zig index 4d97a0978dc5a6663f3dd46bbd5f923feb089ddb..09bca03a050c1281dedc68c1c94028d0f5e79dea 100644 --- a/lib/compiler_rt/mulhc3.zig +++ b/lib/compiler_rt/mulhc3.zig @@ -1,8 +1,6 @@ const common = @import("./common.zig"); const mulc3 = @import("./mulc3.zig"); -pub const panic = common.panic; - comptime { if (@import("builtin").zig_backend != .stage2_c) { @export(&__mulhc3, .{ .name = "__mulhc3", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/mulhf3.zig b/lib/compiler_rt/mulhf3.zig index 2731625a160e8bbd8891603d0b6c70029a2d4b5a..22f7a95627f9c82dbe3f9b818e062c7188cd8a44 100644 --- a/lib/compiler_rt/mulhf3.zig +++ b/lib/compiler_rt/mulhf3.zig @@ -1,8 +1,6 @@ const common = @import("./common.zig"); const mulf3 = @import("./mulf3.zig").mulf3; -pub const panic = common.panic; - comptime { @export(&__mulhf3, .{ .name = "__mulhf3", .linkage = common.linkage, .visibility = common.visibility }); } diff --git a/lib/compiler_rt/mulo.zig b/lib/compiler_rt/mulo.zig index 20fbff3b96e4ecc1eb39dec897affafd278badc6..b85623eb7e5107984a9b52b1a47d0f40200f1218 100644 --- a/lib/compiler_rt/mulo.zig +++ b/lib/compiler_rt/mulo.zig @@ -3,8 +3,6 @@ const builtin = @import("builtin"); const math = std.math; const common = @import("common.zig"); -pub const panic = common.panic; - comptime { @export(&__mulosi4, .{ .name = "__mulosi4", .linkage = common.linkage, .visibility = common.visibility }); @export(&__mulodi4, .{ .name = "__mulodi4", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/mulsc3.zig b/lib/compiler_rt/mulsc3.zig index f011d2fe2319778a5a8b9b51b340ddb1dfe295f0..49c1e31aecd43e18961bf2c3853b8855993a917c 100644 --- a/lib/compiler_rt/mulsc3.zig +++ b/lib/compiler_rt/mulsc3.zig @@ -1,8 +1,6 @@ const common = @import("./common.zig"); const mulc3 = @import("./mulc3.zig"); -pub const panic = common.panic; - comptime { if (@import("builtin").zig_backend != .stage2_c) { @export(&__mulsc3, .{ .name = "__mulsc3", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/mulsf3.zig b/lib/compiler_rt/mulsf3.zig index 8929b5deba123448372928fe0f7a088bc702e442..a603652262bcfd7612abe196ac80d6a04dc1be35 100644 --- a/lib/compiler_rt/mulsf3.zig +++ b/lib/compiler_rt/mulsf3.zig @@ -1,8 +1,6 @@ const common = @import("./common.zig"); const mulf3 = @import("./mulf3.zig").mulf3; -pub const panic = common.panic; - comptime { if (common.want_aeabi) { @export(&__aeabi_fmul, .{ .name = "__aeabi_fmul", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/multc3.zig b/lib/compiler_rt/multc3.zig index cf3621919daee92be2fe27e0a7b256044e079631..1ed373660f81ef101d6b37086b456a11dd546707 100644 --- a/lib/compiler_rt/multc3.zig +++ b/lib/compiler_rt/multc3.zig @@ -1,8 +1,6 @@ const common = @import("./common.zig"); const mulc3 = @import("./mulc3.zig"); -pub const panic = common.panic; - comptime { if (@import("builtin").zig_backend != .stage2_c) { if (common.want_ppc_abi) diff --git a/lib/compiler_rt/multf3.zig b/lib/compiler_rt/multf3.zig index 0da8c506e8551b4378283fe0eac4653b1bc1ede2..6ef9cc2c2580ece0ab9f86495091c21e03a9275c 100644 --- a/lib/compiler_rt/multf3.zig +++ b/lib/compiler_rt/multf3.zig @@ -1,8 +1,6 @@ const common = @import("./common.zig"); const mulf3 = @import("./mulf3.zig").mulf3; -pub const panic = common.panic; - comptime { if (common.want_ppc_abi) { @export(&__multf3, .{ .name = "__mulkf3", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/mulvsi3.zig b/lib/compiler_rt/mulvsi3.zig index d225552a582a7824639118d983c84fe2be5a9231..e773ea0d9ad756fe0d8a834ed04f4c79834fa171 100644 --- a/lib/compiler_rt/mulvsi3.zig +++ b/lib/compiler_rt/mulvsi3.zig @@ -2,8 +2,6 @@ const mulv = @import("mulo.zig"); const common = @import("./common.zig"); const testing = @import("std").testing; -pub const panic = common.panic; - comptime { @export(&__mulvsi3, .{ .name = "__mulvsi3", .linkage = common.linkage, .visibility = common.visibility }); } diff --git a/lib/compiler_rt/mulxc3.zig b/lib/compiler_rt/mulxc3.zig index ef6927ce089a5d38480cf41660f2acb5a94b7322..cbf9c640830519ad6058986202fa886a7ee5f15d 100644 --- a/lib/compiler_rt/mulxc3.zig +++ b/lib/compiler_rt/mulxc3.zig @@ -1,8 +1,6 @@ const common = @import("./common.zig"); const mulc3 = @import("./mulc3.zig"); -pub const panic = common.panic; - comptime { if (@import("builtin").zig_backend != .stage2_c) { @export(&__mulxc3, .{ .name = "__mulxc3", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/mulxf3.zig b/lib/compiler_rt/mulxf3.zig index 2db80b81b0102d6e032856f564afc2bbc5fe9ce0..1d9f465ebd053a498493f964f6406911384b72c7 100644 --- a/lib/compiler_rt/mulxf3.zig +++ b/lib/compiler_rt/mulxf3.zig @@ -1,8 +1,6 @@ const common = @import("./common.zig"); const mulf3 = @import("./mulf3.zig").mulf3; -pub const panic = common.panic; - comptime { @export(&__mulxf3, .{ .name = "__mulxf3", .linkage = common.linkage, .visibility = common.visibility }); } diff --git a/lib/compiler_rt/negXi2.zig b/lib/compiler_rt/negXi2.zig index eee5202e71b8d0a53f2e4726fe68466d368fd917..7a57e988c6e5520a91595e89f4f160271638b35f 100644 --- a/lib/compiler_rt/negXi2.zig +++ b/lib/compiler_rt/negXi2.zig @@ -10,8 +10,6 @@ const std = @import("std"); const builtin = @import("builtin"); const common = @import("common.zig"); -pub const panic = common.panic; - comptime { @export(&__negsi2, .{ .name = "__negsi2", .linkage = common.linkage, .visibility = common.visibility }); @export(&__negdi2, .{ .name = "__negdi2", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/negdf2.zig b/lib/compiler_rt/negdf2.zig index 85e7f2bd82afbfcd0ee409bac49e97c44a7d30b8..b0ac26e6c0c5b8f6e946c38c81ccbd15c38da129 100644 --- a/lib/compiler_rt/negdf2.zig +++ b/lib/compiler_rt/negdf2.zig @@ -1,7 +1,5 @@ const common = @import("./common.zig"); -pub const panic = common.panic; - comptime { if (common.want_aeabi) { @export(&__aeabi_dneg, .{ .name = "__aeabi_dneg", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/neghf2.zig b/lib/compiler_rt/neghf2.zig index 86b28d48021dca0cf5e25132e42a7416564a1de1..59c7e155b72f714d28d7fa24b0b8860ca853e3ac 100644 --- a/lib/compiler_rt/neghf2.zig +++ b/lib/compiler_rt/neghf2.zig @@ -1,7 +1,5 @@ const common = @import("./common.zig"); -pub const panic = common.panic; - comptime { @export(&__neghf2, .{ .name = "__neghf2", .linkage = common.linkage, .visibility = common.visibility }); } diff --git a/lib/compiler_rt/negsf2.zig b/lib/compiler_rt/negsf2.zig index 08276e7f26bacfb5f5f15b13294c99f7d7a520ef..065dc3acd10607b31f7ae4f210c6d873761cf1bf 100644 --- a/lib/compiler_rt/negsf2.zig +++ b/lib/compiler_rt/negsf2.zig @@ -1,7 +1,5 @@ const common = @import("./common.zig"); -pub const panic = common.panic; - comptime { if (common.want_aeabi) { @export(&__aeabi_fneg, .{ .name = "__aeabi_fneg", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/negtf2.zig b/lib/compiler_rt/negtf2.zig index 9fb9384446591b8f107a1566e0a952fcdf389591..ecb5cc2c9007256061a7f102029e1d14c75d0494 100644 --- a/lib/compiler_rt/negtf2.zig +++ b/lib/compiler_rt/negtf2.zig @@ -1,7 +1,5 @@ const common = @import("./common.zig"); -pub const panic = common.panic; - comptime { if (common.want_ppc_abi) @export(&__negtf2, .{ .name = "__negkf2", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/negv.zig b/lib/compiler_rt/negv.zig index 6a2e717db1505f7a7b98be11116e6fdade939703..98934b78f424552476ddab4bf819db24bc48b33b 100644 --- a/lib/compiler_rt/negv.zig +++ b/lib/compiler_rt/negv.zig @@ -5,8 +5,6 @@ const std = @import("std"); const builtin = @import("builtin"); const common = @import("common.zig"); -pub const panic = common.panic; - comptime { @export(&__negvsi2, .{ .name = "__negvsi2", .linkage = common.linkage, .visibility = common.visibility }); @export(&__negvdi2, .{ .name = "__negvdi2", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/negxf2.zig b/lib/compiler_rt/negxf2.zig index 9a9c178654989ce9feb27528b92f19e3c3bc8427..ea16c8fef215c883559c16d06bf9766c02106fcb 100644 --- a/lib/compiler_rt/negxf2.zig +++ b/lib/compiler_rt/negxf2.zig @@ -1,7 +1,5 @@ const common = @import("./common.zig"); -pub const panic = common.panic; - comptime { @export(&__negxf2, .{ .name = "__negxf2", .linkage = common.linkage, .visibility = common.visibility }); } diff --git a/lib/compiler_rt/parity.zig b/lib/compiler_rt/parity.zig index 5a3e5a886d0eb8319900b4a5447b51f21f5afc06..71d50f327b178d01545fb9315e01aee2061970cf 100644 --- a/lib/compiler_rt/parity.zig +++ b/lib/compiler_rt/parity.zig @@ -5,8 +5,6 @@ const std = @import("std"); const builtin = @import("builtin"); const common = @import("common.zig"); -pub const panic = common.panic; - comptime { @export(&__paritysi2, .{ .name = "__paritysi2", .linkage = common.linkage, .visibility = common.visibility }); @export(&__paritydi2, .{ .name = "__paritydi2", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/popcount.zig b/lib/compiler_rt/popcount.zig index f937bd0ce05cf370ed768524844d5142460a6070..12ecda16cc1c2c41119017ccf32c1405f9a434f8 100644 --- a/lib/compiler_rt/popcount.zig +++ b/lib/compiler_rt/popcount.zig @@ -10,8 +10,6 @@ const builtin = @import("builtin"); const std = @import("std"); const common = @import("common.zig"); -pub const panic = common.panic; - comptime { @export(&__popcountsi2, .{ .name = "__popcountsi2", .linkage = common.linkage, .visibility = common.visibility }); @export(&__popcountdi2, .{ .name = "__popcountdi2", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/powiXf2.zig b/lib/compiler_rt/powiXf2.zig index 21e7042a06b2bc5b9e832c1a71c9a34a6c7c7de2..b603998d556949d01c0915c51e2c50f72a509a82 100644 --- a/lib/compiler_rt/powiXf2.zig +++ b/lib/compiler_rt/powiXf2.zig @@ -7,8 +7,6 @@ const builtin = @import("builtin"); const common = @import("common.zig"); const std = @import("std"); -pub const panic = common.panic; - comptime { @export(&__powihf2, .{ .name = "__powihf2", .linkage = common.linkage, .visibility = common.visibility }); @export(&__powisf2, .{ .name = "__powisf2", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/round.zig b/lib/compiler_rt/round.zig index 9e8ea42d67c338e803dcf16f38832e85f7a39b15..b643bfd89fab0a9e277dfd0cecafc54d706595e1 100644 --- a/lib/compiler_rt/round.zig +++ b/lib/compiler_rt/round.zig @@ -12,8 +12,6 @@ const expect = std.testing.expect; const arch = builtin.cpu.arch; const common = @import("common.zig"); -pub const panic = common.panic; - comptime { @export(&__roundh, .{ .name = "__roundh", .linkage = common.linkage, .visibility = common.visibility }); @export(&roundf, .{ .name = "roundf", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/shift.zig b/lib/compiler_rt/shift.zig index 17860da943a66af4d42a988d1c6e2423447f29ca..9c75bd3475fb2e4dddd31cee61f44cf46514ac15 100644 --- a/lib/compiler_rt/shift.zig +++ b/lib/compiler_rt/shift.zig @@ -3,8 +3,6 @@ const builtin = @import("builtin"); const Log2Int = std.math.Log2Int; const common = @import("common.zig"); -pub const panic = common.panic; - comptime { // symbol compatibility with libgcc @export(&__ashlsi3, .{ .name = "__ashlsi3", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/sin.zig b/lib/compiler_rt/sin.zig index e14779d27a2b2cd74e847a6191dc38a8d38749d4..d25c1cd23ae194c3dabed9c497714aca5457e95e 100644 --- a/lib/compiler_rt/sin.zig +++ b/lib/compiler_rt/sin.zig @@ -16,8 +16,6 @@ const trig = @import("trig.zig"); const rem_pio2 = @import("rem_pio2.zig").rem_pio2; const rem_pio2f = @import("rem_pio2f.zig").rem_pio2f; -pub const panic = common.panic; - comptime { @export(&__sinh, .{ .name = "__sinh", .linkage = common.linkage, .visibility = common.visibility }); @export(&sinf, .{ .name = "sinf", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/sincos.zig b/lib/compiler_rt/sincos.zig index 72b90a51e70339fc4828a90719dcf770a468afcc..46fec8ff64c37ee972b998793f29742260709286 100644 --- a/lib/compiler_rt/sincos.zig +++ b/lib/compiler_rt/sincos.zig @@ -8,8 +8,6 @@ const rem_pio2 = @import("rem_pio2.zig").rem_pio2; const rem_pio2f = @import("rem_pio2f.zig").rem_pio2f; const common = @import("common.zig"); -pub const panic = common.panic; - comptime { @export(&__sincosh, .{ .name = "__sincosh", .linkage = common.linkage, .visibility = common.visibility }); @export(&sincosf, .{ .name = "sincosf", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/sqrt.zig b/lib/compiler_rt/sqrt.zig index ed4602120b1efcf66aed14c0a8b84378abe1aaa7..2c321863682a547f405708cf97c52e0acf3f5a17 100644 --- a/lib/compiler_rt/sqrt.zig +++ b/lib/compiler_rt/sqrt.zig @@ -11,8 +11,6 @@ const arch = builtin.cpu.arch; const math = std.math; const common = @import("common.zig"); -pub const panic = common.panic; - comptime { @export(&__sqrth, .{ .name = "__sqrth", .linkage = common.linkage, .visibility = common.visibility }); @export(&sqrtf, .{ .name = "sqrtf", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/stack_probe.zig b/lib/compiler_rt/stack_probe.zig index 21259ec4354a09e6ef8e0b82fdd9271b48ef6c85..83e08be0153754724f8e3b08609fcf3b33dbb1c5 100644 --- a/lib/compiler_rt/stack_probe.zig +++ b/lib/compiler_rt/stack_probe.zig @@ -5,8 +5,6 @@ const os_tag = builtin.os.tag; const arch = builtin.cpu.arch; const abi = builtin.abi; -pub const panic = common.panic; - comptime { if (builtin.os.tag == .windows) { // Default stack-probe functions emitted by LLVM diff --git a/lib/compiler_rt/subdf3.zig b/lib/compiler_rt/subdf3.zig index 24ded58e0c54450f77617f1449dbc2ca7e3614d1..b1d78509028c1a57217e188e23d28e70b04d6227 100644 --- a/lib/compiler_rt/subdf3.zig +++ b/lib/compiler_rt/subdf3.zig @@ -1,8 +1,6 @@ const common = @import("./common.zig"); const addf3 = @import("./addf3.zig").addf3; -pub const panic = common.panic; - comptime { if (common.want_aeabi) { @export(&__aeabi_dsub, .{ .name = "__aeabi_dsub", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/subhf3.zig b/lib/compiler_rt/subhf3.zig index cc42eb927022cc80d83575b439dbc338a6809faa..e019005ae478c34125c77f4b698f5c6737c8e26c 100644 --- a/lib/compiler_rt/subhf3.zig +++ b/lib/compiler_rt/subhf3.zig @@ -1,8 +1,6 @@ const common = @import("./common.zig"); const addf3 = @import("./addf3.zig").addf3; -pub const panic = common.panic; - comptime { @export(&__subhf3, .{ .name = "__subhf3", .linkage = common.linkage, .visibility = common.visibility }); } diff --git a/lib/compiler_rt/subsf3.zig b/lib/compiler_rt/subsf3.zig index f3bb334bd81e7460b08e1da81b9ca3dfd48eee73..9a1cb559b23af9393a9916d36f67e2987ad74af5 100644 --- a/lib/compiler_rt/subsf3.zig +++ b/lib/compiler_rt/subsf3.zig @@ -1,8 +1,6 @@ const common = @import("./common.zig"); const addf3 = @import("./addf3.zig").addf3; -pub const panic = common.panic; - comptime { if (common.want_aeabi) { @export(&__aeabi_fsub, .{ .name = "__aeabi_fsub", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/subtf3.zig b/lib/compiler_rt/subtf3.zig index aff4904adf6e6147a6967b46caf5afb0858a2a07..b187eaf579b3894df819e902051c9fe33058d897 100644 --- a/lib/compiler_rt/subtf3.zig +++ b/lib/compiler_rt/subtf3.zig @@ -1,8 +1,6 @@ const common = @import("./common.zig"); const addf3 = @import("./addf3.zig").addf3; -pub const panic = common.panic; - comptime { if (common.want_ppc_abi) { @export(&__subtf3, .{ .name = "__subkf3", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/subvdi3.zig b/lib/compiler_rt/subvdi3.zig index a34deb2da1bfd6c688206d7ee62f70533604a36b..17c893c91141925285dd5eecbd6297523fbb57a2 100644 --- a/lib/compiler_rt/subvdi3.zig +++ b/lib/compiler_rt/subvdi3.zig @@ -1,8 +1,6 @@ const common = @import("./common.zig"); const testing = @import("std").testing; -pub const panic = common.panic; - comptime { @export(&__subvdi3, .{ .name = "__subvdi3", .linkage = common.linkage, .visibility = common.visibility }); } diff --git a/lib/compiler_rt/subvsi3.zig b/lib/compiler_rt/subvsi3.zig index c524a3a63499e2408aa579c6c2c89a2bf47103b1..68f551071c8ff1e123aecf6075fd85f7c649ab40 100644 --- a/lib/compiler_rt/subvsi3.zig +++ b/lib/compiler_rt/subvsi3.zig @@ -1,8 +1,6 @@ const common = @import("./common.zig"); const testing = @import("std").testing; -pub const panic = common.panic; - comptime { @export(&__subvsi3, .{ .name = "__subvsi3", .linkage = common.linkage, .visibility = common.visibility }); } diff --git a/lib/compiler_rt/subxf3.zig b/lib/compiler_rt/subxf3.zig index e79dd230b5d8a8da80ce672cea28eb63a754eead..d119f0bd984fc311b2f67a89760c9f147397af9d 100644 --- a/lib/compiler_rt/subxf3.zig +++ b/lib/compiler_rt/subxf3.zig @@ -1,8 +1,6 @@ const std = @import("std"); const common = @import("./common.zig"); -pub const panic = common.panic; - comptime { @export(&__subxf3, .{ .name = "__subxf3", .linkage = common.linkage, .visibility = common.visibility }); } diff --git a/lib/compiler_rt/tan.zig b/lib/compiler_rt/tan.zig index af8d68eb44447af300b2b1af992fc1e1492b6fca..a78d7afb67b491d1d5ebb246689dbc7e4b8d9f73 100644 --- a/lib/compiler_rt/tan.zig +++ b/lib/compiler_rt/tan.zig @@ -18,8 +18,6 @@ const rem_pio2f = @import("rem_pio2f.zig").rem_pio2f; const arch = builtin.cpu.arch; const common = @import("common.zig"); -pub const panic = common.panic; - comptime { @export(&__tanh, .{ .name = "__tanh", .linkage = common.linkage, .visibility = common.visibility }); @export(&tanf, .{ .name = "tanf", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/trunc.zig b/lib/compiler_rt/trunc.zig index ff829ea7108992829d365f8f5705a5579529269f..55468c752fa4ff279ab872f5bd15077576fe1186 100644 --- a/lib/compiler_rt/trunc.zig +++ b/lib/compiler_rt/trunc.zig @@ -12,8 +12,6 @@ const mem = std.mem; const expect = std.testing.expect; const common = @import("common.zig"); -pub const panic = common.panic; - comptime { @export(&__trunch, .{ .name = "__trunch", .linkage = common.linkage, .visibility = common.visibility }); @export(&truncf, .{ .name = "truncf", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/truncdfhf2.zig b/lib/compiler_rt/truncdfhf2.zig index a24650ddb78f4f256fc46d370fc37fbe76823c95..4db14f0211849bb30d47f429e24b7a510736aab6 100644 --- a/lib/compiler_rt/truncdfhf2.zig +++ b/lib/compiler_rt/truncdfhf2.zig @@ -1,8 +1,6 @@ const common = @import("./common.zig"); const truncf = @import("./truncf.zig").truncf; -pub const panic = common.panic; - comptime { if (common.want_aeabi) { @export(&__aeabi_d2h, .{ .name = "__aeabi_d2h", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/truncdfsf2.zig b/lib/compiler_rt/truncdfsf2.zig index fd42d5d34428d3cf5a53ccd7d9fb3725e598218b..8d6ed5c3206f963401abb558adcdc9e56aae9e22 100644 --- a/lib/compiler_rt/truncdfsf2.zig +++ b/lib/compiler_rt/truncdfsf2.zig @@ -1,8 +1,6 @@ const common = @import("./common.zig"); const truncf = @import("./truncf.zig").truncf; -pub const panic = common.panic; - comptime { if (common.want_aeabi) { @export(&__aeabi_d2f, .{ .name = "__aeabi_d2f", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/truncsfhf2.zig b/lib/compiler_rt/truncsfhf2.zig index 18d5854c91b36a434a957dd97071357463f23a8b..acd5631da8f71e013e2eb55e5bf87ab3a02299cd 100644 --- a/lib/compiler_rt/truncsfhf2.zig +++ b/lib/compiler_rt/truncsfhf2.zig @@ -1,8 +1,6 @@ const common = @import("./common.zig"); const truncf = @import("./truncf.zig").truncf; -pub const panic = common.panic; - comptime { if (common.gnu_f16_abi) { @export(&__gnu_f2h_ieee, .{ .name = "__gnu_f2h_ieee", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/trunctfdf2.zig b/lib/compiler_rt/trunctfdf2.zig index 37b9227e0b975d610ce86d8b7e9de6f47231bcb1..4f569dae675c8e1fa9e394f167388bd79c53d3ff 100644 --- a/lib/compiler_rt/trunctfdf2.zig +++ b/lib/compiler_rt/trunctfdf2.zig @@ -1,8 +1,6 @@ const common = @import("./common.zig"); const truncf = @import("./truncf.zig").truncf; -pub const panic = common.panic; - comptime { if (common.want_ppc_abi) { @export(&__trunctfdf2, .{ .name = "__trunckfdf2", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/trunctfhf2.zig b/lib/compiler_rt/trunctfhf2.zig index daf4776106dbc002dbb65d1f968ae64a70df245e..f5adc918c2d50fc455e08db16e09de88b25088a3 100644 --- a/lib/compiler_rt/trunctfhf2.zig +++ b/lib/compiler_rt/trunctfhf2.zig @@ -1,8 +1,6 @@ const common = @import("./common.zig"); const truncf = @import("./truncf.zig").truncf; -pub const panic = common.panic; - comptime { @export(&__trunctfhf2, .{ .name = "__trunctfhf2", .linkage = common.linkage, .visibility = common.visibility }); } diff --git a/lib/compiler_rt/trunctfsf2.zig b/lib/compiler_rt/trunctfsf2.zig index e238e35fb346184d7ee9b4a1a5ad5e0d83c722be..17e7570c9f0ecaa1b607d06deaf400a8ff83abc2 100644 --- a/lib/compiler_rt/trunctfsf2.zig +++ b/lib/compiler_rt/trunctfsf2.zig @@ -1,8 +1,6 @@ const common = @import("./common.zig"); const truncf = @import("./truncf.zig").truncf; -pub const panic = common.panic; - comptime { if (common.want_ppc_abi) { @export(&__trunctfsf2, .{ .name = "__trunckfsf2", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/trunctfxf2.zig b/lib/compiler_rt/trunctfxf2.zig index ddc977943a09fbcb3343e2d7452fc54f9de8052a..df0db2393a6277e451df262fedc0a5b4bf48fb1c 100644 --- a/lib/compiler_rt/trunctfxf2.zig +++ b/lib/compiler_rt/trunctfxf2.zig @@ -2,8 +2,6 @@ const math = @import("std").math; const common = @import("./common.zig"); const trunc_f80 = @import("./truncf.zig").trunc_f80; -pub const panic = common.panic; - comptime { @export(&__trunctfxf2, .{ .name = "__trunctfxf2", .linkage = common.linkage, .visibility = common.visibility }); } diff --git a/lib/compiler_rt/truncxfdf2.zig b/lib/compiler_rt/truncxfdf2.zig index f45a07962e5c4c35285ccf20a7a207a510bb2bad..1d7e66c278f3c0ecf55fa3e034ed429ad5bae913 100644 --- a/lib/compiler_rt/truncxfdf2.zig +++ b/lib/compiler_rt/truncxfdf2.zig @@ -1,8 +1,6 @@ const common = @import("./common.zig"); const trunc_f80 = @import("./truncf.zig").trunc_f80; -pub const panic = common.panic; - comptime { @export(&__truncxfdf2, .{ .name = "__truncxfdf2", .linkage = common.linkage, .visibility = common.visibility }); } diff --git a/lib/compiler_rt/truncxfhf2.zig b/lib/compiler_rt/truncxfhf2.zig index 34ee97c04b944db8f2d67a85740e8c95dad4dffe..b2dd9c7468265eb8ac5c0b9d865327e4e2208493 100644 --- a/lib/compiler_rt/truncxfhf2.zig +++ b/lib/compiler_rt/truncxfhf2.zig @@ -1,8 +1,6 @@ const common = @import("./common.zig"); const trunc_f80 = @import("./truncf.zig").trunc_f80; -pub const panic = common.panic; - comptime { @export(&__truncxfhf2, .{ .name = "__truncxfhf2", .linkage = common.linkage, .visibility = common.visibility }); } diff --git a/lib/compiler_rt/truncxfsf2.zig b/lib/compiler_rt/truncxfsf2.zig index 136e941af42543bc9926789f1e3f9d4fb58f405c..9d36f7aa29a407b0e266b74437cfdd0c869eae35 100644 --- a/lib/compiler_rt/truncxfsf2.zig +++ b/lib/compiler_rt/truncxfsf2.zig @@ -1,8 +1,6 @@ const common = @import("./common.zig"); const trunc_f80 = @import("./truncf.zig").trunc_f80; -pub const panic = common.panic; - comptime { @export(&__truncxfsf2, .{ .name = "__truncxfsf2", .linkage = common.linkage, .visibility = common.visibility }); } diff --git a/lib/compiler_rt/udivmodti4.zig b/lib/compiler_rt/udivmodti4.zig index 9e9585c635db05af3260e4c8456db2e3b20e2c0e..a1b89bb9625b5c221fb454c37511f6cb1fc981aa 100644 --- a/lib/compiler_rt/udivmodti4.zig +++ b/lib/compiler_rt/udivmodti4.zig @@ -3,8 +3,6 @@ const builtin = @import("builtin"); const udivmod = @import("udivmod.zig").udivmod; const common = @import("common.zig"); -pub const panic = common.panic; - comptime { if (common.want_windows_v2u64_abi) { @export(&__udivmodti4_windows_x86_64, .{ .name = "__udivmodti4", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/udivti3.zig b/lib/compiler_rt/udivti3.zig index 8423c1df1876bea7c8237dc1bd38730bf995c49d..a579d81e47dff972a53973e8276df9b28fede533 100644 --- a/lib/compiler_rt/udivti3.zig +++ b/lib/compiler_rt/udivti3.zig @@ -3,8 +3,6 @@ const builtin = @import("builtin"); const udivmod = @import("udivmod.zig").udivmod; const common = @import("common.zig"); -pub const panic = common.panic; - comptime { if (common.want_windows_v2u64_abi) { @export(&__udivti3_windows_x86_64, .{ .name = "__udivti3", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/umodti3.zig b/lib/compiler_rt/umodti3.zig index 592bc44390a11b34fc0108c88217fc694741af90..63cfcc2c25367aac11c205181494c2b84ee7a8af 100644 --- a/lib/compiler_rt/umodti3.zig +++ b/lib/compiler_rt/umodti3.zig @@ -3,8 +3,6 @@ const builtin = @import("builtin"); const udivmod = @import("udivmod.zig").udivmod; const common = @import("common.zig"); -pub const panic = common.panic; - comptime { if (common.want_windows_v2u64_abi) { @export(&__umodti3_windows_x86_64, .{ .name = "__umodti3", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/unorddf2.zig b/lib/compiler_rt/unorddf2.zig index dcc8762c66d78b0b9ce4a9655c150ab8d0b1bc21..5b17343a5168961aa57f61ee3ac3c9279ecd270e 100644 --- a/lib/compiler_rt/unorddf2.zig +++ b/lib/compiler_rt/unorddf2.zig @@ -1,8 +1,6 @@ const common = @import("./common.zig"); const comparef = @import("./comparef.zig"); -pub const panic = common.panic; - comptime { if (common.want_aeabi) { @export(&__aeabi_dcmpun, .{ .name = "__aeabi_dcmpun", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/unordhf2.zig b/lib/compiler_rt/unordhf2.zig index 2c2edad816866c9f3890b662b0e17f908c299d77..4fcffd1a1d1cca24f075b363f62370b938000b70 100644 --- a/lib/compiler_rt/unordhf2.zig +++ b/lib/compiler_rt/unordhf2.zig @@ -1,8 +1,6 @@ const common = @import("./common.zig"); const comparef = @import("./comparef.zig"); -pub const panic = common.panic; - comptime { @export(&__unordhf2, .{ .name = "__unordhf2", .linkage = common.linkage, .visibility = common.visibility }); } diff --git a/lib/compiler_rt/unordsf2.zig b/lib/compiler_rt/unordsf2.zig index a403fa4fd3d5f0466887234b934d20141162951c..56bfb94028d8e6623b4ca7cd141f7d81b9809f85 100644 --- a/lib/compiler_rt/unordsf2.zig +++ b/lib/compiler_rt/unordsf2.zig @@ -1,8 +1,6 @@ const common = @import("./common.zig"); const comparef = @import("./comparef.zig"); -pub const panic = common.panic; - comptime { if (common.want_aeabi) { @export(&__aeabi_fcmpun, .{ .name = "__aeabi_fcmpun", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/unordtf2.zig b/lib/compiler_rt/unordtf2.zig index 618f148296fd4e4bddbc73cf295e551e37bc7046..22e90acaaed05f16447edb08287d5122f73749af 100644 --- a/lib/compiler_rt/unordtf2.zig +++ b/lib/compiler_rt/unordtf2.zig @@ -1,8 +1,6 @@ const common = @import("./common.zig"); const comparef = @import("./comparef.zig"); -pub const panic = common.panic; - comptime { if (common.want_ppc_abi) { @export(&__unordtf2, .{ .name = "__unordkf2", .linkage = common.linkage, .visibility = common.visibility }); diff --git a/lib/compiler_rt/unordxf2.zig b/lib/compiler_rt/unordxf2.zig index 97a4f8d06f7b6231ee13da4121195c6d840d1976..2ebf5f413cc6ccfa26fd4df0a00e5064262b0edc 100644 --- a/lib/compiler_rt/unordxf2.zig +++ b/lib/compiler_rt/unordxf2.zig @@ -1,8 +1,6 @@ const common = @import("./common.zig"); const comparef = @import("./comparef.zig"); -pub const panic = common.panic; - comptime { @export(&__unordxf2, .{ .name = "__unordxf2", .linkage = common.linkage, .visibility = common.visibility }); } -- 2.54.0 From 08447ca47ed2ece816de9b0c5735be3f17edc6ca Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 1 Jan 2026 14:53:21 -0800 Subject: [PATCH 13/60] std.fs.path: make relative a pure function Instead of querying the operating system for current working directory and environment variables, this function now accepts those things as inputs. --- lib/compiler/build_runner.zig | 1 + lib/compiler/test_runner.zig | 2 +- lib/std/Build.zig | 3 +- lib/std/Build/Cache.zig | 35 ++- lib/std/Build/Step/Options.zig | 4 + lib/std/Build/Step/Run.zig | 16 +- lib/std/Build/Watch/FsEvents.zig | 12 +- lib/std/Build/WebServer.zig | 12 +- lib/std/Io/Threaded.zig | 15 +- lib/std/fs/path.zig | 416 ++++++++++++++++++++----------- lib/std/os/windows.zig | 135 +--------- lib/std/process/Args.zig | 3 +- lib/std/process/Child.zig | 2 +- lib/std/process/Environ.zig | 16 +- lib/std/start.zig | 161 ++++-------- 15 files changed, 405 insertions(+), 428 deletions(-) diff --git a/lib/compiler/build_runner.zig b/lib/compiler/build_runner.zig index f83456e93d50ed31798002c97e1ed52fb30f745f..6b225b205d2e5b7d2d2d0dac43b9c54084378fc3 100644 --- a/lib/compiler/build_runner.zig +++ b/lib/compiler/build_runner.zig @@ -84,6 +84,7 @@ pub fn main(init: process.Init.Minimal) !void { .io = io, .gpa = arena, .manifest_dir = try local_cache_directory.handle.createDirPathOpen(io, "h", .{}), + .cwd = try process.getCwdAlloc(single_threaded_arena.allocator()), }, .zig_exe = zig_exe, .env_map = try init.environ.createMap(arena), diff --git a/lib/compiler/test_runner.zig b/lib/compiler/test_runner.zig index f0971cfa39579608cda24cfa7b65cc07cdf318c1..5885f5cf12655871f0baf154514160d09ec48879 100644 --- a/lib/compiler/test_runner.zig +++ b/lib/compiler/test_runner.zig @@ -38,7 +38,7 @@ pub fn main(init: std.process.Init.Minimal) void { } if (need_simple) { - return mainSimple() catch @panic("test failure\n"); + return mainSimple() catch @panic("test failure"); } const args = init.args.toSlice(fba.allocator()) catch @panic("unable to parse command line args"); diff --git a/lib/std/Build.zig b/lib/std/Build.zig index 7e8c782dbdde1a0a1e366eee768a9a8e98b75d69..db88a114619f9c234b930d3d778c7524207df32d 100644 --- a/lib/std/Build.zig +++ b/lib/std/Build.zig @@ -1738,8 +1738,7 @@ pub fn pathFromRoot(b: *Build, sub_path: []const u8) []u8 { } fn pathFromCwd(b: *Build, sub_path: []const u8) []u8 { - const cwd = process.getCwdAlloc(b.allocator) catch @panic("OOM"); - return b.pathResolve(&.{ cwd, sub_path }); + return b.pathResolve(&.{ b.graph.cache.cwd, sub_path }); } pub fn pathJoin(b: *Build, paths: []const []const u8) []u8 { diff --git a/lib/std/Build/Cache.zig b/lib/std/Build/Cache.zig index b384ab13ff9ae1fc1509b36e60e34bbc91a57177..e35beca617d3d1913571195f17e4cb88795f10f2 100644 --- a/lib/std/Build/Cache.zig +++ b/lib/std/Build/Cache.zig @@ -30,6 +30,8 @@ mutex: Io.Mutex = .init, /// and usefulness of the cache for advanced use cases. prefixes_buffer: [4]Directory = undefined, prefixes_len: usize = 0, +/// Used to identify prefixes. References external memory. +cwd: []const u8, pub const Path = @import("Cache/Path.zig"); pub const Directory = @import("Cache/Directory.zig"); @@ -78,11 +80,12 @@ fn findPrefix(cache: *const Cache, file_path: []const u8) !PrefixedPath { /// Takes ownership of `resolved_path` on success. fn findPrefixResolved(cache: *const Cache, resolved_path: []u8) !PrefixedPath { const gpa = cache.gpa; + const cwd = cache.cwd; const prefixes_slice = cache.prefixes(); var i: u8 = 1; // Start at 1 to skip over checking the null prefix. while (i < prefixes_slice.len) : (i += 1) { const p = prefixes_slice[i].path.?; - const sub_path = getPrefixSubpath(gpa, p, resolved_path) catch |err| switch (err) { + const sub_path = getPrefixSubpath(gpa, cwd, p, resolved_path) catch |err| switch (err) { error.NotASubPath => continue, else => |e| return e, }; @@ -100,10 +103,10 @@ fn findPrefixResolved(cache: *const Cache, resolved_path: []u8) !PrefixedPath { }; } -fn getPrefixSubpath(allocator: Allocator, prefix: []const u8, path: []u8) ![]u8 { - const relative = try std.fs.path.relative(allocator, prefix, path); - errdefer allocator.free(relative); - var component_iterator = std.fs.path.NativeComponentIterator.init(relative); +fn getPrefixSubpath(gpa: Allocator, cwd: []const u8, prefix: []const u8, path: []u8) ![]u8 { + const relative = try std.fs.path.relative(gpa, cwd, null, prefix, path); + errdefer gpa.free(relative); + var component_iterator: std.fs.path.NativeComponentIterator = .init(relative); if (component_iterator.root() != null) { return error.NotASubPath; } @@ -1307,11 +1310,14 @@ fn testGetCurrentFileTimestamp(io: Io, dir: Io.Dir) !Io.Timestamp { } test "cache file and then recall it" { - const io = std.testing.io; + const io = testing.io; var tmp = testing.tmpDir(.{}); defer tmp.cleanup(); + const cwd = try std.process.getCwdAlloc(testing.allocator); + defer testing.allocator.free(cwd); + const temp_file = "test.txt"; const temp_manifest_dir = "temp_manifest_dir"; @@ -1331,6 +1337,7 @@ test "cache file and then recall it" { .io = io, .gpa = testing.allocator, .manifest_dir = try tmp.dir.createDirPathOpen(io, temp_manifest_dir, .{}), + .cwd = cwd, }; cache.addPrefix(.{ .path = null, .handle = tmp.dir }); defer cache.manifest_dir.close(io); @@ -1371,11 +1378,14 @@ test "cache file and then recall it" { } test "check that changing a file makes cache fail" { - const io = std.testing.io; + const io = testing.io; var tmp = testing.tmpDir(.{}); defer tmp.cleanup(); + const cwd = try std.process.getCwdAlloc(testing.allocator); + defer testing.allocator.free(cwd); + const temp_file = "cache_hash_change_file_test.txt"; const temp_manifest_dir = "cache_hash_change_file_manifest_dir"; const original_temp_file_contents = "Hello, world!\n"; @@ -1397,6 +1407,7 @@ test "check that changing a file makes cache fail" { .io = io, .gpa = testing.allocator, .manifest_dir = try tmp.dir.createDirPathOpen(io, temp_manifest_dir, .{}), + .cwd = cwd, }; cache.addPrefix(.{ .path = null, .handle = tmp.dir }); defer cache.manifest_dir.close(io); @@ -1448,6 +1459,9 @@ test "no file inputs" { var tmp = testing.tmpDir(.{}); defer tmp.cleanup(); + const cwd = try std.process.getCwdAlloc(testing.allocator); + defer testing.allocator.free(cwd); + const temp_manifest_dir = "no_file_inputs_manifest_dir"; var digest1: HexDigest = undefined; @@ -1457,6 +1471,7 @@ test "no file inputs" { .io = io, .gpa = testing.allocator, .manifest_dir = try tmp.dir.createDirPathOpen(io, temp_manifest_dir, .{}), + .cwd = cwd, }; cache.addPrefix(.{ .path = null, .handle = tmp.dir }); defer cache.manifest_dir.close(io); @@ -1489,11 +1504,14 @@ test "no file inputs" { } test "Manifest with files added after initial hash work" { - const io = std.testing.io; + const io = testing.io; var tmp = testing.tmpDir(.{}); defer tmp.cleanup(); + const cwd = try std.process.getCwdAlloc(testing.allocator); + defer testing.allocator.free(cwd); + const temp_file1 = "cache_hash_post_file_test1.txt"; const temp_file2 = "cache_hash_post_file_test2.txt"; const temp_manifest_dir = "cache_hash_post_file_manifest_dir"; @@ -1516,6 +1534,7 @@ test "Manifest with files added after initial hash work" { .io = io, .gpa = testing.allocator, .manifest_dir = try tmp.dir.createDirPathOpen(io, temp_manifest_dir, .{}), + .cwd = cwd, }; cache.addPrefix(.{ .path = null, .handle = tmp.dir }); defer cache.manifest_dir.close(io); diff --git a/lib/std/Build/Step/Options.zig b/lib/std/Build/Step/Options.zig index 8cfa7c1261d9a54c5d64dfb84c5a0452f82b7c6c..614ff515d45e9fb1f8f4f89e105a45d31c11a165 100644 --- a/lib/std/Build/Step/Options.zig +++ b/lib/std/Build/Step/Options.zig @@ -537,6 +537,9 @@ test Options { var arena = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena.deinit(); + const cwd = try std.process.getCwdAlloc(std.testing.allocator); + defer std.testing.allocator.free(cwd); + var graph: std.Build.Graph = .{ .io = io, .arena = arena.allocator(), @@ -544,6 +547,7 @@ test Options { .io = io, .gpa = arena.allocator(), .manifest_dir = Io.Dir.cwd(), + .cwd = cwd, }, .zig_exe = "test", .env_map = std.process.Environ.Map.init(arena.allocator()), diff --git a/lib/std/Build/Step/Run.zig b/lib/std/Build/Step/Run.zig index 5500d16ecad1612f2c60c5f07815b2bc277cfd82..32b04b4a988b923957f31f33a8e27f257c94ad52 100644 --- a/lib/std/Build/Step/Run.zig +++ b/lib/std/Build/Step/Run.zig @@ -750,28 +750,30 @@ fn checksContainStderr(checks: []const StdIo.Check) bool { /// to make sure the child doesn't see paths relative to a cwd other than its own. fn convertPathArg(run: *Run, path: Build.Cache.Path) []const u8 { const b = run.step.owner; - const path_str = path.toString(b.graph.arena) catch @panic("OOM"); + const graph = b.graph; + const arena = graph.arena; + + const path_str = path.toString(arena) catch @panic("OOM"); if (Dir.path.isAbsolute(path_str)) { // Absolute paths don't need changing. return path_str; } const child_cwd_rel: []const u8 = rel: { const child_lazy_cwd = run.cwd orelse break :rel path_str; - const child_cwd = child_lazy_cwd.getPath3(b, &run.step).toString(b.graph.arena) catch @panic("OOM"); + const child_cwd = child_lazy_cwd.getPath3(b, &run.step).toString(arena) catch @panic("OOM"); // Convert it from relative to *our* cwd, to relative to the *child's* cwd. - break :rel Dir.path.relative(b.graph.arena, child_cwd, path_str) catch @panic("OOM"); + break :rel Dir.path.relative(arena, graph.cache.cwd, &graph.env_map, child_cwd, path_str) catch @panic("OOM"); }; // Not every path can be made relative, e.g. if the path and the child cwd are on different // disk designators on Windows. In that case, `relative` will return an absolute path which we can // just return. - if (Dir.path.isAbsolute(child_cwd_rel)) { - return child_cwd_rel; - } + if (Dir.path.isAbsolute(child_cwd_rel)) return child_cwd_rel; + // We're not done yet. In some cases this path must be prefixed with './': // * On POSIX, the executable name cannot be a single component like 'foo' // * Some executables might treat a leading '-' like a flag, which we must avoid // There's no harm in it, so just *always* apply this prefix. - return Dir.path.join(b.graph.arena, &.{ ".", child_cwd_rel }) catch @panic("OOM"); + return Dir.path.join(arena, &.{ ".", child_cwd_rel }) catch @panic("OOM"); } const IndexedOutput = struct { diff --git a/lib/std/Build/Watch/FsEvents.zig b/lib/std/Build/Watch/FsEvents.zig index 2a48534b3accd009b42efa9abcb64d2da725e66f..b23805c7ee14a459a69adea27ebe68be3371276f 100644 --- a/lib/std/Build/Watch/FsEvents.zig +++ b/lib/std/Build/Watch/FsEvents.zig @@ -43,6 +43,8 @@ dispatch_queue: dispatch_queue_t, /// of writing. See the comment at the start of `wait` for details. since_event: FSEventStreamEventId, +cwd_path: []const u8, + /// All of the symbols we pull from the `dlopen`ed CoreServices framework. If any of these symbols /// is not present, `init` will close the framework and return an error. const ResolvedSymbols = struct { @@ -78,7 +80,7 @@ const ResolvedSymbols = struct { kCFAllocatorUseContext: *const CFAllocatorRef, }; -pub fn init() error{ OpenFrameworkFailed, MissingCoreServicesSymbol }!FsEvents { +pub fn init(cwd_path: []const u8) error{ OpenFrameworkFailed, MissingCoreServicesSymbol }!FsEvents { var core_services = std.DynLib.open("/System/Library/Frameworks/CoreServices.framework/CoreServices") catch return error.OpenFrameworkFailed; errdefer core_services.close(); @@ -99,6 +101,7 @@ pub fn init() error{ OpenFrameworkFailed, MissingCoreServicesSymbol }!FsEvents { // Not `.since_now`, because this means we can init `FsEvents` *before* we do work in order // to notice any changes which happened during said work. .since_event = resolved_symbols.FSEventsGetCurrentEventId(), + .cwd_path = cwd_path, }; } @@ -120,9 +123,6 @@ pub fn setPaths(fse: *FsEvents, gpa: Allocator, steps: []const *std.Build.Step) defer fse.paths_arena = paths_arena_instance.state; const paths_arena = paths_arena_instance.allocator(); - const cwd_path = try std.process.getCwdAlloc(gpa); - defer gpa.free(cwd_path); - var need_dirs: std.StringArrayHashMapUnmanaged(void) = .empty; defer need_dirs.deinit(gpa); @@ -131,7 +131,9 @@ pub fn setPaths(fse: *FsEvents, gpa: Allocator, steps: []const *std.Build.Step) // We take `step` by pointer for a slight memory optimization in a moment. for (steps) |*step| { for (step.*.inputs.table.keys(), step.*.inputs.table.values()) |path, *files| { - const resolved_dir = try std.fs.path.resolvePosix(paths_arena, &.{ cwd_path, path.root_dir.path orelse ".", path.sub_path }); + const resolved_dir = try std.fs.path.resolvePosix(paths_arena, &.{ + fse.cwd_path, path.root_dir.path orelse ".", path.sub_path, + }); try need_dirs.put(gpa, resolved_dir, {}); for (files.items) |file_name| { const watch_path = if (std.mem.eql(u8, file_name, ".")) diff --git a/lib/std/Build/WebServer.zig b/lib/std/Build/WebServer.zig index ae9a200f24772851584d7bd694886f9029f58a93..9d1ad45524e9ee20e63b9132bbfece57bf667e2f 100644 --- a/lib/std/Build/WebServer.zig +++ b/lib/std/Build/WebServer.zig @@ -482,8 +482,8 @@ pub fn serveFile( }); } pub fn serveTarFile(ws: *WebServer, request: *http.Server.Request, paths: []const Cache.Path) !void { - const gpa = ws.gpa; - const io = ws.graph.io; + const graph = ws.graph; + const io = graph.io; var send_buffer: [0x4000]u8 = undefined; var response = try request.respondStreaming(&send_buffer, .{ @@ -495,9 +495,6 @@ pub fn serveTarFile(ws: *WebServer, request: *http.Server.Request, paths: []cons }, }); - var cached_cwd_path: ?[]const u8 = null; - defer if (cached_cwd_path) |p| gpa.free(p); - var archiver: std.tar.Writer = .{ .underlying_writer = &response.writer }; for (paths) |path| { @@ -516,10 +513,7 @@ pub fn serveTarFile(ws: *WebServer, request: *http.Server.Request, paths: []cons // resulting in modules named "" and "src". The compiler needs to tell the build system // about the module graph so that the build system can correctly encode this information in // the tar file. - archiver.prefix = path.root_dir.path orelse cwd: { - if (cached_cwd_path == null) cached_cwd_path = try std.process.getCwdAlloc(gpa); - break :cwd cached_cwd_path.?; - }; + archiver.prefix = path.root_dir.path orelse graph.cache.cwd; try archiver.writeFile(path.sub_path, &file_reader, @intCast(stat.mtime.toSeconds())); } diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 08c12a565394be0999442e40a27adfb71e568fa9..6843c02bb525e35f5f2865205eb3d81e13ed791a 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -86,14 +86,14 @@ pub const Argv0 = switch (native_os) { const Environ = struct { /// Unmodified data directly from the OS. - block: process.Environ.Block = &.{}, + process_environ: process.Environ = .empty, /// Protected by `mutex`. Determines whether the other fields have been - /// memoized based on `block`. + /// memoized based on `process_environ`. initialized: bool = false, - /// Protected by `mutex`. Memoized based on `block`. Tracks whether the + /// Protected by `mutex`. Memoized based on `process_environ`. Tracks whether the /// environment variables are present, ignoring their value. exist: Exist = .{}, - /// Protected by `mutex`. Memoized based on `block`. + /// Protected by `mutex`. Memoized based on `process_environ`. string: String = .{}, /// ZIG_PROGRESS zig_progress_handle: std.Progress.ParentFileError!u31 = error.EnvironmentVariableMissing, @@ -1186,7 +1186,7 @@ pub fn init( .have_signal_handler = false, .argv0 = options.argv0, .worker_threads = .init(null), - .environ = .{ .block = options.environ.block }, + .environ = .{ .process_environ = options.environ }, .robust_cancel = options.robust_cancel, }; @@ -12693,7 +12693,7 @@ fn scanEnviron(t: *Threaded) void { comptime assert(@sizeOf(Environ.String) == 0); } } else { - for (t.environ.block) |opt_line| { + for (t.environ.process_environ.block) |opt_line| { const line = opt_line.?; var line_i: usize = 0; while (line[line_i] != 0 and line[line_i] != '=') : (line_i += 1) {} @@ -12837,7 +12837,7 @@ fn processSpawnPosix(userdata: ?*anyopaque, options: process.SpawnOptions) proce .zig_progress_fd = prog_fd, })).ptr; } - break :m (try process.Environ.createBlockPosix(.{ .block = t.environ.block }, arena, .{ + break :m (try process.Environ.createBlockPosix(t.environ.process_environ, arena, .{ .zig_progress_fd = prog_fd, })).ptr; }; @@ -12934,6 +12934,7 @@ fn processSpawnPosix(userdata: ?*anyopaque, options: process.SpawnOptions) proce return .{ .id = pid, + .thread_handle = {}, .stdin = switch (options.stdin) { .pipe => .{ .handle = stdin_pipe[1] }, else => null, diff --git a/lib/std/fs/path.zig b/lib/std/fs/path.zig index a8465f52d4148d10aea014b10a330b3d0578f371..4af6547249d76904317e5faf14731ca6643a8ec0 100644 --- a/lib/std/fs/path.zig +++ b/lib/std/fs/path.zig @@ -13,16 +13,15 @@ //! https://github.com/WebAssembly/wasi-filesystem/issues/17#issuecomment-1430639353 const builtin = @import("builtin"); +const native_os = builtin.target.os.tag; + const std = @import("../std.zig"); -const debug = std.debug; -const assert = debug.assert; +const assert = std.debug.assert; const testing = std.testing; const mem = std.mem; -const ascii = std.ascii; -const Allocator = mem.Allocator; -const windows = std.os.windows; -const process = std.process; -const native_os = builtin.target.os.tag; +const Allocator = std.mem.Allocator; +const eqlIgnoreCaseWtf8 = std.os.windows.eqlIgnoreCaseWtf8; +const eqlIgnoreCaseWtf16 = std.os.windows.eqlIgnoreCaseWtf16; pub const sep_windows: u8 = '\\'; pub const sep_posix: u8 = '/'; @@ -281,7 +280,7 @@ pub fn isAbsolute(path: []const u8) bool { } fn isAbsoluteWindowsImpl(comptime T: type, path: []const T) bool { - return switch (windows.getWin32PathType(T, path)) { + return switch (getWin32PathType(T, path)) { // Unambiguously absolute .drive_absolute, .unc_absolute, .local_device, .root_local_device => true, // Unambiguously relative @@ -515,13 +514,13 @@ test parsePathPosix { pub fn WindowsPath2(comptime T: type) type { return struct { - kind: windows.Win32PathType, + kind: Win32PathType, root: []const T, }; } pub fn parsePathWindows(comptime T: type, path: []const T) WindowsPath2(T) { - const kind = windows.getWin32PathType(T, path); + const kind = getWin32PathType(T, path); const root = root: switch (kind) { .drive_absolute, .drive_relative => { const drive_letter_len = getDriveLetter(T, path).len; @@ -731,7 +730,7 @@ fn parseUNC(comptime T: type, path: []const T) WindowsUNC(T) { // For the share, there can be any number of path separators between the server // and the share, so we want to skip over all of them instead of just looking for // the first one. - var it = std.mem.tokenizeAny(T, path[server_end + 1 ..], any_sep); + var it = mem.tokenizeAny(T, path[server_end + 1 ..], any_sep); const share = it.next() orelse return .{ .server = path[2..server_end], .sep_after_server = true, @@ -803,8 +802,8 @@ const DiskDesignatorKind = enum { drive, unc }; /// `p1` and `p2` are both assumed to be the `kind` provided. fn compareDiskDesignators(comptime T: type, kind: DiskDesignatorKind, p1: []const T, p2: []const T) bool { const eql = switch (T) { - u8 => windows.eqlIgnoreCaseWtf8, - u16 => windows.eqlIgnoreCaseWtf16, + u8 => eqlIgnoreCaseWtf8, + u16 => eqlIgnoreCaseWtf16, else => @compileError("only u8 (WTF-8) and u16 (WTF-16LE) is supported"), }; switch (kind) { @@ -1094,10 +1093,14 @@ pub fn resolveWindows(allocator: Allocator, paths: []const []const u8) Allocator } /// This function is like a series of `cd` statements executed one after another. +/// /// It resolves "." and ".." to the best of its ability, but will not convert relative paths to /// an absolute path, use Io.Dir.realpath instead. +/// /// ".." components may persist in the resolved path if the resolved path is relative. +/// /// The result does not have a trailing path separator. +/// /// This function does not perform any syscalls. Executing this series of path /// lookups on the actual filesystem may produce different results due to /// symlinks. @@ -1494,25 +1497,54 @@ fn testBasenameWindows(input: []const u8, expected_output: []const u8) !void { try testing.expectEqualSlices(u8, expected_output, basenameWindows(input)); } -pub const RelativeError = std.process.GetCwdAllocError; - -/// Returns the relative path from `from` to `to`. If `from` and `to` each -/// resolve to the same path (after calling `resolve` on each), a zero-length -/// string is returned. -/// On Windows, the result is not guaranteed to be relative, as the paths may be -/// on different volumes. In that case, the result will be the canonicalized absolute -/// path of `to`. -pub fn relative(allocator: Allocator, from: []const u8, to: []const u8) RelativeError![]u8 { +/// Returns the non-absolute path from `from` to `to`. +/// +/// Other than memory allocation, this is a pure function; the result solely +/// depends on the input parameters. +/// +/// If `from` and `to` each resolve to the same path (after calling `resolve` +/// on each), a zero-length string is returned. +/// +/// See `relativePosix` and `relativeWindows` for operating system specific +/// details and for how `env_map` is used. +pub fn relative( + gpa: Allocator, + cwd: []const u8, + env_map: ?*const std.process.Environ.Map, + from: []const u8, + to: []const u8, +) Allocator.Error![]u8 { if (native_os == .windows) { - return relativeWindows(allocator, from, to); + return relativeWindows(gpa, cwd, env_map, from, to); } else { - return relativePosix(allocator, from, to); + return relativePosix(gpa, cwd, from, to); } } -pub fn relativeWindows(allocator: Allocator, from: []const u8, to: []const u8) ![]u8 { - if (native_os != .windows) @compileError("this function relies on Windows-specific semantics"); - +/// Returns the non-absolute path from `from` to `to` according to Windows rules. +/// +/// Other than memory allocation, this is a pure function; the result solely +/// depends on the input parameters. +/// +/// If `from` and `to` each resolve to the same path (after calling `resolve` +/// on each), a zero-length string is returned. +/// +/// The result is not guaranteed to be relative, as the paths may be on +/// different volumes. In that case, the result will be the canonicalized +/// absolute path of `to`. +/// +/// Per-drive CWDs are stored in special semi-hidden environment variables of +/// the format `=:`, e.g. `=C:`. This type of CWD is purely a +/// shell concept, so there's no guarantee that it'll be set or that it'll even +/// be accurate. This is the only reason for the `env_map` parameter. `null` is +/// treated equivalent to the environment variable missing. +pub fn relativeWindows( + gpa: Allocator, + cwd: []const u8, + env_map: ?*const std.process.Environ.Map, + from: []const u8, + to: []const u8, +) Allocator.Error![]u8 { const parsed_from = parsePathWindows(u8, from); const parsed_to = parsePathWindows(u8, to); @@ -1533,14 +1565,14 @@ pub fn relativeWindows(allocator: Allocator, from: []const u8, to: []const u8) ! }; if (result_is_always_to) { - return windowsResolveAgainstCwd(allocator, to, parsed_to); + return windowsResolveAgainstCwd(gpa, cwd, env_map, to, parsed_to); } - const resolved_from = try windowsResolveAgainstCwd(allocator, from, parsed_from); - defer allocator.free(resolved_from); + const resolved_from = try windowsResolveAgainstCwd(gpa, cwd, env_map, from, parsed_from); + defer gpa.free(resolved_from); var clean_up_resolved_to = true; - const resolved_to = try windowsResolveAgainstCwd(allocator, to, parsed_to); - defer if (clean_up_resolved_to) allocator.free(resolved_to); + const resolved_to = try windowsResolveAgainstCwd(gpa, cwd, env_map, to, parsed_to); + defer if (clean_up_resolved_to) gpa.free(resolved_to); const parsed_resolved_from = parsePathWindows(u8, resolved_from); const parsed_resolved_to = parsePathWindows(u8, resolved_to); @@ -1569,18 +1601,18 @@ pub fn relativeWindows(allocator: Allocator, from: []const u8, to: []const u8) ! var from_it = mem.tokenizeAny(u8, resolved_from[parsed_resolved_from.root.len..], "/\\"); var to_it = mem.tokenizeAny(u8, resolved_to[parsed_resolved_to.root.len..], "/\\"); while (true) { - const from_component = from_it.next() orelse return allocator.dupe(u8, to_it.rest()); + const from_component = from_it.next() orelse return gpa.dupe(u8, to_it.rest()); const to_rest = to_it.rest(); if (to_it.next()) |to_component| { - if (windows.eqlIgnoreCaseWtf8(from_component, to_component)) + if (eqlIgnoreCaseWtf8(from_component, to_component)) continue; } var up_index_end = "..".len; while (from_it.next()) |_| { up_index_end += "\\..".len; } - const result = try allocator.alloc(u8, up_index_end + @intFromBool(to_rest.len > 0) + to_rest.len); - errdefer allocator.free(result); + const result = try gpa.alloc(u8, up_index_end + @intFromBool(to_rest.len > 0) + to_rest.len); + errdefer gpa.free(result); result[0..2].* = "..".*; var result_index: usize = 2; @@ -1597,85 +1629,60 @@ pub fn relativeWindows(allocator: Allocator, from: []const u8, to: []const u8) ! result_index += to_component.len; } - return allocator.realloc(result, result_index); + return gpa.realloc(result, result_index); } return [_]u8{}; } -fn windowsResolveAgainstCwd(allocator: Allocator, path: []const u8, parsed: WindowsPath2(u8)) ![]u8 { +fn windowsResolveAgainstCwd( + gpa: Allocator, + cwd: []const u8, + env_map: ?*const std.process.Environ.Map, + path: []const u8, + parsed: WindowsPath2(u8), +) ![]u8 { // Space for 256 WTF-16 code units; potentially 3 WTF-8 bytes per WTF-16 code unit - var temp_allocator_state = std.heap.stackFallback(256 * 3, allocator); + var temp_allocator_state = std.heap.stackFallback(256 * 3, gpa); return switch (parsed.kind) { .drive_absolute, .unc_absolute, .root_local_device, .local_device, - => try resolveWindows(allocator, &.{path}), - .relative => blk: { - const temp_allocator = temp_allocator_state.get(); + => try resolveWindows(gpa, &.{path}), - const peb_cwd = windows.peb().ProcessParameters.CurrentDirectory.DosPath; - const cwd_w = (peb_cwd.Buffer.?)[0 .. peb_cwd.Length / 2]; + .relative => try resolveWindows(gpa, &.{ cwd, path }), - const wtf8_len = std.unicode.calcWtf8Len(cwd_w); - const wtf8_buf = try temp_allocator.alloc(u8, wtf8_len); - defer temp_allocator.free(wtf8_buf); - assert(std.unicode.wtf16LeToWtf8(wtf8_buf, cwd_w) == wtf8_len); - - break :blk try resolveWindows(allocator, &.{ wtf8_buf, path }); - }, .rooted => blk: { - const peb_cwd = windows.peb().ProcessParameters.CurrentDirectory.DosPath; - const cwd_w = (peb_cwd.Buffer.?)[0 .. peb_cwd.Length / 2]; - const parsed_cwd = parsePathWindows(u16, cwd_w); + const parsed_cwd = parsePathWindows(u8, cwd); switch (parsed_cwd.kind) { .drive_absolute => { var drive_buf = "_:\\".*; - drive_buf[0] = @truncate(cwd_w[0]); - break :blk try resolveWindows(allocator, &.{ &drive_buf, path }); + drive_buf[0] = cwd[0]; + break :blk try resolveWindows(gpa, &.{ &drive_buf, path }); }, .unc_absolute => { - const temp_allocator = temp_allocator_state.get(); - var root_buf = try temp_allocator.alloc(u8, parsed_cwd.root.len * 3); - defer temp_allocator.free(root_buf); - - const wtf8_len = std.unicode.wtf16LeToWtf8(root_buf, parsed_cwd.root); - const root = root_buf[0..wtf8_len]; - break :blk try resolveWindows(allocator, &.{ root, path }); + break :blk try resolveWindows(gpa, &.{ parsed_cwd.root, path }); }, // Effectively a malformed CWD, give up and just return a normalized path - else => break :blk try resolveWindows(allocator, &.{path}), + else => break :blk try resolveWindows(gpa, &.{path}), } }, .drive_relative => blk: { const temp_allocator = temp_allocator_state.get(); const drive_cwd = drive_cwd: { - const peb_cwd = windows.peb().ProcessParameters.CurrentDirectory.DosPath; - const cwd_w = (peb_cwd.Buffer.?)[0 .. peb_cwd.Length / 2]; - const parsed_cwd = parsePathWindows(u16, cwd_w); + const parsed_cwd = parsePathWindows(u8, cwd); if (parsed_cwd.kind == .drive_absolute) { const drive_letter_w = parsed_cwd.root[0]; const drive_letters_match = drive_letter_w <= 0x7F and - ascii.toUpper(@intCast(drive_letter_w)) == ascii.toUpper(parsed.root[0]); - if (drive_letters_match) { - const wtf8_len = std.unicode.calcWtf8Len(cwd_w); - const wtf8_buf = try temp_allocator.alloc(u8, wtf8_len); - assert(std.unicode.wtf16LeToWtf8(wtf8_buf, cwd_w) == wtf8_len); - break :drive_cwd wtf8_buf[0..]; - } + std.ascii.toUpper(@intCast(drive_letter_w)) == std.ascii.toUpper(parsed.root[0]); + if (drive_letters_match) + break :drive_cwd cwd; - // Per-drive CWD's are stored in special semi-hidden environment variables - // of the format `=:`, e.g. `=C:`. This type of CWD is - // purely a shell concept, so there's no guarantee that it'll be set - // or that it'll even be accurate. - var key_buf = std.unicode.wtf8ToWtf16LeStringLiteral("=_:").*; - key_buf[1] = parsed.root[0]; - if (std.process.getenvW(&key_buf)) |drive_cwd_w| { - const wtf8_len = std.unicode.calcWtf8Len(drive_cwd_w); - const wtf8_buf = try temp_allocator.alloc(u8, wtf8_len); - assert(std.unicode.wtf16LeToWtf8(wtf8_buf, drive_cwd_w) == wtf8_len); - break :drive_cwd wtf8_buf[0..]; + if (env_map) |m| { + if (m.get(&.{ '=', parsed.root[0], ':' })) |v| { + break :drive_cwd try temp_allocator.dupe(u8, v); + } } } @@ -1686,16 +1693,20 @@ fn windowsResolveAgainstCwd(allocator: Allocator, path: []const u8, parsed: Wind break :drive_cwd drive_buf; }; defer temp_allocator.free(drive_cwd); - break :blk try resolveWindows(allocator, &.{ drive_cwd, path }); + break :blk try resolveWindows(gpa, &.{ drive_cwd, path }); }, }; } -pub fn relativePosix(allocator: Allocator, from: []const u8, to: []const u8) ![]u8 { - if (native_os == .windows) @compileError("this function relies on semantics that do not apply to Windows"); - - const cwd = try process.getCwdAlloc(allocator); - defer allocator.free(cwd); +/// Returns the non-absolute path from `from` to `to` according to Windows rules. +/// +/// Other than memory allocation, this is a pure function; the result solely +/// depends on the input parameters. +/// +/// If `from` and `to` each resolve to the same path (after calling `resolve` +/// on each), a zero-length string is returned. +/// +pub fn relativePosix(allocator: Allocator, cwd: []const u8, from: []const u8, to: []const u8) Allocator.Error![]u8 { const resolved_from = try resolvePosix(allocator, &[_][]const u8{ cwd, from }); defer allocator.free(resolved_from); const resolved_to = try resolvePosix(allocator, &[_][]const u8{ cwd, to }); @@ -1736,69 +1747,67 @@ pub fn relativePosix(allocator: Allocator, from: []const u8, to: []const u8) ![] } test relative { - if (native_os == .windows) { - try testRelativeWindows("c:/blah\\blah", "d:/games", "D:\\games"); - try testRelativeWindows("c:/aaaa/bbbb", "c:/aaaa", ".."); - try testRelativeWindows("c:/aaaa/bbbb", "c:/cccc", "..\\..\\cccc"); - try testRelativeWindows("c:/aaaa/bbbb", "C:/aaaa/bbbb", ""); - try testRelativeWindows("c:/aaaa/bbbb", "c:/aaaa/cccc", "..\\cccc"); - try testRelativeWindows("c:/aaaa/", "c:/aaaa/cccc", "cccc"); - try testRelativeWindows("c:/", "c:\\aaaa\\bbbb", "aaaa\\bbbb"); - try testRelativeWindows("c:/aaaa/bbbb", "d:\\", "D:\\"); - try testRelativeWindows("c:/AaAa/bbbb", "c:/aaaa/bbbb", ""); - try testRelativeWindows("c:/aaaaa/", "c:/aaaa/cccc", "..\\aaaa\\cccc"); - try testRelativeWindows("C:\\foo\\bar\\baz\\quux", "C:\\", "..\\..\\..\\.."); - try testRelativeWindows("C:\\foo\\test", "C:\\foo\\test\\bar\\package.json", "bar\\package.json"); - try testRelativeWindows("C:\\foo\\bar\\baz-quux", "C:\\foo\\bar\\baz", "..\\baz"); - try testRelativeWindows("C:\\foo\\bar\\baz", "C:\\foo\\bar\\baz-quux", "..\\baz-quux"); - try testRelativeWindows("\\\\foo\\bar", "\\\\foo\\bar\\baz", "baz"); - try testRelativeWindows("\\\\foo\\bar\\baz", "\\\\foo\\bar", ".."); - try testRelativeWindows("\\\\foo\\bar\\baz-quux", "\\\\foo\\bar\\baz", "..\\baz"); - try testRelativeWindows("\\\\foo/bar\\baz-quux", "//foo\\bar/baz", "..\\baz"); - try testRelativeWindows("\\\\foo\\bar\\baz", "\\\\foo\\bar\\baz-quux", "..\\baz-quux"); - try testRelativeWindows("C:\\baz-quux", "C:\\baz", "..\\baz"); - try testRelativeWindows("C:\\baz", "C:\\baz-quux", "..\\baz-quux"); - try testRelativeWindows("\\\\foo\\baz-quux", "\\\\foo\\baz", "\\\\foo\\baz"); - try testRelativeWindows("\\\\foo\\baz", "\\\\foo\\baz-quux", "\\\\foo\\baz-quux"); - try testRelativeWindows("C:\\baz", "\\\\foo\\bar\\baz", "\\\\foo\\bar\\baz"); - try testRelativeWindows("\\\\foo\\bar\\baz", "C:\\baz", "C:\\baz"); + try testRelativeWindows("c:/blah\\blah", "d:/games", "D:\\games"); + try testRelativeWindows("c:/aaaa/bbbb", "c:/aaaa", ".."); + try testRelativeWindows("c:/aaaa/bbbb", "c:/cccc", "..\\..\\cccc"); + try testRelativeWindows("c:/aaaa/bbbb", "C:/aaaa/bbbb", ""); + try testRelativeWindows("c:/aaaa/bbbb", "c:/aaaa/cccc", "..\\cccc"); + try testRelativeWindows("c:/aaaa/", "c:/aaaa/cccc", "cccc"); + try testRelativeWindows("c:/", "c:\\aaaa\\bbbb", "aaaa\\bbbb"); + try testRelativeWindows("c:/aaaa/bbbb", "d:\\", "D:\\"); + try testRelativeWindows("c:/AaAa/bbbb", "c:/aaaa/bbbb", ""); + try testRelativeWindows("c:/aaaaa/", "c:/aaaa/cccc", "..\\aaaa\\cccc"); + try testRelativeWindows("C:\\foo\\bar\\baz\\quux", "C:\\", "..\\..\\..\\.."); + try testRelativeWindows("C:\\foo\\test", "C:\\foo\\test\\bar\\package.json", "bar\\package.json"); + try testRelativeWindows("C:\\foo\\bar\\baz-quux", "C:\\foo\\bar\\baz", "..\\baz"); + try testRelativeWindows("C:\\foo\\bar\\baz", "C:\\foo\\bar\\baz-quux", "..\\baz-quux"); + try testRelativeWindows("\\\\foo\\bar", "\\\\foo\\bar\\baz", "baz"); + try testRelativeWindows("\\\\foo\\bar\\baz", "\\\\foo\\bar", ".."); + try testRelativeWindows("\\\\foo\\bar\\baz-quux", "\\\\foo\\bar\\baz", "..\\baz"); + try testRelativeWindows("\\\\foo/bar\\baz-quux", "//foo\\bar/baz", "..\\baz"); + try testRelativeWindows("\\\\foo\\bar\\baz", "\\\\foo\\bar\\baz-quux", "..\\baz-quux"); + try testRelativeWindows("C:\\baz-quux", "C:\\baz", "..\\baz"); + try testRelativeWindows("C:\\baz", "C:\\baz-quux", "..\\baz-quux"); + try testRelativeWindows("\\\\foo\\baz-quux", "\\\\foo\\baz", "\\\\foo\\baz"); + try testRelativeWindows("\\\\foo\\baz", "\\\\foo\\baz-quux", "\\\\foo\\baz-quux"); + try testRelativeWindows("C:\\baz", "\\\\foo\\bar\\baz", "\\\\foo\\bar\\baz"); + try testRelativeWindows("\\\\foo\\bar\\baz", "C:\\baz", "C:\\baz"); - try testRelativeWindows("c:blah\\blah", "c:foo", "..\\..\\foo"); - try testRelativeWindows("c:foo", "c:foo\\bar", "bar"); - try testRelativeWindows("\\blah\\blah", "\\foo", "..\\..\\foo"); - try testRelativeWindows("\\foo", "\\foo\\bar", "bar"); + try testRelativeWindows("c:blah\\blah", "c:foo", "..\\..\\foo"); + try testRelativeWindows("c:foo", "c:foo\\bar", "bar"); + try testRelativeWindows("\\blah\\blah", "\\foo", "..\\..\\foo"); + try testRelativeWindows("\\foo", "\\foo\\bar", "bar"); - try testRelativeWindows("a/b/c", "a\\b", ".."); - try testRelativeWindows("a/b/c", "a", "..\\.."); - try testRelativeWindows("a/b/c", "a\\b\\c\\d", "d"); + try testRelativeWindows("a/b/c", "a\\b", ".."); + try testRelativeWindows("a/b/c", "a", "..\\.."); + try testRelativeWindows("a/b/c", "a\\b\\c\\d", "d"); - try testRelativeWindows("\\\\FOO\\bar\\baz", "\\\\foo\\BAR\\BAZ", ""); - // Unicode-aware case-insensitive path comparison - try testRelativeWindows("\\\\кириллица\\ελληνικά\\português", "\\\\КИРИЛЛИЦА\\ΕΛΛΗΝΙΚΆ\\PORTUGUÊS", ""); - } else { - try testRelativePosix("/var/lib", "/var", ".."); - try testRelativePosix("/var/lib", "/bin", "../../bin"); - try testRelativePosix("/var/lib", "/var/lib", ""); - try testRelativePosix("/var/lib", "/var/apache", "../apache"); - try testRelativePosix("/var/", "/var/lib", "lib"); - try testRelativePosix("/", "/var/lib", "var/lib"); - try testRelativePosix("/foo/test", "/foo/test/bar/package.json", "bar/package.json"); - try testRelativePosix("/Users/a/web/b/test/mails", "/Users/a/web/b", "../.."); - try testRelativePosix("/foo/bar/baz-quux", "/foo/bar/baz", "../baz"); - try testRelativePosix("/foo/bar/baz", "/foo/bar/baz-quux", "../baz-quux"); - try testRelativePosix("/baz-quux", "/baz", "../baz"); - try testRelativePosix("/baz", "/baz-quux", "../baz-quux"); - } + try testRelativeWindows("\\\\FOO\\bar\\baz", "\\\\foo\\BAR\\BAZ", ""); + // Unicode-aware case-insensitive path comparison + try testRelativeWindows("\\\\кириллица\\ελληνικά\\português", "\\\\КИРИЛЛИЦА\\ΕΛΛΗΝΙΚΆ\\PORTUGUÊS", ""); + + try testRelativePosix("/var/lib", "/var", ".."); + try testRelativePosix("/var/lib", "/bin", "../../bin"); + try testRelativePosix("/var/lib", "/var/lib", ""); + try testRelativePosix("/var/lib", "/var/apache", "../apache"); + try testRelativePosix("/var/", "/var/lib", "lib"); + try testRelativePosix("/", "/var/lib", "var/lib"); + try testRelativePosix("/foo/test", "/foo/test/bar/package.json", "bar/package.json"); + try testRelativePosix("/Users/a/web/b/test/mails", "/Users/a/web/b", "../.."); + try testRelativePosix("/foo/bar/baz-quux", "/foo/bar/baz", "../baz"); + try testRelativePosix("/foo/bar/baz", "/foo/bar/baz-quux", "../baz-quux"); + try testRelativePosix("/baz-quux", "/baz", "../baz"); + try testRelativePosix("/baz", "/baz-quux", "../baz-quux"); } fn testRelativePosix(from: []const u8, to: []const u8, expected_output: []const u8) !void { - const result = try relativePosix(testing.allocator, from, to); + const result = try relativePosix(testing.allocator, ".", from, to); defer testing.allocator.free(result); try testing.expectEqualStrings(expected_output, result); } fn testRelativeWindows(from: []const u8, to: []const u8, expected_output: []const u8) !void { - const result = try relativeWindows(testing.allocator, from, to); + const result = try relativeWindows(testing.allocator, ".", null, from, to); defer testing.allocator.free(result); try testing.expectEqualStrings(expected_output, result); } @@ -2554,3 +2563,124 @@ pub const fmtAsUtf8Lossy = std.unicode.fmtUtf8; /// a lossy conversion if the path contains any unpaired surrogates. /// Unpaired surrogates are replaced by the replacement character (U+FFFD). pub const fmtWtf16LeAsUtf8Lossy = std.unicode.fmtUtf16Le; + +/// Similar to `RTL_PATH_TYPE`, but without the `UNKNOWN` path type. +pub const Win32PathType = enum { + /// `\\server\share\foo` + unc_absolute, + /// `C:\foo` + drive_absolute, + /// `C:foo` + drive_relative, + /// `\foo` + rooted, + /// `foo` + relative, + /// `\\.\foo`, `\\?\foo` + local_device, + /// `\\.`, `\\?` + root_local_device, +}; + +/// Get the path type of a Win32 namespace path. +/// Similar to `RtlDetermineDosPathNameType_U`. +/// If `T` is `u16`, then `path` should be encoded as WTF-16LE. +pub fn getWin32PathType(comptime T: type, path: []const T) Win32PathType { + if (path.len < 1) return .relative; + + const windows_path = std.fs.path.PathType.windows; + if (windows_path.isSep(T, path[0])) { + // \x + if (path.len < 2 or !windows_path.isSep(T, path[1])) return .rooted; + // \\. or \\? + if (path.len > 2 and (path[2] == mem.nativeToLittle(T, '.') or path[2] == mem.nativeToLittle(T, '?'))) { + // exactly \\. or \\? with nothing trailing + if (path.len == 3) return .root_local_device; + // \\.\x or \\?\x + if (windows_path.isSep(T, path[3])) return .local_device; + } + // \\x + return .unc_absolute; + } else { + // Some choice has to be made about how non-ASCII code points as drive-letters are handled, since + // path[0] is a different size for WTF-16 vs WTF-8, leading to a potential mismatch in classification + // for a WTF-8 path and its WTF-16 equivalent. For example, `€:\` encoded in WTF-16 is three code + // units `<0x20AC>:\` whereas `€:\` encoded as WTF-8 is 6 code units `<0xE2><0x82><0xAC>:\` so + // checking path[0], path[1] and path[2] would not behave the same between WTF-8/WTF-16. + // + // `RtlDetermineDosPathNameType_U` exclusively deals with WTF-16 and considers + // `€:\` a drive-absolute path, but code points that take two WTF-16 code units to encode get + // classified as a relative path (e.g. with U+20000 as the drive-letter that'd be encoded + // in WTF-16 as `<0xD840><0xDC00>:\` and be considered a relative path). + // + // The choice made here is to emulate the behavior of `RtlDetermineDosPathNameType_U` for both + // WTF-16 and WTF-8. This is because, while unlikely and not supported by the Disk Manager GUI, + // drive letters are not actually restricted to A-Z. Using `SetVolumeMountPointW` will allow you + // to set any byte value as a drive letter, and going through `IOCTL_MOUNTMGR_CREATE_POINT` will + // allow you to set any WTF-16 code unit as a drive letter. + // + // Non-A-Z drive letters don't interact well with most of Windows, but certain things do work, e.g. + // `cd /D €:\` will work, filesystem functions still work, etc. + // + // The unfortunate part of this is that this makes handling WTF-8 more complicated as we can't + // just check path[0], path[1], path[2]. + const colon_i: usize = switch (T) { + u8 => i: { + const code_point_len = std.unicode.utf8ByteSequenceLength(path[0]) catch return .relative; + // Conveniently, 4-byte sequences in WTF-8 have the same starting code point + // as 2-code-unit sequences in WTF-16. + if (code_point_len > 3) return .relative; + break :i code_point_len; + }, + u16 => 1, + else => @compileError("unsupported type: " ++ @typeName(T)), + }; + // x + if (path.len < colon_i + 1 or path[colon_i] != mem.nativeToLittle(T, ':')) return .relative; + // x:\ + if (path.len > colon_i + 1 and windows_path.isSep(T, path[colon_i + 1])) return .drive_absolute; + // x: + return .drive_relative; + } +} + +test getWin32PathType { + try std.testing.expectEqual(.relative, getWin32PathType(u8, "")); + try std.testing.expectEqual(.relative, getWin32PathType(u8, "x")); + try std.testing.expectEqual(.relative, getWin32PathType(u8, "x\\")); + + try std.testing.expectEqual(.root_local_device, getWin32PathType(u8, "//.")); + try std.testing.expectEqual(.root_local_device, getWin32PathType(u8, "/\\?")); + try std.testing.expectEqual(.root_local_device, getWin32PathType(u8, "\\\\?")); + + try std.testing.expectEqual(.local_device, getWin32PathType(u8, "//./x")); + try std.testing.expectEqual(.local_device, getWin32PathType(u8, "/\\?\\x")); + try std.testing.expectEqual(.local_device, getWin32PathType(u8, "\\\\?\\x")); + // local device paths require a path separator after the root, otherwise it is considered a UNC path + try std.testing.expectEqual(.unc_absolute, getWin32PathType(u8, "\\\\?x")); + try std.testing.expectEqual(.unc_absolute, getWin32PathType(u8, "//.x")); + + try std.testing.expectEqual(.unc_absolute, getWin32PathType(u8, "//")); + try std.testing.expectEqual(.unc_absolute, getWin32PathType(u8, "\\\\x")); + try std.testing.expectEqual(.unc_absolute, getWin32PathType(u8, "//x")); + + try std.testing.expectEqual(.rooted, getWin32PathType(u8, "\\x")); + try std.testing.expectEqual(.rooted, getWin32PathType(u8, "/")); + + try std.testing.expectEqual(.drive_relative, getWin32PathType(u8, "x:")); + try std.testing.expectEqual(.drive_relative, getWin32PathType(u8, "x:abc")); + try std.testing.expectEqual(.drive_relative, getWin32PathType(u8, "x:a/b/c")); + + try std.testing.expectEqual(.drive_absolute, getWin32PathType(u8, "x:\\")); + try std.testing.expectEqual(.drive_absolute, getWin32PathType(u8, "x:\\abc")); + try std.testing.expectEqual(.drive_absolute, getWin32PathType(u8, "x:/a/b/c")); + + // Non-ASCII code point that is encoded as one WTF-16 code unit is considered a valid drive letter + try std.testing.expectEqual(.drive_absolute, getWin32PathType(u8, "€:\\")); + try std.testing.expectEqual(.drive_absolute, getWin32PathType(u16, std.unicode.wtf8ToWtf16LeStringLiteral("€:\\"))); + try std.testing.expectEqual(.drive_relative, getWin32PathType(u8, "€:")); + try std.testing.expectEqual(.drive_relative, getWin32PathType(u16, std.unicode.wtf8ToWtf16LeStringLiteral("€:"))); + // But code points that are encoded as two WTF-16 code units are not + try std.testing.expectEqual(.relative, getWin32PathType(u8, "\u{10000}:\\")); + try std.testing.expectEqual(.relative, getWin32PathType(u16, std.unicode.wtf8ToWtf16LeStringLiteral("\u{10000}:\\"))); +} diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index b47533a3ec9b24738d421146e79ecb238a91dfd7..f5bc595fde021ea8d9a4b09221cb96f0a22b8a8d 100644 --- a/lib/std/os/windows.zig +++ b/lib/std/os/windows.zig @@ -2927,7 +2927,7 @@ pub fn CreateSymbolicLink( // Already an NT path, no need to do anything to it break :target_path target_path; } else { - switch (getWin32PathType(u16, target_path)) { + switch (std.fs.path.getWin32PathType(u16, target_path)) { // Rooted paths need to avoid getting put through wToPrefixedFileW // (and they are treated as relative in this context) // Note: It seems that rooted paths in symbolic links are relative to @@ -4235,7 +4235,7 @@ pub const RemoveDotDirsError = error{TooManyParentDirs}; /// 2) all repeating back slashes have been collapsed /// 3) the path is a relative one (does not start with a back slash) pub fn removeDotDirsSanitized(comptime T: type, path: []T) RemoveDotDirsError!usize { - std.debug.assert(path.len == 0 or path[0] != '\\'); + assert(path.len == 0 or path[0] != '\\'); var write_idx: usize = 0; var read_idx: usize = 0; @@ -4251,7 +4251,7 @@ pub fn removeDotDirsSanitized(comptime T: type, path: []T) RemoveDotDirsError!us } if (after_dot == '.' and (read_idx + 2 == path.len or path[read_idx + 2] == '\\')) { if (write_idx == 0) return error.TooManyParentDirs; - std.debug.assert(write_idx >= 2); + assert(write_idx >= 2); write_idx -= 1; while (true) { write_idx -= 1; @@ -4353,7 +4353,7 @@ pub fn wToPrefixedFileW(dir: ?HANDLE, path: [:0]const u16) Wtf16ToPrefixedFileWE path_space.data[path_space.len] = 0; return path_space; } else { - const path_type = getWin32PathType(u16, path); + const path_type = std.fs.path.getWin32PathType(u16, path); var path_space: PathSpace = undefined; if (path_type == .local_device) { switch (getLocalDevicePathType(u16, path)) { @@ -4491,8 +4491,8 @@ pub fn wToPrefixedFileW(dir: ?HANDLE, path: [:0]const u16) Wtf16ToPrefixedFileWE if (path_type == .unc_absolute) { // Now add in the UNC, the `C` should overwrite the first `\` of the // FullPathName, ultimately resulting in `\??\UNC\` - std.debug.assert(path_space.data[path_buf_offset] == '\\'); - std.debug.assert(path_space.data[path_buf_offset + 1] == '\\'); + assert(path_space.data[path_buf_offset] == '\\'); + assert(path_space.data[path_buf_offset + 1] == '\\'); const unc = [_]u16{ 'U', 'N', 'C' }; path_space.data[nt_prefix.len..][0..unc.len].* = unc; } @@ -4500,127 +4500,6 @@ pub fn wToPrefixedFileW(dir: ?HANDLE, path: [:0]const u16) Wtf16ToPrefixedFileWE } } -/// Similar to `RTL_PATH_TYPE`, but without the `UNKNOWN` path type. -pub const Win32PathType = enum { - /// `\\server\share\foo` - unc_absolute, - /// `C:\foo` - drive_absolute, - /// `C:foo` - drive_relative, - /// `\foo` - rooted, - /// `foo` - relative, - /// `\\.\foo`, `\\?\foo` - local_device, - /// `\\.`, `\\?` - root_local_device, -}; - -/// Get the path type of a Win32 namespace path. -/// Similar to `RtlDetermineDosPathNameType_U`. -/// If `T` is `u16`, then `path` should be encoded as WTF-16LE. -pub fn getWin32PathType(comptime T: type, path: []const T) Win32PathType { - if (path.len < 1) return .relative; - - const windows_path = std.fs.path.PathType.windows; - if (windows_path.isSep(T, path[0])) { - // \x - if (path.len < 2 or !windows_path.isSep(T, path[1])) return .rooted; - // \\. or \\? - if (path.len > 2 and (path[2] == mem.nativeToLittle(T, '.') or path[2] == mem.nativeToLittle(T, '?'))) { - // exactly \\. or \\? with nothing trailing - if (path.len == 3) return .root_local_device; - // \\.\x or \\?\x - if (windows_path.isSep(T, path[3])) return .local_device; - } - // \\x - return .unc_absolute; - } else { - // Some choice has to be made about how non-ASCII code points as drive-letters are handled, since - // path[0] is a different size for WTF-16 vs WTF-8, leading to a potential mismatch in classification - // for a WTF-8 path and its WTF-16 equivalent. For example, `€:\` encoded in WTF-16 is three code - // units `<0x20AC>:\` whereas `€:\` encoded as WTF-8 is 6 code units `<0xE2><0x82><0xAC>:\` so - // checking path[0], path[1] and path[2] would not behave the same between WTF-8/WTF-16. - // - // `RtlDetermineDosPathNameType_U` exclusively deals with WTF-16 and considers - // `€:\` a drive-absolute path, but code points that take two WTF-16 code units to encode get - // classified as a relative path (e.g. with U+20000 as the drive-letter that'd be encoded - // in WTF-16 as `<0xD840><0xDC00>:\` and be considered a relative path). - // - // The choice made here is to emulate the behavior of `RtlDetermineDosPathNameType_U` for both - // WTF-16 and WTF-8. This is because, while unlikely and not supported by the Disk Manager GUI, - // drive letters are not actually restricted to A-Z. Using `SetVolumeMountPointW` will allow you - // to set any byte value as a drive letter, and going through `IOCTL_MOUNTMGR_CREATE_POINT` will - // allow you to set any WTF-16 code unit as a drive letter. - // - // Non-A-Z drive letters don't interact well with most of Windows, but certain things do work, e.g. - // `cd /D €:\` will work, filesystem functions still work, etc. - // - // The unfortunate part of this is that this makes handling WTF-8 more complicated as we can't - // just check path[0], path[1], path[2]. - const colon_i: usize = switch (T) { - u8 => i: { - const code_point_len = std.unicode.utf8ByteSequenceLength(path[0]) catch return .relative; - // Conveniently, 4-byte sequences in WTF-8 have the same starting code point - // as 2-code-unit sequences in WTF-16. - if (code_point_len > 3) return .relative; - break :i code_point_len; - }, - u16 => 1, - else => @compileError("unsupported type: " ++ @typeName(T)), - }; - // x - if (path.len < colon_i + 1 or path[colon_i] != mem.nativeToLittle(T, ':')) return .relative; - // x:\ - if (path.len > colon_i + 1 and windows_path.isSep(T, path[colon_i + 1])) return .drive_absolute; - // x: - return .drive_relative; - } -} - -test getWin32PathType { - try std.testing.expectEqual(.relative, getWin32PathType(u8, "")); - try std.testing.expectEqual(.relative, getWin32PathType(u8, "x")); - try std.testing.expectEqual(.relative, getWin32PathType(u8, "x\\")); - - try std.testing.expectEqual(.root_local_device, getWin32PathType(u8, "//.")); - try std.testing.expectEqual(.root_local_device, getWin32PathType(u8, "/\\?")); - try std.testing.expectEqual(.root_local_device, getWin32PathType(u8, "\\\\?")); - - try std.testing.expectEqual(.local_device, getWin32PathType(u8, "//./x")); - try std.testing.expectEqual(.local_device, getWin32PathType(u8, "/\\?\\x")); - try std.testing.expectEqual(.local_device, getWin32PathType(u8, "\\\\?\\x")); - // local device paths require a path separator after the root, otherwise it is considered a UNC path - try std.testing.expectEqual(.unc_absolute, getWin32PathType(u8, "\\\\?x")); - try std.testing.expectEqual(.unc_absolute, getWin32PathType(u8, "//.x")); - - try std.testing.expectEqual(.unc_absolute, getWin32PathType(u8, "//")); - try std.testing.expectEqual(.unc_absolute, getWin32PathType(u8, "\\\\x")); - try std.testing.expectEqual(.unc_absolute, getWin32PathType(u8, "//x")); - - try std.testing.expectEqual(.rooted, getWin32PathType(u8, "\\x")); - try std.testing.expectEqual(.rooted, getWin32PathType(u8, "/")); - - try std.testing.expectEqual(.drive_relative, getWin32PathType(u8, "x:")); - try std.testing.expectEqual(.drive_relative, getWin32PathType(u8, "x:abc")); - try std.testing.expectEqual(.drive_relative, getWin32PathType(u8, "x:a/b/c")); - - try std.testing.expectEqual(.drive_absolute, getWin32PathType(u8, "x:\\")); - try std.testing.expectEqual(.drive_absolute, getWin32PathType(u8, "x:\\abc")); - try std.testing.expectEqual(.drive_absolute, getWin32PathType(u8, "x:/a/b/c")); - - // Non-ASCII code point that is encoded as one WTF-16 code unit is considered a valid drive letter - try std.testing.expectEqual(.drive_absolute, getWin32PathType(u8, "€:\\")); - try std.testing.expectEqual(.drive_absolute, getWin32PathType(u16, std.unicode.wtf8ToWtf16LeStringLiteral("€:\\"))); - try std.testing.expectEqual(.drive_relative, getWin32PathType(u8, "€:")); - try std.testing.expectEqual(.drive_relative, getWin32PathType(u16, std.unicode.wtf8ToWtf16LeStringLiteral("€:"))); - // But code points that are encoded as two WTF-16 code units are not - try std.testing.expectEqual(.relative, getWin32PathType(u8, "\u{10000}:\\")); - try std.testing.expectEqual(.relative, getWin32PathType(u16, std.unicode.wtf8ToWtf16LeStringLiteral("\u{10000}:\\"))); -} - /// Returns true if the path starts with `\??\`, which is indicative of an NT path /// but is not enough to fully distinguish between NT paths and Win32 paths, as /// `\??\` is not actually a distinct prefix but rather the path to a special virtual @@ -4663,7 +4542,7 @@ const LocalDevicePathType = enum { /// Asserts `path` is of type `Win32PathType.local_device`. fn getLocalDevicePathType(comptime T: type, path: []const T) LocalDevicePathType { if (std.debug.runtime_safety) { - std.debug.assert(getWin32PathType(T, path) == .local_device); + assert(std.fs.path.getWin32PathType(T, path) == .local_device); } const backslash = mem.nativeToLittle(T, '\\'); diff --git a/lib/std/process/Args.zig b/lib/std/process/Args.zig index 4cf9c438b2703745ce6ca3da81260c6b0c4b4c6d..e10764ceb520bcabe251fd3fbd44e18ea2de04e0 100644 --- a/lib/std/process/Args.zig +++ b/lib/std/process/Args.zig @@ -12,6 +12,7 @@ vector: Vector, pub const Vector = switch (native_os) { .windows => []const u16, // WTF-16 encoded + .freestanding, .other => void, else => []const [*:0]const u8, }; @@ -57,7 +58,7 @@ pub const Iterator = struct { /// Returned slice is pointing to the iterator's internal buffer. /// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/). /// On other platforms, the result is an opaque sequence of bytes with no particular encoding. - pub fn next(it: *Iterator) ?([:0]const u8) { + pub fn next(it: *Iterator) ?[:0]const u8 { return it.inner.next(); } diff --git a/lib/std/process/Child.zig b/lib/std/process/Child.zig index 176405cc8b5fc3d0a3efc90155d94ce7c1d0f01f..17e15f208da5fbb7155ada08fe2a9006127be258 100644 --- a/lib/std/process/Child.zig +++ b/lib/std/process/Child.zig @@ -21,7 +21,7 @@ pub const Id = switch (native_os) { /// On Windows this is the hProcess. /// On POSIX this is the pid. id: ?Id, -thread_handle: if (native_os == .windows) std.os.windows.HANDLE else void = {}, +thread_handle: if (native_os == .windows) std.os.windows.HANDLE else void, /// The writing end of the child process's standard input pipe. /// Usage requires `process.SpawnOptions.StdIo.pipe`. stdin: ?File, diff --git a/lib/std/process/Environ.zig b/lib/std/process/Environ.zig index 2556ae558eaa79bb659a511b8f949ad0c9a697ee..95446aae25520efe20850e34d9c6b8ee3f60c5f1 100644 --- a/lib/std/process/Environ.zig +++ b/lib/std/process/Environ.zig @@ -12,11 +12,6 @@ const posix = std.posix; const mem = std.mem; /// Unmodified, unprocessed data provided by the operating system. -/// -/// On Windows this might point to memory in the PEB. -/// -/// On WASI without libc, this is void because the environment has to be -/// queried and heap-allocated at runtime. block: Block, pub const empty: Environ = .{ @@ -26,12 +21,19 @@ pub const empty: Environ = .{ }, }; +/// On WASI without libc, this is `void` because the environment has to be +/// queried and heap-allocated at runtime. +/// +/// On Windows, the memory pointed at by the PEB changes when the environment +/// is modified, so a long-lived pointer cannot be used. Therefore, on this +/// operating system `void` is also used. pub const Block = switch (native_os) { - .windows => [*:0]const u16, + .windows => void, .wasi => switch (builtin.link_libc) { false => void, true => [:null]const ?[*:0]const u8, }, + .freestanding, .other => void, else => [:null]const ?[*:0]const u8, }; @@ -345,7 +347,7 @@ pub fn createMap(env: Environ, allocator: Allocator) CreateMapError!Map { errdefer result.deinit(); if (native_os == .windows) { - const ptr = env.block; + const ptr = std.os.windows.peb().ProcessParameters.Environment; var i: usize = 0; while (ptr[i] != 0) { diff --git a/lib/std/start.zig b/lib/std/start.zig index df4d6e9aff13335480d9cd01c921e99eca4a9f5e..6db64c26bd2da1b4356f9d5b247e7c4c56c054da 100644 --- a/lib/std/start.zig +++ b/lib/std/start.zig @@ -11,118 +11,63 @@ const native_os = builtin.os.tag; const start_sym_name = if (native_arch.isMIPS()) "__start" else "_start"; -// The self-hosted compiler is not fully capable of handling all of this start.zig file. -// Until then, we have simplified logic here for self-hosted. TODO remove this once -// self-hosted is capable enough to handle all of the real start.zig logic. -pub const simplified_logic = switch (builtin.zig_backend) { - .stage2_aarch64, - .stage2_arm, - .stage2_powerpc, - .stage2_sparc64, - .stage2_spirv, - .stage2_x86, - => true, - else => false, -}; - comptime { // No matter what, we import the root file, so that any export, test, comptime // decls there get run. _ = root; - if (simplified_logic) { - if (builtin.output_mode == .Exe) { - if ((builtin.link_libc or builtin.object_format == .c) and @hasDecl(root, "main")) { - if (!@typeInfo(@TypeOf(root.main)).@"fn".calling_convention.eql(.c)) { - @export(&main2, .{ .name = "main" }); - } - } else if (builtin.os.tag == .windows) { - if (!@hasDecl(root, "wWinMainCRTStartup") and !@hasDecl(root, "mainCRTStartup")) { - @export(&wWinMainCRTStartup2, .{ .name = "wWinMainCRTStartup" }); - } - } else if (builtin.os.tag == .opencl or builtin.os.tag == .vulkan) { - if (@hasDecl(root, "main")) - @export(&spirvMain2, .{ .name = "main" }); - } else { - if (!@hasDecl(root, "_start")) { - @export(&_start2, .{ .name = "_start" }); - } - } + if (builtin.output_mode == .Lib and builtin.link_mode == .dynamic) { + if (native_os == .windows and !@hasDecl(root, "_DllMainCRTStartup")) { + @export(&_DllMainCRTStartup, .{ .name = "_DllMainCRTStartup" }); } - } else { - if (builtin.output_mode == .Lib and builtin.link_mode == .dynamic) { - if (native_os == .windows and !@hasDecl(root, "_DllMainCRTStartup")) { - @export(&_DllMainCRTStartup, .{ .name = "_DllMainCRTStartup" }); + } else if (builtin.output_mode == .Exe or @hasDecl(root, "main")) { + if (builtin.link_libc and @hasDecl(root, "main")) { + if (native_arch.isWasm()) { + @export(&mainWithoutEnv, .{ .name = "__main_argc_argv" }); + } else if (!@typeInfo(@TypeOf(root.main)).@"fn".calling_convention.eql(.c)) { + @export(&main, .{ .name = "main" }); } - } else if (builtin.output_mode == .Exe or @hasDecl(root, "main")) { - if (builtin.link_libc and @hasDecl(root, "main")) { - if (native_arch.isWasm()) { - @export(&mainWithoutEnv, .{ .name = "__main_argc_argv" }); - } else if (!@typeInfo(@TypeOf(root.main)).@"fn".calling_convention.eql(.c)) { - @export(&main, .{ .name = "main" }); - } - } else if (native_os == .windows and builtin.link_libc and @hasDecl(root, "wWinMain")) { - if (!@typeInfo(@TypeOf(root.wWinMain)).@"fn".calling_convention.eql(.c)) { - @export(&wWinMain, .{ .name = "wWinMain" }); - } - } else if (native_os == .windows) { - if (!@hasDecl(root, "WinMain") and !@hasDecl(root, "WinMainCRTStartup") and - !@hasDecl(root, "wWinMain") and !@hasDecl(root, "wWinMainCRTStartup")) - { - @export(&WinStartup, .{ .name = "wWinMainCRTStartup" }); - } else if (@hasDecl(root, "WinMain") and !@hasDecl(root, "WinMainCRTStartup") and - !@hasDecl(root, "wWinMain") and !@hasDecl(root, "wWinMainCRTStartup")) - { - @compileError("WinMain not supported; declare wWinMain or main instead"); - } else if (@hasDecl(root, "wWinMain") and !@hasDecl(root, "wWinMainCRTStartup") and - !@hasDecl(root, "WinMain") and !@hasDecl(root, "WinMainCRTStartup")) - { - @export(&wWinMainCRTStartup, .{ .name = "wWinMainCRTStartup" }); - } - } else if (native_os == .uefi) { - if (!@hasDecl(root, "EfiMain")) @export(&EfiMain, .{ .name = "EfiMain" }); - } else if (native_os == .wasi) { - const wasm_start_sym = switch (builtin.wasi_exec_model) { - .reactor => "_initialize", - .command => "_start", - }; - if (!@hasDecl(root, wasm_start_sym) and @hasDecl(root, "main")) { - // Only call main when defined. For WebAssembly it's allowed to pass `-fno-entry` in which - // case it's not required to provide an entrypoint such as main. - @export(&wasi_start, .{ .name = wasm_start_sym }); - } - } else if (native_arch.isWasm() and native_os == .freestanding) { + } else if (native_os == .windows and builtin.link_libc and @hasDecl(root, "wWinMain")) { + if (!@typeInfo(@TypeOf(root.wWinMain)).@"fn".calling_convention.eql(.c)) { + @export(&wWinMain, .{ .name = "wWinMain" }); + } + } else if (native_os == .windows) { + if (!@hasDecl(root, "WinMain") and !@hasDecl(root, "WinMainCRTStartup") and + !@hasDecl(root, "wWinMain") and !@hasDecl(root, "wWinMainCRTStartup")) + { + @export(&WinStartup, .{ .name = "wWinMainCRTStartup" }); + } else if (@hasDecl(root, "WinMain") and !@hasDecl(root, "WinMainCRTStartup") and + !@hasDecl(root, "wWinMain") and !@hasDecl(root, "wWinMainCRTStartup")) + { + @compileError("WinMain not supported; declare wWinMain or main instead"); + } else if (@hasDecl(root, "wWinMain") and !@hasDecl(root, "wWinMainCRTStartup") and + !@hasDecl(root, "WinMain") and !@hasDecl(root, "WinMainCRTStartup")) + { + @export(&wWinMainCRTStartup, .{ .name = "wWinMainCRTStartup" }); + } + } else if (native_os == .uefi) { + if (!@hasDecl(root, "EfiMain")) @export(&EfiMain, .{ .name = "EfiMain" }); + } else if (native_os == .wasi) { + const wasm_start_sym = switch (builtin.wasi_exec_model) { + .reactor => "_initialize", + .command => "_start", + }; + if (!@hasDecl(root, wasm_start_sym) and @hasDecl(root, "main")) { // Only call main when defined. For WebAssembly it's allowed to pass `-fno-entry` in which // case it's not required to provide an entrypoint such as main. - if (!@hasDecl(root, start_sym_name) and @hasDecl(root, "main")) @export(&wasm_freestanding_start, .{ .name = start_sym_name }); - } else switch (native_os) { - .other, .freestanding, .@"3ds", .vita => {}, - else => if (!@hasDecl(root, start_sym_name)) @export(&_start, .{ .name = start_sym_name }), + @export(&wasi_start, .{ .name = wasm_start_sym }); } + } else if (native_arch.isWasm() and native_os == .freestanding) { + // Only call main when defined. For WebAssembly it's allowed to pass `-fno-entry` in which + // case it's not required to provide an entrypoint such as main. + if (!@hasDecl(root, start_sym_name) and @hasDecl(root, "main")) @export(&wasm_freestanding_start, .{ .name = start_sym_name }); + } else switch (native_os) { + .other, .freestanding, .@"3ds", .vita => {}, + else => if (!@hasDecl(root, start_sym_name)) @export(&_start, .{ .name = start_sym_name }), } } } -// Simplified start code for stage2 until it supports more language features /// - -fn main2() callconv(.c) c_int { - return callMain(); -} - -fn _start2() callconv(.withStackAlign(.c, 1)) noreturn { - std.process.exit(callMain()); -} - -fn spirvMain2() callconv(.kernel) void { - root.main(); -} - -fn wWinMainCRTStartup2() callconv(.c) noreturn { - std.process.exit(callMain()); -} - -//////////////////////////////////////////////////////////////////////////////// - fn _DllMainCRTStartup( hinstDLL: std.os.windows.HINSTANCE, fdwReason: std.os.windows.DWORD, @@ -142,15 +87,15 @@ fn _DllMainCRTStartup( fn wasm_freestanding_start() callconv(.c) void { // This is marked inline because for some reason LLVM in // release mode fails to inline it, and we want fewer call frames in stack traces. - _ = @call(.always_inline, callMain, .{}); + _ = @call(.always_inline, callMain, .{ {}, {} }); } fn wasi_start() callconv(.c) void { // The function call is marked inline because for some reason LLVM in // release mode fails to inline it, and we want fewer call frames in stack traces. switch (builtin.wasi_exec_model) { - .reactor => _ = @call(.always_inline, callMain, .{}), - .command => std.os.wasi.proc_exit(@call(.always_inline, callMain, .{})), + .reactor => _ = @call(.always_inline, callMain, .{ {}, {} }), + .command => std.os.wasi.proc_exit(@call(.always_inline, callMain, .{ {}, {} })), } } @@ -524,13 +469,10 @@ fn WinStartup() callconv(.withStackAlign(.c, 1)) noreturn { std.debug.maybeEnableSegfaultHandler(); - const peb = std.os.windows.peb(); const cmd_line = std.os.windows.peb().ProcessParameters.CommandLine; + const cmd_line_w = cmd_line.Buffer.?[0..@divExact(cmd_line.Length, 2)]; - std.os.windows.ntdll.RtlExitUserProcess(callMain( - cmd_line.Buffer.?[0..@divExact(cmd_line.Length, 2)], - peb.ProcessParameters.Environment, - )); + std.os.windows.ntdll.RtlExitUserProcess(callMain(cmd_line_w, {})); } fn wWinMainCRTStartup() callconv(.withStackAlign(.c, 1)) noreturn { @@ -637,6 +579,7 @@ fn posixCallMainAndExit(argc_argv_ptr: [*]usize) callconv(.c) noreturn { } fn expandStackSize(phdrs: []elf.Phdr) void { + @disableInstrumentation(); for (phdrs) |*phdr| { switch (phdr.p_type) { elf.PT_GNU_STACK => { @@ -674,7 +617,7 @@ fn expandStackSize(phdrs: []elf.Phdr) void { inline fn callMainWithArgs(argc: usize, argv: [*][*:0]u8, envp: [:null]?[*:0]u8) u8 { if (std.Options.debug_threaded_io) |t| { if (@sizeOf(std.Io.Threaded.Argv0) != 0) t.argv0.value = argv[0]; - t.environ = .{ .block = envp }; + t.environ = .{ .process_environ = .{ .block = envp } }; } std.debug.maybeEnableSegfaultHandler(); return callMain(argv[0..argc], envp); @@ -735,8 +678,8 @@ inline fn callMain(args: std.process.Args.Vector, environ: std.process.Environ.B defer arena_allocator.deinit(); var threaded: std.Io.Threaded = .init(gpa, .{ - .argv0 = if (@sizeOf(std.Io.Threaded.Argv0) != 0) .{ .value = args[0] } else .{}, - .environ = .{ .block = environ }, + .argv0 = .init(.{ .value = args }), + .environ = .{ .process_environ = .{ .block = environ } }, }); defer threaded.deinit(); -- 2.54.0 From 60447ea97cd86e38e566c23d40c123e19d50eb1a Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 1 Jan 2026 17:53:02 -0800 Subject: [PATCH 14/60] std: fix windows compilation errors --- lib/std/Io/Threaded.zig | 396 ++++++++++++++++++++------------ lib/std/fs/test.zig | 2 +- lib/std/os/windows.zig | 15 +- lib/std/os/windows/kernel32.zig | 2 +- lib/std/os/windows/test.zig | 6 +- lib/std/process/Args.zig | 21 +- lib/std/process/Environ.zig | 104 +++++---- 7 files changed, 333 insertions(+), 213 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 6843c02bb525e35f5f2865205eb3d81e13ed791a..9f888e1ea93fa63e7004167498a85427bb6db27d 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -5654,7 +5654,7 @@ fn dirSymLinkWindows( // Already an NT path, no need to do anything to it break :target_path target_path_w.span(); } else { - switch (w.getWin32PathType(u16, target_path_w.span())) { + switch (Dir.path.getWin32PathType(u16, target_path_w.span())) { // Rooted paths need to avoid getting put through wToPrefixedFileW // (and they are treated as relative in this context) // Note: It seems that rooted paths in symbolic links are relative to @@ -12617,6 +12617,45 @@ fn initializeWsa(t: *Threaded) error{ NetworkDown, Canceled }!void { fn doNothingSignalHandler(_: posix.SIG) callconv(.c) void {} +const WindowsEnvironStrings = struct { + PATH: ?[:0]const u16 = null, + PATHEXT: ?[:0]const u16 = null, + + fn scan() WindowsEnvironStrings { + const ptr = windows.peb().ProcessParameters.Environment; + + var result: WindowsEnvironStrings = .{}; + var i: usize = 0; + while (ptr[i] != 0) { + const key_start = i; + + // There are some special environment variables that start with =, + // so we need a special case to not treat = as a key/value separator + // if it's the first character. + // https://devblogs.microsoft.com/oldnewthing/20100506-00/?p=14133 + if (ptr[key_start] == '=') i += 1; + + while (ptr[i] != 0 and ptr[i] != '=') : (i += 1) {} + const key_w = ptr[key_start..i]; + + if (ptr[i] == '=') i += 1; + + const value_start = i; + while (ptr[i] != 0) : (i += 1) {} + const value_w = ptr[value_start..i :0]; + + i += 1; // skip over null byte + + inline for (@typeInfo(WindowsEnvironStrings).@"struct".fields) |field| { + const field_name_w = comptime std.unicode.wtf8ToWtf16LeStringLiteral(field.name); + if (std.mem.eql(u16, key_w, field_name_w)) @field(result, field.name) = value_w; + } + } + + return result; + } +}; + fn scanEnviron(t: *Threaded) void { t.mutex.lock(); defer t.mutex.unlock(); @@ -12625,6 +12664,9 @@ fn scanEnviron(t: *Threaded) void { t.environ.initialized = true; if (is_windows) { + // This value expires with any call that modifies the environment, + // which is outside of this Io implementation's control, so references + // must be short-lived. const ptr = windows.peb().ProcessParameters.Environment; var i: usize = 0; @@ -12779,6 +12821,7 @@ fn processSpawnPosix(userdata: ?*anyopaque, options: process.SpawnOptions) proce }; const any_ignore = (options.stdin == .ignore or options.stdout == .ignore or options.stderr == .ignore); + // TODO: cache file handle of /dev/null! const dev_null_fd = if (any_ignore) posix.openZ("/dev/null", .{ .ACCMODE = .RDWR }, 0) catch |err| switch (err) { error.PathAlreadyExists => unreachable, @@ -12962,55 +13005,88 @@ fn childWait(userdata: ?*anyopaque, child: *std.process.Child) process.Child.Wai fn childKill(userdata: ?*anyopaque, child: *std.process.Child) void { const t: *Threaded = @ptrCast(@alignCast(userdata)); if (is_windows) { - childKillWindows(t, child, 1) catch { - childCleanupStreams(child); - }; + childKillWindows(t, child, 1) catch childCleanupWindows(child); } else { - childKillPosix(t, child) catch { - childCleanupStreams(child); - }; + childKillPosix(t, child) catch childCleanupPosix(child); } } fn childKillWindows(t: *Threaded, child: *process.Child, exit_code: windows.UINT) !void { - windows.TerminateProcess(child.id, exit_code) catch |err| switch (err) { - error.AccessDenied => { - // Usually when TerminateProcess triggers a ACCESS_DENIED error, it - // indicates that the process has already exited, but there may be - // some rare edge cases where our process handle no longer has the - // PROCESS_TERMINATE access right, so let's do another check to make - // sure the process is really no longer running: - windows.WaitForSingleObjectEx(child.id, 0, false) catch return err; - return error.AlreadyTerminated; - }, - else => return err, - }; - try childWaitWindows(t, child); + _ = t; // TODO cancelation + const handle = child.id.?; + if (windows.kernel32.TerminateProcess(handle, exit_code) == 0) { + switch (windows.GetLastError()) { + .ACCESS_DENIED => { + // Usually when TerminateProcess triggers a ACCESS_DENIED error, it + // indicates that the process has already exited, but there may be + // some rare edge cases where our process handle no longer has the + // PROCESS_TERMINATE access right, so let's do another check to make + // sure the process is really no longer running: + windows.WaitForSingleObjectEx(handle, 0, false) catch return error.AccessDenied; + return error.AlreadyTerminated; + }, + else => |err| return windows.unexpectedError(err), + } + } + _ = windows.kernel32.WaitForSingleObjectEx(handle, windows.INFINITE, windows.FALSE); + childCleanupWindows(child); } fn childWaitWindows(t: *Threaded, child: *process.Child) process.Child.WaitError!process.Child.Term { - _ = t; // TODO cancelation - windows.WaitForSingleObjectEx(child.id, windows.INFINITE, false); + const current_thread = Thread.getCurrent(t); + const handle = child.id.?; + + while (true) { + try current_thread.checkCancel(); + switch (windows.kernel32.WaitForSingleObjectEx(handle, windows.INFINITE, windows.FALSE)) { + windows.WAIT_OBJECT_0 => break, + windows.WAIT_ABANDONED, windows.WAIT_TIMEOUT => continue, + windows.WAIT_FAILED => switch (windows.GetLastError()) { + else => |err| return windows.unexpectedError(err), + }, + else => return error.Unexpected, + } + } const term: process.Child.Term = x: { var exit_code: windows.DWORD = undefined; - if (windows.kernel32.GetExitCodeProcess(child.id, &exit_code) == 0) { + if (windows.kernel32.GetExitCodeProcess(handle, &exit_code) == 0) { break :x .{ .unknown = 0 }; } else { break :x .{ .exited = @as(u8, @truncate(exit_code)) }; } }; - if (child.request_resource_usage_statistics) { - child.resource_usage_statistics.rusage = try windows.GetProcessMemoryInfo(child.id); - } - - posix.close(child.id); - posix.close(child.thread_handle); - childCleanupStreams(child); + childCleanupWindows(child); return term; } +fn childCleanupWindows(child: *process.Child) void { + const handle = child.id orelse return; + + if (child.request_resource_usage_statistics) + child.resource_usage_statistics.rusage = windows.GetProcessMemoryInfo(handle) catch null; + + windows.CloseHandle(handle); + child.id = null; + + windows.CloseHandle(child.thread_handle); + child.thread_handle = undefined; + + if (child.stdin) |*stdin| { + windows.CloseHandle(stdin.handle); + child.stdin = null; + } + if (child.stdout) |*stdout| { + windows.CloseHandle(stdout.handle); + child.stdout = null; + } + if (child.stderr) |*stderr| { + windows.CloseHandle(stderr.handle); + child.stderr = null; + } +} + fn childWaitPosix(t: *Threaded, child: *process.Child) process.Child.WaitError!process.Child.Term { _ = t; // TODO cancelation const pid = child.id.?; @@ -13023,7 +13099,7 @@ fn childWaitPosix(t: *Threaded, child: *process.Child) process.Child.WaitError!p } break :res posix.waitpid(pid, 0); }; - childCleanupStreams(child); + childCleanupPosix(child); return statusToTerm(res.status); } @@ -13050,7 +13126,7 @@ fn childKillPosix(t: *Threaded, child: *process.Child) !void { _ = try childWaitPosix(t, child); } -fn childCleanupStreams(child: *process.Child) void { +fn childCleanupPosix(child: *process.Child) void { if (child.stdin) |*stdin| { posix.close(stdin.handle); child.stdin = null; @@ -13140,9 +13216,8 @@ fn setUpChildIo(stdio: process.SpawnOptions.StdIo, pipe_fd: i32, std_fileno: i32 } } -fn processSpawnWindows(userdata: ?*anyopaque, child: *process.Child) process.SpawnError!void { +fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) process.SpawnError!process.Child { const t: *Threaded = @ptrCast(@alignCast(userdata)); - _ = t; var saAttr: windows.SECURITY_ATTRIBUTES = .{ .nLength = @sizeOf(windows.SECURITY_ATTRIBUTES), @@ -13151,10 +13226,11 @@ fn processSpawnWindows(userdata: ?*anyopaque, child: *process.Child) process.Spa }; const any_ignore = - child.stdin_behavior == .ignore or - child.stdout_behavior == .ignore or - child.stderr_behavior == .ignore; + options.stdin == .ignore or + options.stdout == .ignore or + options.stderr == .ignore; + // TODO: cache the handle to null file! const nul_handle = if (any_ignore) // "\Device\Null" or "\??\NUL" windows.OpenFile(&[_]u16{ '\\', 'D', 'e', 'v', 'i', 'c', 'e', '\\', 'N', 'u', 'l', 'l' }, .{ @@ -13185,7 +13261,7 @@ fn processSpawnWindows(userdata: ?*anyopaque, child: *process.Child) process.Spa var g_hChildStd_IN_Rd: ?windows.HANDLE = null; var g_hChildStd_IN_Wr: ?windows.HANDLE = null; - switch (child.stdin_behavior) { + switch (options.stdin) { .pipe => { try windowsMakePipeIn(&g_hChildStd_IN_Rd, &g_hChildStd_IN_Wr, &saAttr); }, @@ -13198,14 +13274,15 @@ fn processSpawnWindows(userdata: ?*anyopaque, child: *process.Child) process.Spa .close => { g_hChildStd_IN_Rd = null; }, + .file => @panic("TODO implement passing file stdio in processSpawnWindows"), } - errdefer if (child.stdin_behavior == .pipe) { + errdefer if (options.stdin == .pipe) { windowsDestroyPipe(g_hChildStd_IN_Rd, g_hChildStd_IN_Wr); }; var g_hChildStd_OUT_Rd: ?windows.HANDLE = null; var g_hChildStd_OUT_Wr: ?windows.HANDLE = null; - switch (child.stdout_behavior) { + switch (options.stdout) { .pipe => { try windowsMakeAsyncPipe(&g_hChildStd_OUT_Rd, &g_hChildStd_OUT_Wr, &saAttr); }, @@ -13218,14 +13295,15 @@ fn processSpawnWindows(userdata: ?*anyopaque, child: *process.Child) process.Spa .close => { g_hChildStd_OUT_Wr = null; }, + .file => @panic("TODO implement passing file stdio in processSpawnWindows"), } - errdefer if (child.stdout_behavior == .pipe) { + errdefer if (options.stdout == .pipe) { windowsDestroyPipe(g_hChildStd_OUT_Rd, g_hChildStd_OUT_Wr); }; var g_hChildStd_ERR_Rd: ?windows.HANDLE = null; var g_hChildStd_ERR_Wr: ?windows.HANDLE = null; - switch (child.stderr_behavior) { + switch (options.stderr) { .pipe => { try windowsMakeAsyncPipe(&g_hChildStd_ERR_Rd, &g_hChildStd_ERR_Wr, &saAttr); }, @@ -13238,12 +13316,13 @@ fn processSpawnWindows(userdata: ?*anyopaque, child: *process.Child) process.Spa .close => { g_hChildStd_ERR_Wr = null; }, + .file => @panic("TODO implement passing file stdio in processSpawnWindows"), } - errdefer if (child.stderr_behavior == .pipe) { + errdefer if (options.stderr == .pipe) { windowsDestroyPipe(g_hChildStd_ERR_Rd, g_hChildStd_ERR_Wr); }; - var siStartInfo = windows.STARTUPINFOW{ + var siStartInfo: windows.STARTUPINFOW = .{ .cb = @sizeOf(windows.STARTUPINFOW), .hStdError = g_hChildStd_ERR_Wr, .hStdOutput = g_hChildStd_OUT_Wr, @@ -13266,63 +13345,63 @@ fn processSpawnWindows(userdata: ?*anyopaque, child: *process.Child) process.Spa }; var piProcInfo: windows.PROCESS_INFORMATION = undefined; - const cwd_w = if (child.cwd) |cwd| try std.unicode.wtf8ToWtf16LeAllocZ(child.allocator, cwd) else null; - defer if (cwd_w) |cwd| child.allocator.free(cwd); + var arena_allocator = std.heap.ArenaAllocator.init(t.allocator); + defer arena_allocator.deinit(); + const arena = arena_allocator.allocator(); + + const cwd_w = if (options.cwd) |cwd| try std.unicode.wtf8ToWtf16LeAllocZ(arena, cwd) else null; const cwd_w_ptr = if (cwd_w) |cwd| cwd.ptr else null; - const maybe_envp_buf = if (child.env_map) |env_map| try process.createWindowsEnvBlock(child.allocator, env_map) else null; - defer if (maybe_envp_buf) |envp_buf| child.allocator.free(envp_buf); + const maybe_envp_buf = if (options.env_map) |env_map| try env_map.createBlockWindows(arena) else null; const envp_ptr = if (maybe_envp_buf) |envp_buf| envp_buf.ptr else null; - const app_name_wtf8 = child.argv[0]; + const app_name_wtf8 = options.argv[0]; const app_name_is_absolute = Dir.path.isAbsolute(app_name_wtf8); - // the cwd set in Child is in effect when choosing the executable path - // to match posix semantics + // The cwd provided by options is in effect when choosing the executable + // path to match POSIX semantics. var cwd_path_w_needs_free = false; const cwd_path_w = x: { // If the app name is absolute, then we need to use its dirname as the cwd if (app_name_is_absolute) { cwd_path_w_needs_free = true; const dir = Dir.path.dirname(app_name_wtf8).?; - break :x try std.unicode.wtf8ToWtf16LeAllocZ(child.allocator, dir); - } else if (child.cwd) |cwd| { + break :x try std.unicode.wtf8ToWtf16LeAllocZ(arena, dir); + } else if (options.cwd) |cwd| { cwd_path_w_needs_free = true; - break :x try std.unicode.wtf8ToWtf16LeAllocZ(child.allocator, cwd); + break :x try std.unicode.wtf8ToWtf16LeAllocZ(arena, cwd); } else { break :x &[_:0]u16{}; // empty for cwd } }; - defer if (cwd_path_w_needs_free) child.allocator.free(cwd_path_w); - // If the app name has more than just a filename, then we need to separate that - // into the basename and dirname and use the dirname as an addition to the cwd - // path. This is because NtQueryDirectoryFile cannot accept FileName params with - // path separators. + // If the app name has more than just a filename, then we need to separate + // that into the basename and dirname and use the dirname as an addition to + // the cwd path. This is because NtQueryDirectoryFile cannot accept + // FileName params with path separators. const app_basename_wtf8 = Dir.path.basename(app_name_wtf8); // If the app name is absolute, then the cwd will already have the app's dirname in it, // so only populate app_dirname if app name is a relative path with > 0 path separators. const maybe_app_dirname_wtf8 = if (!app_name_is_absolute) Dir.path.dirname(app_name_wtf8) else null; const app_dirname_w: ?[:0]u16 = x: { if (maybe_app_dirname_wtf8) |app_dirname_wtf8| { - break :x try std.unicode.wtf8ToWtf16LeAllocZ(child.allocator, app_dirname_wtf8); + break :x try std.unicode.wtf8ToWtf16LeAllocZ(arena, app_dirname_wtf8); } break :x null; }; - defer if (app_dirname_w != null) child.allocator.free(app_dirname_w.?); - - const app_name_w = try std.unicode.wtf8ToWtf16LeAllocZ(child.allocator, app_basename_wtf8); - defer child.allocator.free(app_name_w); + const app_name_w = try std.unicode.wtf8ToWtf16LeAllocZ(arena, app_basename_wtf8); const flags: windows.CreateProcessFlags = .{ - .create_suspended = child.start_suspended, + .create_suspended = options.start_suspended, .create_unicode_environment = true, - .create_no_window = child.create_no_window, + .create_no_window = options.create_no_window, }; run: { - const PATH: [:0]const u16 = process.getenvW(std.unicode.utf8ToUtf16LeStringLiteral("PATH")) orelse &[_:0]u16{}; - const PATHEXT: [:0]const u16 = process.getenvW(std.unicode.utf8ToUtf16LeStringLiteral("PATHEXT")) orelse &[_:0]u16{}; + // We have to scan each time because the PEB environment pointer is not stable. + const env_strings: WindowsEnvironStrings = .scan(); + const PATH = env_strings.PATH orelse &[_:0]u16{}; + const PATHEXT = env_strings.PATHEXT orelse &[_:0]u16{}; // In case the command ends up being a .bat/.cmd script, we need to escape things using the cmd.exe rules // and invoke cmd.exe ourselves in order to mitigate arbitrary command execution from maliciously @@ -13331,26 +13410,34 @@ fn processSpawnWindows(userdata: ?*anyopaque, child: *process.Child) process.Spa // We'll need to wait until we're actually trying to run the command to know for sure // if the resolved command has the `.bat` or `.cmd` extension, so we defer actually // serializing the command line until we determine how it should be serialized. - var cmd_line_cache = WindowsCommandLineCache.init(child.allocator, child.argv); - defer cmd_line_cache.deinit(); + var cmd_line_cache = WindowsCommandLineCache.init(arena, options.argv); var app_buf: std.ArrayList(u16) = .empty; - defer app_buf.deinit(child.allocator); - - try app_buf.appendSlice(child.allocator, app_name_w); + try app_buf.appendSlice(arena, app_name_w); var dir_buf: std.ArrayList(u16) = .empty; - defer dir_buf.deinit(child.allocator); if (cwd_path_w.len > 0) { - try dir_buf.appendSlice(child.allocator, cwd_path_w); + try dir_buf.appendSlice(arena, cwd_path_w); } if (app_dirname_w) |app_dir| { - if (dir_buf.items.len > 0) try dir_buf.append(child.allocator, Dir.path.sep); - try dir_buf.appendSlice(child.allocator, app_dir); + if (dir_buf.items.len > 0) try dir_buf.append(arena, Dir.path.sep); + try dir_buf.appendSlice(arena, app_dir); } - windowsCreateProcessPathExt(child.allocator, io, &dir_buf, &app_buf, PATHEXT, &cmd_line_cache, envp_ptr, cwd_w_ptr, flags, &siStartInfo, &piProcInfo) catch |no_path_err| { + windowsCreateProcessPathExt( + t, + arena, + &dir_buf, + &app_buf, + PATHEXT, + &cmd_line_cache, + envp_ptr, + cwd_w_ptr, + flags, + &siStartInfo, + &piProcInfo, + ) catch |no_path_err| { const original_err = switch (no_path_err) { // argv[0] contains unsupported characters that will never resolve to a valid exe. error.InvalidArg0 => return error.FileNotFound, @@ -13362,7 +13449,7 @@ fn processSpawnWindows(userdata: ?*anyopaque, child: *process.Child) process.Spa // If the app name had path separators, that disallows PATH searching, // and there's no need to search the PATH if the app name is absolute. // We still search the path if the cwd is absolute because of the - // "cwd set in Child is in effect when choosing the executable path + // "cwd provided by options is in effect when choosing the executable path // to match posix semantics" behavior--we don't want to skip searching // the PATH just because we were trying to set the cwd of the child process. if (app_dirname_w != null or app_name_is_absolute) { @@ -13372,9 +13459,21 @@ fn processSpawnWindows(userdata: ?*anyopaque, child: *process.Child) process.Spa var it = std.mem.tokenizeScalar(u16, PATH, ';'); while (it.next()) |search_path| { dir_buf.clearRetainingCapacity(); - try dir_buf.appendSlice(child.allocator, search_path); + try dir_buf.appendSlice(arena, search_path); - if (windowsCreateProcessPathExt(child.allocator, io, &dir_buf, &app_buf, PATHEXT, &cmd_line_cache, envp_ptr, cwd_w_ptr, flags, &siStartInfo, &piProcInfo)) { + if (windowsCreateProcessPathExt( + t, + arena, + &dir_buf, + &app_buf, + PATHEXT, + &cmd_line_cache, + envp_ptr, + cwd_w_ptr, + flags, + &siStartInfo, + &piProcInfo, + )) { break :run; } else |err| switch (err) { // argv[0] contains unsupported characters that will never resolve to a valid exe. @@ -13389,35 +13488,18 @@ fn processSpawnWindows(userdata: ?*anyopaque, child: *process.Child) process.Spa }; } - if (g_hChildStd_IN_Wr) |h| { - child.stdin = File{ .handle = h }; - } else { - child.stdin = null; - } - if (g_hChildStd_OUT_Rd) |h| { - child.stdout = File{ .handle = h }; - } else { - child.stdout = null; - } - if (g_hChildStd_ERR_Rd) |h| { - child.stderr = File{ .handle = h }; - } else { - child.stderr = null; - } + if (options.stdin == .pipe) windows.CloseHandle(g_hChildStd_IN_Rd.?); + if (options.stderr == .pipe) windows.CloseHandle(g_hChildStd_ERR_Wr.?); + if (options.stdout == .pipe) windows.CloseHandle(g_hChildStd_OUT_Wr.?); - child.id = piProcInfo.hProcess; - child.thread_handle = piProcInfo.hThread; - child.term = null; - - if (child.stdin_behavior == .pipe) { - posix.close(g_hChildStd_IN_Rd.?); - } - if (child.stderr_behavior == .pipe) { - posix.close(g_hChildStd_ERR_Wr.?); - } - if (child.stdout_behavior == .pipe) { - posix.close(g_hChildStd_OUT_Wr.?); - } + return .{ + .id = piProcInfo.hProcess, + .thread_handle = piProcInfo.hThread, + .stdin = if (g_hChildStd_IN_Wr) |h| .{ .handle = h } else null, + .stdout = if (g_hChildStd_OUT_Rd) |h| .{ .handle = h } else null, + .stderr = if (g_hChildStd_ERR_Rd) |h| .{ .handle = h } else null, + .request_resource_usage_statistics = options.request_resource_usage_statistics, + }; } /// Expects `app_buf` to contain exactly the app name, and `dir_buf` to contain exactly the dir path. @@ -13425,12 +13507,12 @@ fn processSpawnWindows(userdata: ?*anyopaque, child: *process.Child) process.Spa /// Note: `app_buf` should not contain any leading path separators. /// Note: If the dir is the cwd, dir_buf should be empty (len = 0). fn windowsCreateProcessPathExt( - allocator: Allocator, + arena: Allocator, dir_buf: *std.ArrayList(u16), app_buf: *std.ArrayList(u16), pathext: [:0]const u16, cmd_line_cache: *WindowsCommandLineCache, - envp_ptr: ?[*]u16, + envp_ptr: ?[*:0]const u16, cwd_ptr: ?[*:0]u16, flags: windows.CreateProcessFlags, lpStartupInfo: *windows.STARTUPINFOW, @@ -13471,7 +13553,7 @@ fn windowsCreateProcessPathExt( // that scenario. var dir = dir: { // needs to be null-terminated - try dir_buf.append(allocator, 0); + try dir_buf.append(arena, 0); defer dir_buf.shrinkRetainingCapacity(dir_path_len); const dir_path_z = dir_buf.items[0 .. dir_buf.items.len - 1 :0]; const prefixed_path = try windows.wToPrefixedFileW(null, dir_path_z); @@ -13482,8 +13564,8 @@ fn windowsCreateProcessPathExt( defer windows.CloseHandle(dir.handle); // Add wildcard and null-terminator - try app_buf.append(allocator, '*'); - try app_buf.append(allocator, 0); + try app_buf.append(arena, '*'); + try app_buf.append(arena, 0); const app_name_wildcard = app_buf.items[0 .. app_buf.items.len - 1 :0]; // This 2048 is arbitrary, we just want it to be large enough to get multiple FILE_DIRECTORY_INFORMATION entries @@ -13563,10 +13645,10 @@ fn windowsCreateProcessPathExt( if (unappended_exists) { if (dir_path_len != 0) switch (dir_buf.items[dir_buf.items.len - 1]) { '/', '\\' => {}, - else => try dir_buf.append(allocator, Dir.path.sep), + else => try dir_buf.append(arena, Dir.path.sep), }; - try dir_buf.appendSlice(allocator, app_buf.items[0..app_name_len]); - try dir_buf.append(allocator, 0); + try dir_buf.appendSlice(arena, app_buf.items[0..app_name_len]); + try dir_buf.append(arena, 0); const full_app_name = dir_buf.items[0 .. dir_buf.items.len - 1 :0]; const is_bat_or_cmd = bat_or_cmd: { @@ -13588,7 +13670,15 @@ fn windowsCreateProcessPathExt( else full_app_name; - if (windowsCreateProcess(app_name_w.ptr, cmd_line_w.ptr, envp_ptr, cwd_ptr, flags, lpStartupInfo, lpProcessInformation)) |_| { + if (windowsCreateProcess( + app_name_w.ptr, + cmd_line_w.ptr, + envp_ptr, + cwd_ptr, + flags, + lpStartupInfo, + lpProcessInformation, + )) |_| { return; } else |err| switch (err) { error.FileNotFound, @@ -13623,11 +13713,11 @@ fn windowsCreateProcessPathExt( dir_buf.shrinkRetainingCapacity(dir_path_len); if (dir_path_len != 0) switch (dir_buf.items[dir_buf.items.len - 1]) { '/', '\\' => {}, - else => try dir_buf.append(allocator, Dir.path.sep), + else => try dir_buf.append(arena, Dir.path.sep), }; - try dir_buf.appendSlice(allocator, app_buf.items[0..app_name_len]); - try dir_buf.appendSlice(allocator, ext); - try dir_buf.append(allocator, 0); + try dir_buf.appendSlice(arena, app_buf.items[0..app_name_len]); + try dir_buf.appendSlice(arena, ext); + try dir_buf.append(arena, 0); const full_app_name = dir_buf.items[0 .. dir_buf.items.len - 1 :0]; const is_bat_or_cmd = switch (ext_enum) { @@ -13667,41 +13757,61 @@ fn windowsCreateProcessPathExt( fn windowsCreateProcess( app_name: [*:0]u16, cmd_line: [*:0]u16, - envp_ptr: ?[*]u16, + env_ptr: ?[*:0]const u16, cwd_ptr: ?[*:0]u16, flags: windows.CreateProcessFlags, lpStartupInfo: *windows.STARTUPINFOW, lpProcessInformation: *windows.PROCESS_INFORMATION, ) !void { - // TODO the docs for environment pointer say: - // > A pointer to the environment block for the new process. If this parameter - // > is NULL, the new process uses the environment of the calling process. - // > ... - // > An environment block can contain either Unicode or ANSI characters. If - // > the environment block pointed to by lpEnvironment contains Unicode - // > characters, be sure that dwCreationFlags includes CREATE_UNICODE_ENVIRONMENT. - // > If this parameter is NULL and the environment block of the parent process - // > contains Unicode characters, you must also ensure that dwCreationFlags - // > includes CREATE_UNICODE_ENVIRONMENT. - // This seems to imply that we have to somehow know whether our process parent passed - // CREATE_UNICODE_ENVIRONMENT if we want to pass NULL for the environment parameter. - // Since we do not know this information that would imply that we must not pass NULL - // for the parameter. - // However this would imply that programs compiled with -DUNICODE could not pass - // environment variables to programs that were not, which seems unlikely. - // More investigation is needed. - return windows.CreateProcessW( + if (windows.kernel32.CreateProcessW( app_name, cmd_line, null, null, windows.TRUE, flags, - @as(?*anyopaque, @ptrCast(envp_ptr)), + env_ptr, cwd_ptr, lpStartupInfo, lpProcessInformation, - ); + ) == 0) switch (windows.GetLastError()) { + .FILE_NOT_FOUND => return error.FileNotFound, + .PATH_NOT_FOUND => return error.FileNotFound, + .DIRECTORY => return error.FileNotFound, + .ACCESS_DENIED => return error.AccessDenied, + .INVALID_PARAMETER => unreachable, + .INVALID_NAME => return error.InvalidName, + .FILENAME_EXCED_RANGE => return error.NameTooLong, + .SHARING_VIOLATION => return error.FileBusy, + + // These are all the system errors that are mapped to ENOEXEC by + // the undocumented _dosmaperr (old CRT) or __acrt_errno_map_os_error + // (newer CRT) functions. Their code can be found in crt/src/dosmap.c (old SDK) + // or urt/misc/errno.cpp (newer SDK) in the Windows SDK. + .BAD_FORMAT, + .INVALID_STARTING_CODESEG, // MIN_EXEC_ERROR in errno.cpp + .INVALID_STACKSEG, + .INVALID_MODULETYPE, + .INVALID_EXE_SIGNATURE, + .EXE_MARKED_INVALID, + .BAD_EXE_FORMAT, + .ITERATED_DATA_EXCEEDS_64k, + .INVALID_MINALLOCSIZE, + .DYNLINK_FROM_INVALID_RING, + .IOPL_NOT_ENABLED, + .INVALID_SEGDPL, + .AUTODATASEG_EXCEEDS_64k, + .RING2SEG_MUST_BE_MOVABLE, + .RELOC_CHAIN_XEEDS_SEGLIM, + .INFLOOP_IN_RELOC_CHAIN, // MAX_EXEC_ERROR in errno.cpp + // This one is not mapped to ENOEXEC but it is possible, for example + // when calling CreateProcessW on a plain text file with a .exe extension + .EXE_MACHINE_TYPE_MISMATCH, + => return error.InvalidExe, + + .COMMITMENT_LIMIT => return error.SystemResources, + else => |err| return windows.unexpectedError(err), + }; } /// Case-insensitive WTF-16 lookup diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index 94cf3055e562c0cc077b19c0951578f5bdf05c66..7158eb734e1ce6f86f02c58cec52fb5007fea7b1 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -79,7 +79,7 @@ const PathType = enum { // using '127.0.0.1' as the server name and '$' as the share name. var fd_path_buf: [Dir.max_path_bytes]u8 = undefined; const dir_path = fd_path_buf[0..try dir.realPath(io, &fd_path_buf)]; - const windows_path_type = windows.getWin32PathType(u8, dir_path); + const windows_path_type = Dir.path.getWin32PathType(u8, dir_path); switch (windows_path_type) { .unc_absolute => return Dir.path.joinZ(allocator, &.{ dir_path, relative_path }), .drive_absolute => { diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index f5bc595fde021ea8d9a4b09221cb96f0a22b8a8d..c041293e2f8f7ad1d3233aa71f7b52067223e63d 100644 --- a/lib/std/os/windows.zig +++ b/lib/std/os/windows.zig @@ -3756,17 +3756,6 @@ pub fn GetModuleFileNameW(hModule: ?HMODULE, buf_ptr: [*]u16, buf_len: DWORD) Ge return buf_ptr[0..rc :0]; } -pub const TerminateProcessError = error{ AccessDenied, Unexpected }; - -pub fn TerminateProcess(hProcess: HANDLE, uExitCode: UINT) TerminateProcessError!void { - if (kernel32.TerminateProcess(hProcess, uExitCode) == 0) { - switch (GetLastError()) { - Win32Error.ACCESS_DENIED => return error.AccessDenied, - else => |err| return unexpectedError(err), - } - } -} - pub const NtAllocateVirtualMemoryError = error{ AccessDenied, InvalidParameter, @@ -3919,7 +3908,7 @@ pub fn CreateProcessW( lpThreadAttributes: ?*SECURITY_ATTRIBUTES, bInheritHandles: BOOL, dwCreationFlags: CreateProcessFlags, - lpEnvironment: ?*anyopaque, + lpEnvironment: ?[*:0]u16, lpCurrentDirectory: ?LPCWSTR, lpStartupInfo: *STARTUPINFOW, lpProcessInformation: *PROCESS_INFORMATION, @@ -4539,7 +4528,7 @@ const LocalDevicePathType = enum { }; /// Only relevant for Win32 -> NT path conversion. -/// Asserts `path` is of type `Win32PathType.local_device`. +/// Asserts `path` is of type `std.fs.path.Win32PathType.local_device`. fn getLocalDevicePathType(comptime T: type, path: []const T) LocalDevicePathType { if (std.debug.runtime_safety) { assert(std.fs.path.getWin32PathType(T, path) == .local_device); diff --git a/lib/std/os/windows/kernel32.zig b/lib/std/os/windows/kernel32.zig index 81cae8fac897aafb2ca9bdf97bd00f87088a97d7..1ef3d0e55ec6985da80d57881f0a48feeb2dd0c3 100644 --- a/lib/std/os/windows/kernel32.zig +++ b/lib/std/os/windows/kernel32.zig @@ -265,7 +265,7 @@ pub extern "kernel32" fn CreateProcessW( lpThreadAttributes: ?*SECURITY_ATTRIBUTES, bInheritHandles: BOOL, dwCreationFlags: windows.CreateProcessFlags, - lpEnvironment: ?LPVOID, + lpEnvironment: ?[*:0]const u16, lpCurrentDirectory: ?LPCWSTR, lpStartupInfo: *STARTUPINFOW, lpProcessInformation: *PROCESS_INFORMATION, diff --git a/lib/std/os/windows/test.zig b/lib/std/os/windows/test.zig index 0245583f5ba753d1885223e1c93ecc80f4982019..58eb4e0b97645bf59699d0b3939a4deb1bda0d73 100644 --- a/lib/std/os/windows/test.zig +++ b/lib/std/os/windows/test.zig @@ -274,8 +274,8 @@ test "getWin32PathType vs RtlDetermineDosPathNameType_U" { std.debug.assert(std.unicode.wtf16LeToWtf8(wtf8_buf.items, path) == wtf8_len); const windows_type = RtlDetermineDosPathNameType_U(path); - const wtf16_type = windows.getWin32PathType(u16, path); - const wtf8_type = windows.getWin32PathType(u8, wtf8_buf.items); + const wtf16_type = std.fs.path.getWin32PathType(u16, path); + const wtf8_type = std.fs.path.getWin32PathType(u8, wtf8_buf.items); checkPathType(windows_type, wtf16_type) catch |err| { std.debug.print("expected type {}, got {} for path: {f}\n", .{ windows_type, wtf16_type, std.unicode.fmtUtf16Le(path) }); @@ -295,7 +295,7 @@ test "getWin32PathType vs RtlDetermineDosPathNameType_U" { } } -fn checkPathType(windows_type: RTL_PATH_TYPE, zig_type: windows.Win32PathType) !void { +fn checkPathType(windows_type: RTL_PATH_TYPE, zig_type: std.fs.path.Win32PathType) !void { const expected_windows_type: RTL_PATH_TYPE = switch (zig_type) { .unc_absolute => .UncAbsolute, .drive_absolute => .DriveAbsolute, diff --git a/lib/std/process/Args.zig b/lib/std/process/Args.zig index e10764ceb520bcabe251fd3fbd44e18ea2de04e0..e5c106c8f42852bc6238ddc51075469bf75fcc55 100644 --- a/lib/std/process/Args.zig +++ b/lib/std/process/Args.zig @@ -29,13 +29,8 @@ pub const Iterator = struct { /// Initialize the args iterator. Consider using `initAllocator` instead /// for cross-platform compatibility. pub fn init(a: Args) Iterator { - if (native_os == .wasi) { - @compileError("In WASI, use initAllocator instead."); - } - if (native_os == .windows) { - @compileError("In Windows, use initAllocator instead."); - } - + if (native_os == .wasi) @compileError("In WASI, use initAllocator instead."); + if (native_os == .windows) @compileError("In Windows, use initAllocator instead."); return .{ .inner = .init(a) }; } @@ -44,10 +39,10 @@ pub const Iterator = struct { /// You must deinitialize iterator's internal buffers by calling `deinit` when done. pub fn initAllocator(a: Args, gpa: Allocator) InitError!Iterator { if (native_os == .wasi and !builtin.link_libc) { - return .{ .inner = try .init(a, gpa) }; + return .{ .inner = try .init(gpa) }; } if (native_os == .windows) { - return .{ .inner = try .init(a, gpa) }; + return .{ .inner = try .init(gpa, a.vector) }; } return .{ .inner = .init(a) }; @@ -111,7 +106,7 @@ pub const Iterator = struct { /// /// The iterator stores and uses `cmd_line_w`, so its memory must be valid for /// at least as long as the returned Windows. - pub fn init(allocator: Allocator, cmd_line_w: []const u16) Windows.InitError!Windows { + pub fn init(gpa: Allocator, cmd_line_w: []const u16) Windows.InitError!Windows { const wtf8_len = std.unicode.calcWtf8Len(cmd_line_w); // This buffer must be large enough to contain contiguous NUL-terminated slices @@ -121,11 +116,11 @@ pub const Iterator = struct { // - The first argument needs one extra byte of space allocated for its NUL // terminator, but for each subsequent argument the necessary whitespace // between arguments guarantees room for their NUL terminator(s). - const buffer = try allocator.alloc(u8, wtf8_len + 1); - errdefer allocator.free(buffer); + const buffer = try gpa.alloc(u8, wtf8_len + 1); + errdefer gpa.free(buffer); return .{ - .allocator = allocator, + .allocator = gpa, .cmd_line = cmd_line_w, .buffer = buffer, }; diff --git a/lib/std/process/Environ.zig b/lib/std/process/Environ.zig index 95446aae25520efe20850e34d9c6b8ee3f60c5f1..be568ade0177d4ccff762789abe1ee1e9d0902a8 100644 --- a/lib/std/process/Environ.zig +++ b/lib/std/process/Environ.zig @@ -15,7 +15,7 @@ const mem = std.mem; block: Block, pub const empty: Environ = .{ - .block = switch (@TypeOf(Block)) { + .block = switch (Block) { void => {}, else => &.{}, }, @@ -65,7 +65,7 @@ pub const Map = struct { @as(u8, @intCast((cp_upper >> 0) & 0xff)), }); } - return h.final(); + return @truncate(h.final()); } return std.array_hash_map.hashString(s); } @@ -293,8 +293,8 @@ pub const Map = struct { return envp_buf; } - /// Caller must free result. - pub fn createBlockWindows(map: *const Map, gpa: Allocator) Allocator.Error![]u16 { + /// Caller owns result. + pub fn createBlockWindows(map: *const Map, gpa: Allocator) error{ OutOfMemory, InvalidWtf8 }![:0]u16 { // count bytes needed const max_chars_needed = x: { // Only need 2 trailing NUL code units for an empty environment @@ -330,54 +330,27 @@ pub const Map = struct { result[i] = 0; i += 1; } - return try gpa.realloc(result, i); + const reallocated = try gpa.realloc(result, i); + return reallocated[0 .. i - 1 :0]; } }; pub const CreateMapError = error{ OutOfMemory, /// WASI-only. `environ_sizes_get` or `environ_get` failed for an - /// unexpected reason. + /// unanticipated, undocumented reason. Unexpected, }; /// Allocates a `Map` and copies environment block into it. pub fn createMap(env: Environ, allocator: Allocator) CreateMapError!Map { + if (native_os == .windows) + return createMapWide(std.os.windows.peb().ProcessParameters.Environment, allocator); + var result = Map.init(allocator); errdefer result.deinit(); - if (native_os == .windows) { - const ptr = std.os.windows.peb().ProcessParameters.Environment; - - var i: usize = 0; - while (ptr[i] != 0) { - const key_start = i; - - // There are some special environment variables that start with =, - // so we need a special case to not treat = as a key/value separator - // if it's the first character. - // https://devblogs.microsoft.com/oldnewthing/20100506-00/?p=14133 - if (ptr[key_start] == '=') i += 1; - - while (ptr[i] != 0 and ptr[i] != '=') : (i += 1) {} - const key_w = ptr[key_start..i]; - const key = try unicode.wtf16LeToWtf8Alloc(allocator, key_w); - errdefer allocator.free(key); - - if (ptr[i] == '=') i += 1; - - const value_start = i; - while (ptr[i] != 0) : (i += 1) {} - const value_w = ptr[value_start..i]; - const value = try unicode.wtf16LeToWtf8Alloc(allocator, value_w); - errdefer allocator.free(value); - - i += 1; // skip over null byte - - try result.putMove(key, value); - } - return result; - } else if (native_os == .wasi and !builtin.link_libc) { + if (native_os == .wasi and !builtin.link_libc) { var environ_count: usize = undefined; var environ_buf_size: usize = undefined; @@ -439,6 +412,40 @@ pub fn createMap(env: Environ, allocator: Allocator) CreateMapError!Map { } } +pub fn createMapWide(ptr: [*:0]u16, gpa: Allocator) CreateMapError!Map { + var result = Map.init(gpa); + errdefer result.deinit(); + + var i: usize = 0; + while (ptr[i] != 0) { + const key_start = i; + + // There are some special environment variables that start with =, + // so we need a special case to not treat = as a key/value separator + // if it's the first character. + // https://devblogs.microsoft.com/oldnewthing/20100506-00/?p=14133 + if (ptr[key_start] == '=') i += 1; + + while (ptr[i] != 0 and ptr[i] != '=') : (i += 1) {} + const key_w = ptr[key_start..i]; + const key = try unicode.wtf16LeToWtf8Alloc(gpa, key_w); + errdefer gpa.free(key); + + if (ptr[i] == '=') i += 1; + + const value_start = i; + while (ptr[i] != 0) : (i += 1) {} + const value_w = ptr[value_start..i]; + const value = try unicode.wtf16LeToWtf8Alloc(gpa, value_w); + errdefer gpa.free(value); + + i += 1; // skip over null byte + + try result.putMove(key, value); + } + return result; +} + pub const ContainsError = error{ OutOfMemory, /// On Windows, environment variable keys provided by the user must be @@ -777,7 +784,7 @@ test "convert from Environ to Map and back again" { const arena = arena_allocator.allocator(); const environ: Environ = switch (native_os) { - .windows => .{ .block = try map.createBlockWindows(arena) }, + .windows => return error.SkipZigTest, .wasi => if (!builtin.libc) return error.SkipZigTest, else => .{ .block = try map.createBlockPosix(arena, .{}) }, }; @@ -804,3 +811,22 @@ test "convert from Environ to Map and back again" { try testing.expectEqualDeep(map.keys(), map2.keys()); try testing.expectEqualDeep(map.values(), map2.values()); } + +test createMapWide { + const gpa = testing.allocator; + + var map: Map = .init(gpa); + defer map.deinit(); + try map.put("FOO", "BAR"); + try map.put("A", ""); + try map.put("", "B"); + + const environ: [:0]u16 = try map.createBlockWindows(gpa); + defer gpa.free(environ); + + var map2 = try createMapWide(environ, gpa); + defer map2.deinit(); + + try testing.expectEqualDeep(&[_][]const u8{ "FOO", "A", "=B" }, map2.keys()); + try testing.expectEqualDeep(&[_][]const u8{ "BAR", "", "" }, map2.values()); +} -- 2.54.0 From 0362d9f3215e36dd12b5ba47bca8a9cd107b1697 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 1 Jan 2026 18:00:12 -0800 Subject: [PATCH 15/60] start: make leaks into warnings I think it will be less confusing if memory leak checking doesn't change the return code of the process. --- lib/std/start.zig | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/std/start.zig b/lib/std/start.zig index 6db64c26bd2da1b4356f9d5b247e7c4c56c054da..c4d30cfa09fa1ea4cbc83951cb00fcdd8c2fc9f5 100644 --- a/lib/std/start.zig +++ b/lib/std/start.zig @@ -669,17 +669,16 @@ inline fn callMain(args: std.process.Args.Vector, environ: std.process.Environ.B else std.heap.smp_allocator; - defer if (use_debug_allocator) switch (debug_allocator.deinit()) { - .leak => std.process.exit(1), - .ok => {}, + defer if (use_debug_allocator) { + _ = debug_allocator.deinit(); // Leaks do not affect return code. }; var arena_allocator = std.heap.ArenaAllocator.init(std.heap.page_allocator); defer arena_allocator.deinit(); var threaded: std.Io.Threaded = .init(gpa, .{ - .argv0 = .init(.{ .value = args }), - .environ = .{ .process_environ = .{ .block = environ } }, + .argv0 = .init(.{ .vector = args }), + .environ = .{ .block = environ }, }); defer threaded.deinit(); -- 2.54.0 From 50e185b71822c180fccd07826a8dd5e3ec641cc1 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 1 Jan 2026 18:10:24 -0800 Subject: [PATCH 16/60] start: tweak allocator choice Favor DebugAllocator in Debug mode, even when linking libc. Prevent use of smp_allocator when single_threaded --- lib/std/process/Environ.zig | 14 -------------- lib/std/start.zig | 20 +++++++++++++------- 2 files changed, 13 insertions(+), 21 deletions(-) diff --git a/lib/std/process/Environ.zig b/lib/std/process/Environ.zig index be568ade0177d4ccff762789abe1ee1e9d0902a8..8254cf093a846cb4f301c773b8e13cf0c96bfef3 100644 --- a/lib/std/process/Environ.zig +++ b/lib/std/process/Environ.zig @@ -381,20 +381,6 @@ pub fn createMap(env: Environ, allocator: Allocator) CreateMapError!Map { try result.put(key, value); } return result; - } else if (builtin.link_libc) { - var ptr = env.block; - while (ptr[0]) |line| : (ptr += 1) { - var line_i: usize = 0; - while (line[line_i] != 0 and line[line_i] != '=') : (line_i += 1) {} - const key = line[0..line_i]; - - var end_i: usize = line_i; - while (line[end_i] != 0) : (end_i += 1) {} - const value = line[line_i + 1 .. end_i]; - - try result.put(key, value); - } - return result; } else { for (env.block) |opt_line| { const line = opt_line.?; diff --git a/lib/std/start.zig b/lib/std/start.zig index c4d30cfa09fa1ea4cbc83951cb00fcdd8c2fc9f5..e6b95cb51d45dc51417d8ecbff0be06ad929e071 100644 --- a/lib/std/start.zig +++ b/lib/std/start.zig @@ -623,7 +623,7 @@ inline fn callMainWithArgs(argc: usize, argv: [*][*:0]u8, envp: [:null]?[*:0]u8) return callMain(argv[0..argc], envp); } -fn main(c_argc: c_int, c_argv: [*][*:0]c_char, c_envp: [*:null]?[*:0]c_char) callconv(.c) c_int { +fn main(c_argc: c_int, c_argv: [*][*:0]u8, c_envp: [*:null]?[*:0]u8) callconv(.c) c_int { var env_count: usize = 0; while (c_envp[env_count] != null) : (env_count += 1) {} const envp = c_envp[0..env_count :null]; @@ -638,7 +638,7 @@ fn main(c_argc: c_int, c_argv: [*][*:0]c_char, c_envp: [*:null]?[*:0]c_char) cal return callMainWithArgs(@as(usize, @intCast(c_argc)), @as([*][*:0]u8, @ptrCast(c_argv)), envp); } -fn mainWithoutEnv(c_argc: c_int, c_argv: [*][*:0]c_char) callconv(.c) c_int { +fn mainWithoutEnv(c_argc: c_int, c_argv: [*][*:0]u8) callconv(.c) c_int { const argv = @as([*][*:0]u8, @ptrCast(c_argv))[0..@intCast(c_argc)]; if (@sizeOf(std.Io.Threaded.Argv0) != 0) { if (std.Options.debug_threaded_io) |t| t.argv0.value = argv[0]; @@ -649,7 +649,11 @@ fn mainWithoutEnv(c_argc: c_int, c_argv: [*][*:0]c_char) callconv(.c) c_int { /// General error message for a malformed return type const bad_main_ret = "expected return type of main to be 'void', '!void', 'noreturn', 'u8', or '!u8'"; -const use_debug_allocator = !builtin.link_libc and !native_arch.isWasm() and builtin.mode == .Debug; +const use_debug_allocator = !native_arch.isWasm() and switch (builtin.mode) { + .Debug => true, + .ReleaseSafe => !builtin.link_libc, // Not ideal, but the best we have for now. + .ReleaseFast, .ReleaseSmall => !builtin.link_libc and builtin.single_threaded, // Also not ideal. +}; var debug_allocator: std.heap.DebugAllocator(.{}) = .init; inline fn callMain(args: std.process.Args.Vector, environ: std.process.Environ.Block) u8 { @@ -660,14 +664,16 @@ inline fn callMain(args: std.process.Args.Vector, environ: std.process.Environ.B .environ = .{ .block = environ }, })); - const gpa = if (builtin.link_libc) - std.heap.c_allocator - else if (native_arch.isWasm()) + const gpa = if (native_arch.isWasm()) std.heap.wasm_allocator else if (use_debug_allocator) debug_allocator.allocator() + else if (builtin.link_libc) + std.heap.c_allocator + else if (!builtin.single_threaded) + std.heap.smp_allocator else - std.heap.smp_allocator; + comptime unreachable; defer if (use_debug_allocator) { _ = debug_allocator.deinit(); // Leaks do not affect return code. -- 2.54.0 From 1ccc87363a6436da293dbb1bc7bc8db2aa4d7bf7 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 1 Jan 2026 18:39:22 -0800 Subject: [PATCH 17/60] std: fixes for WASI --- lib/std/Io/Threaded.zig | 4 +++- lib/std/posix/test.zig | 4 ++-- lib/std/process/Args.zig | 22 +++++++++++++++------- lib/std/process/Environ.zig | 9 ++++----- lib/std/start.zig | 4 ++-- 5 files changed, 26 insertions(+), 17 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 9f888e1ea93fa63e7004167498a85427bb6db27d..965266aee915d7c9b14c1445ef5fb0a5fb2e77f5 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -12787,7 +12787,7 @@ const processSpawn = switch (native_os) { fn processSpawnUnsupported(userdata: ?*anyopaque, options: process.SpawnOptions) process.SpawnError!process.Child { _ = userdata; _ = options; - return error.OperationUnsupported; + return error.Unexpected; } fn processSpawnPosix(userdata: ?*anyopaque, options: process.SpawnOptions) process.SpawnError!process.Child { @@ -12995,6 +12995,7 @@ fn processSpawnPosix(userdata: ?*anyopaque, options: process.SpawnOptions) proce } fn childWait(userdata: ?*anyopaque, child: *std.process.Child) process.Child.WaitError!process.Child.Term { + if (native_os == .wasi) unreachable; const t: *Threaded = @ptrCast(@alignCast(userdata)); switch (native_os) { .windows => return childWaitWindows(t, child), @@ -13003,6 +13004,7 @@ fn childWait(userdata: ?*anyopaque, child: *std.process.Child) process.Child.Wai } fn childKill(userdata: ?*anyopaque, child: *std.process.Child) void { + if (native_os == .wasi) unreachable; const t: *Threaded = @ptrCast(@alignCast(userdata)); if (is_windows) { childKillWindows(t, child, 1) catch childCleanupWindows(child); diff --git a/lib/std/posix/test.zig b/lib/std/posix/test.zig index d95789c254115e6a0ed965bae7121307e0cf401f..6b56f0170959783713cfaa6865a6510ac2ec22b1 100644 --- a/lib/std/posix/test.zig +++ b/lib/std/posix/test.zig @@ -22,10 +22,10 @@ const tmpDir = std.testing.tmpDir; test "check WASI CWD" { if (native_os == .wasi) { - if (std.options.wasiCwd() != 3) { + const cwd: Dir = .cwd(); + if (cwd.handle != 3) { @panic("WASI code that uses cwd (like this test) needs a preopen for cwd (add '--dir=.' to wasmtime)"); } - if (!builtin.link_libc) { // WASI without-libc hardcodes fd 3 as the FDCWD token so it can be passed directly to WASI calls try expectEqual(3, posix.AT.FDCWD); diff --git a/lib/std/process/Args.zig b/lib/std/process/Args.zig index e5c106c8f42852bc6238ddc51075469bf75fcc55..7ecd1d2f9bde11599ebd65c0a20d897f00a0dec4 100644 --- a/lib/std/process/Args.zig +++ b/lib/std/process/Args.zig @@ -10,8 +10,14 @@ const testing = std.testing; vector: Vector, +/// On WASI without libc, this is `void` because the environment has to be +/// queried and heap-allocated at runtime. pub const Vector = switch (native_os) { .windows => []const u16, // WTF-16 encoded + .wasi => switch (builtin.link_libc) { + false => void, + true => []const [*:0]const u8, + }, .freestanding, .other => void, else => []const [*:0]const u8, }; @@ -457,6 +463,8 @@ pub fn iterateAllocator(a: Args, gpa: Allocator) Iterator.InitError!Iterator { return .initAllocator(a, gpa); } +pub const ToSliceError = Iterator.Windows.InitError || Iterator.Wasi.InitError; + /// Returned value may reference several allocations; call `freeSlice` to /// release. /// @@ -464,19 +472,19 @@ pub fn iterateAllocator(a: Args, gpa: Allocator) Iterator.InitError!Iterator { /// [WTF-8](https://wtf-8.codeberg.page/). /// * On other platforms, the result is an opaque sequence of bytes with no /// particular encoding. -pub fn toSlice(a: Args, gpa: Allocator) Allocator.Error![][:0]u8 { +pub fn toSlice(a: Args, gpa: Allocator) ToSliceError![][:0]u8 { var it = try a.iterateAllocator(gpa); defer it.deinit(); - var contents = std.array_list.Managed(u8).init(gpa); - defer contents.deinit(); + var contents: std.ArrayList(u8) = .empty; + defer contents.deinit(gpa); - var slice_list = std.array_list.Managed(usize).init(gpa); - defer slice_list.deinit(); + var slice_list: std.ArrayList(usize) = .empty; + defer slice_list.deinit(gpa); while (it.next()) |arg| { - try contents.appendSlice(arg[0 .. arg.len + 1]); - try slice_list.append(arg.len); + try contents.appendSlice(gpa, arg[0 .. arg.len + 1]); + try slice_list.append(gpa, arg.len); } const contents_slice = contents.items; diff --git a/lib/std/process/Environ.zig b/lib/std/process/Environ.zig index 8254cf093a846cb4f301c773b8e13cf0c96bfef3..39ca80ca8b21e3ba8337c8b9cc40a6da764ea1a5 100644 --- a/lib/std/process/Environ.zig +++ b/lib/std/process/Environ.zig @@ -757,6 +757,9 @@ test Map { } test "convert from Environ to Map and back again" { + if (native_os == .windows) return; + if (native_os == .wasi and !builtin.link_libc) return; + const gpa = testing.allocator; var map: Map = .init(gpa); @@ -769,11 +772,7 @@ test "convert from Environ to Map and back again" { defer arena_allocator.deinit(); const arena = arena_allocator.allocator(); - const environ: Environ = switch (native_os) { - .windows => return error.SkipZigTest, - .wasi => if (!builtin.libc) return error.SkipZigTest, - else => .{ .block = try map.createBlockPosix(arena, .{}) }, - }; + const environ: Environ = .{ .block = try map.createBlockPosix(arena, .{}) }; try testing.expectEqual(true, environ.contains(gpa, "FOO")); try testing.expectEqual(false, environ.contains(gpa, "BAR")); diff --git a/lib/std/start.zig b/lib/std/start.zig index e6b95cb51d45dc51417d8ecbff0be06ad929e071..8a33053634ef5f652c60e1a32bb452b128efb759 100644 --- a/lib/std/start.zig +++ b/lib/std/start.zig @@ -55,7 +55,7 @@ comptime { if (!@hasDecl(root, wasm_start_sym) and @hasDecl(root, "main")) { // Only call main when defined. For WebAssembly it's allowed to pass `-fno-entry` in which // case it's not required to provide an entrypoint such as main. - @export(&wasi_start, .{ .name = wasm_start_sym }); + @export(&startWasi, .{ .name = wasm_start_sym }); } } else if (native_arch.isWasm() and native_os == .freestanding) { // Only call main when defined. For WebAssembly it's allowed to pass `-fno-entry` in which @@ -90,7 +90,7 @@ fn wasm_freestanding_start() callconv(.c) void { _ = @call(.always_inline, callMain, .{ {}, {} }); } -fn wasi_start() callconv(.c) void { +fn startWasi() callconv(.c) void { // The function call is marked inline because for some reason LLVM in // release mode fails to inline it, and we want fewer call frames in stack traces. switch (builtin.wasi_exec_model) { -- 2.54.0 From a6f519c20f105a60c2ac51530354f9ac7c3e1fd9 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 1 Jan 2026 18:57:13 -0800 Subject: [PATCH 18/60] std.process.Args: make toSlice require arena allocation this simplifies the implementation, allows specializing it, and allows deleting the corresponding free function. In practice this is how it is always used anyway. --- lib/std/process/Args.zig | 106 ++++++++++++++++++++++----------------- 1 file changed, 60 insertions(+), 46 deletions(-) diff --git a/lib/std/process/Args.zig b/lib/std/process/Args.zig index 7ecd1d2f9bde11599ebd65c0a20d897f00a0dec4..8e6caf2d651d407a2baaab7b276f0f187381f068 100644 --- a/lib/std/process/Args.zig +++ b/lib/std/process/Args.zig @@ -465,58 +465,72 @@ pub fn iterateAllocator(a: Args, gpa: Allocator) Iterator.InitError!Iterator { pub const ToSliceError = Iterator.Windows.InitError || Iterator.Wasi.InitError; -/// Returned value may reference several allocations; call `freeSlice` to -/// release. +/// Returned value may reference several allocations and may point into `a`. +/// Thefore, an arena-style allocator must be used. /// /// * On Windows, the result is encoded as /// [WTF-8](https://wtf-8.codeberg.page/). /// * On other platforms, the result is an opaque sequence of bytes with no /// particular encoding. -pub fn toSlice(a: Args, gpa: Allocator) ToSliceError![][:0]u8 { - var it = try a.iterateAllocator(gpa); - defer it.deinit(); - - var contents: std.ArrayList(u8) = .empty; - defer contents.deinit(gpa); - - var slice_list: std.ArrayList(usize) = .empty; - defer slice_list.deinit(gpa); - - while (it.next()) |arg| { - try contents.appendSlice(gpa, arg[0 .. arg.len + 1]); - try slice_list.append(gpa, arg.len); - } - - const contents_slice = contents.items; - const slice_sizes = slice_list.items; - const slice_list_bytes = std.math.mul(usize, @sizeOf([]u8), slice_sizes.len) catch return error.OutOfMemory; - const total_bytes = std.math.add(usize, slice_list_bytes, contents_slice.len) catch return error.OutOfMemory; - const buf = try gpa.alignedAlloc(u8, .of([]u8), total_bytes); - errdefer gpa.free(buf); - - const result_slice_list = std.mem.bytesAsSlice([:0]u8, buf[0..slice_list_bytes]); - const result_contents = buf[slice_list_bytes..]; - @memcpy(result_contents[0..contents_slice.len], contents_slice); - - var contents_index: usize = 0; - for (slice_sizes, 0..) |len, i| { - const new_index = contents_index + len; - result_slice_list[i] = result_contents[contents_index..new_index :0]; - contents_index = new_index + 1; - } - - return result_slice_list; -} - -/// Frees memory allocate by `toSlice`. -pub fn freeSlice(gpa: Allocator, to_slice_result: []const [:0]u8) void { - var total_bytes: usize = 0; - for (to_slice_result) |arg| { - total_bytes += @sizeOf([]u8) + arg.len + 1; +/// +/// See also: +/// * `iterate` +/// * `iterateAllocator` +pub fn toSlice(a: Args, arena: Allocator) ToSliceError![]const [:0]const u8 { + if (native_os == .windows) { + var it = try a.iterateAllocator(arena); + var contents: std.ArrayList(u8) = .empty; + var slice_list: std.ArrayList(usize) = .empty; + while (it.next()) |arg| { + try contents.appendSlice(arena, arg[0 .. arg.len + 1]); + try slice_list.append(arena, arg.len); + } + const contents_slice = contents.items; + const slice_sizes = slice_list.items; + const slice_list_bytes = std.math.mul(usize, @sizeOf([]u8), slice_sizes.len) catch return error.OutOfMemory; + const total_bytes = std.math.add(usize, slice_list_bytes, contents_slice.len) catch return error.OutOfMemory; + const buf = try arena.alignedAlloc(u8, .of([]u8), total_bytes); + errdefer arena.free(buf); + + const result_slice_list = std.mem.bytesAsSlice([:0]u8, buf[0..slice_list_bytes]); + const result_contents = buf[slice_list_bytes..]; + @memcpy(result_contents[0..contents_slice.len], contents_slice); + + var contents_index: usize = 0; + for (slice_sizes, 0..) |len, i| { + const new_index = contents_index + len; + result_slice_list[i] = result_contents[contents_index..new_index :0]; + contents_index = new_index + 1; + } + + return result_slice_list; + } else if (native_os == .wasi and !builtin.link_libc) { + var count: usize = undefined; + var buf_size: usize = undefined; + + switch (std.os.wasi.args_sizes_get(&count, &buf_size)) { + .SUCCESS => {}, + else => |err| return std.posix.unexpectedErrno(err), + } + + if (count == 0) return &.{}; + + const argv = try arena.alloc([*:0]u8, count); + const argv_buf = try arena.alloc(u8, buf_size); + + switch (std.os.wasi.args_get(argv.ptr, argv_buf.ptr)) { + .SUCCESS => {}, + else => |err| return std.posix.unexpectedErrno(err), + } + + const args = try arena.alloc([:0]const u8, count); + for (args, argv) |*dst, src| dst.* = std.mem.sliceTo(src, 0); + return args; + } else { + const args = try arena.alloc([:0]const u8, a.vector.len); + for (args, a.vector) |*dst, src| dst.* = std.mem.sliceTo(src, 0); + return args; } - const unaligned_allocated_buf = @as([*]const u8, @ptrCast(to_slice_result.ptr))[0..total_bytes]; - const aligned_allocated_buf: []align(@alignOf([]u8)) const u8 = @alignCast(unaligned_allocated_buf); - return gpa.free(aligned_allocated_buf); } test "Iterator.Windows" { -- 2.54.0 From 960c512efd71ab4b658952fe8761453128cc8292 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 1 Jan 2026 19:40:18 -0800 Subject: [PATCH 19/60] compiler: update std lib API usage --- lib/compiler/std-docs.zig | 69 ++++++++++--------- lib/compiler/translate-c/main.zig | 17 ++--- lib/std/zig.zig | 6 ++ lib/std/zig/LibCInstallation.zig | 9 +-- lib/std/zig/WindowsSdk.zig | 64 ++++++++++------- src/Compilation.zig | 1 + src/Zcu/PerThread.zig | 5 +- src/introspect.zig | 15 ++-- src/libs/mingw.zig | 1 + test/standalone/child_process/main.zig | 11 +-- test/standalone/env_vars/main.zig | 11 +-- .../run_output_paths/create_file.zig | 6 +- .../self_exe_symlink/create-symlink.zig | 16 ++--- test/standalone/simple/cat/main.zig | 14 ++-- 14 files changed, 126 insertions(+), 119 deletions(-) diff --git a/lib/compiler/std-docs.zig b/lib/compiler/std-docs.zig index a9280b6fd91a5a55d4a3a216fe6188d5ee63daea..55f49c44e27d2aef6d0a7d513317454f1a3a44d2 100644 --- a/lib/compiler/std-docs.zig +++ b/lib/compiler/std-docs.zig @@ -21,19 +21,12 @@ fn usage(io: Io) noreturn { std.process.exit(1); } -pub fn main() !void { - var arena_instance = std.heap.ArenaAllocator.init(std.heap.page_allocator); - defer arena_instance.deinit(); - const arena = arena_instance.allocator(); +pub fn main(init: std.process.Init) !void { + const arena = init.arena.allocator(); + const gpa = init.gpa; + const io = init.io; - var general_purpose_allocator: std.heap.GeneralPurposeAllocator(.{}) = .init; - const gpa = general_purpose_allocator.allocator(); - - var threaded: Io.Threaded = .init(gpa, .{}); - defer threaded.deinit(); - const io = threaded.io(); - - var argv = try std.process.argsWithAllocator(arena); + var argv = try init.minimal.args.iterateAllocator(arena); defer argv.deinit(); assert(argv.skip()); const zig_lib_directory = argv.next().?; @@ -72,7 +65,7 @@ pub fn main() !void { const url_with_newline = try std.fmt.allocPrint(arena, "http://127.0.0.1:{d}/\n", .{port}); Io.File.stdout().writeStreamingAll(io, url_with_newline) catch {}; if (should_open_browser) { - openBrowserTab(gpa, io, url_with_newline[0 .. url_with_newline.len - 1 :'\n']) catch |err| { + openBrowserTab(io, url_with_newline[0 .. url_with_newline.len - 1 :'\n']) catch |err| { std.log.err("unable to open browser: {t}", .{err}); }; } @@ -324,11 +317,12 @@ fn buildWasmBinary( "--listen=-", // }); - var child = std.process.Child.init(argv.items, gpa); - child.stdin_behavior = .Pipe; - child.stdout_behavior = .Pipe; - child.stderr_behavior = .Pipe; - try child.spawn(io); + var child = try std.process.spawn(io, .{ + .argv = argv.items, + .stdin = .pipe, + .stdout = .pipe, + .stderr = .pipe, + }); var poller = Io.poll(gpa, enum { stdout, stderr }, .{ .stdout = child.stdout.?, @@ -388,19 +382,26 @@ fn buildWasmBinary( child.stdin = null; switch (try child.wait(io)) { - .Exited => |code| { + .exited => |code| { if (code != 0) { std.log.err( "the following command exited with error code {d}:\n{s}", - .{ code, try std.Build.Step.allocPrintCmd(arena, null, argv.items) }, + .{ code, try std.Build.Step.allocPrintCmd(arena, null, null, argv.items) }, ); return error.WasmCompilationFailed; } }, - .Signal, .Stopped, .Unknown => { + .signal => |sig| { + std.log.err( + "the following command terminated with signal {t}:\n{s}", + .{ sig, try std.Build.Step.allocPrintCmd(arena, null, null, argv.items) }, + ); + return error.WasmCompilationFailed; + }, + .stopped, .unknown => { std.log.err( "the following command terminated unexpectedly:\n{s}", - .{try std.Build.Step.allocPrintCmd(arena, null, argv.items)}, + .{try std.Build.Step.allocPrintCmd(arena, null, null, argv.items)}, ); return error.WasmCompilationFailed; }, @@ -410,14 +411,14 @@ fn buildWasmBinary( try result_error_bundle.renderToStderr(io, .{}, .auto); std.log.err("the following command failed with {d} compilation errors:\n{s}", .{ result_error_bundle.errorMessageCount(), - try std.Build.Step.allocPrintCmd(arena, null, argv.items), + try std.Build.Step.allocPrintCmd(arena, null, null, argv.items), }); return error.WasmCompilationFailed; } return result orelse { std.log.err("child process failed to report result\n{s}", .{ - try std.Build.Step.allocPrintCmd(arena, null, argv.items), + try std.Build.Step.allocPrintCmd(arena, null, null, argv.items), }); return error.WasmCompilationFailed; }; @@ -434,22 +435,24 @@ fn sendMessage(io: Io, file: Io.File, tag: std.zig.Client.Message.Tag) !void { }; } -fn openBrowserTab(gpa: Allocator, io: Io, url: []const u8) !void { +fn openBrowserTab(io: Io, url: []const u8) !void { // Until https://github.com/ziglang/zig/issues/19205 is implemented, we - // spawn a thread for this child process. - _ = try std.Thread.spawn(.{}, openBrowserTabThread, .{ gpa, io, url }); + // spawn and then leak a concurrent task for this child process. + const future = try io.concurrent(openBrowserTabTask, .{ io, url }); + _ = future; // leak it } -fn openBrowserTabThread(gpa: Allocator, io: Io, url: []const u8) !void { +fn openBrowserTabTask(io: Io, url: []const u8) !void { const main_exe = switch (builtin.os.tag) { .windows => "explorer", .macos => "open", else => "xdg-open", }; - var child = std.process.Child.init(&.{ main_exe, url }, gpa); - child.stdin_behavior = .ignore; - child.stdout_behavior = .ignore; - child.stderr_behavior = .ignore; - try child.spawn(io); + var child = try std.process.spawn(io, .{ + .argv = &.{ main_exe, url }, + .stdin = .ignore, + .stdout = .ignore, + .stderr = .ignore, + }); _ = try child.wait(io); } diff --git a/lib/compiler/translate-c/main.zig b/lib/compiler/translate-c/main.zig index ee50df422adbafc194b85930cb21d2b77485a7b6..825d13277662d060bb7504586de8a76a97c8773e 100644 --- a/lib/compiler/translate-c/main.zig +++ b/lib/compiler/translate-c/main.zig @@ -9,19 +9,10 @@ const Translator = @import("Translator.zig"); const fast_exit = @import("builtin").mode != .Debug; -var general_purpose_allocator: std.heap.GeneralPurposeAllocator(.{}) = .init; - -pub fn main() u8 { - const gpa = general_purpose_allocator.allocator(); - defer _ = general_purpose_allocator.deinit(); - - var arena_instance = std.heap.ArenaAllocator.init(gpa); - defer arena_instance.deinit(); - const arena = arena_instance.allocator(); - - var threaded: std.Io.Threaded = .init(gpa, .{}); - defer threaded.deinit(); - const io = threaded.io(); +pub fn main(init: std.process.Init) u8 { + const gpa = init.gpa; + const arena = init.arena.allocator(); + const io = init.io; const args = process.argsAlloc(arena) catch { std.debug.print("ran out of memory allocating arguments\n", .{}); diff --git a/lib/std/zig.zig b/lib/std/zig.zig index 5e7b0efdf1a1d0a73aa6995ac50be046d5ee0c18..0a24a242d721a8810f3944d61aff65be069f33f5 100644 --- a/lib/std/zig.zig +++ b/lib/std/zig.zig @@ -742,6 +742,7 @@ pub const EnvVar = enum { ZIG_IS_DETECTING_LIBC_PATHS, ZIG_IS_TRYING_TO_NOT_CALL_ITSELF, + // C toolchain integration NIX_CFLAGS_COMPILE, NIX_CFLAGS_LINK, NIX_LDFLAGS, @@ -750,13 +751,18 @@ pub const EnvVar = enum { LIBRARY_PATH, CC, + // Terminal integration NO_COLOR, CLICOLOR_FORCE, + // Debug info integration XDG_CACHE_HOME, LOCALAPPDATA, HOME, + // Windows SDK integration + PROGRAMDATA, + pub fn isSet(ev: EnvVar, map: *const std.process.Environ.Map) bool { return map.contains(@tagName(ev)); } diff --git a/lib/std/zig/LibCInstallation.zig b/lib/std/zig/LibCInstallation.zig index d709ae15110179039713a62b40e394af42b9ef01..80c41c594b280d9b3a91aab92807d73fad89bb6e 100644 --- a/lib/std/zig/LibCInstallation.zig +++ b/lib/std/zig/LibCInstallation.zig @@ -13,6 +13,7 @@ const fs = std.fs; const Allocator = std.mem.Allocator; const Path = std.Build.Cache.Path; const log = std.log.scoped(.libc_installation); +const Environ = std.process.Environ; include_dir: ?[]const u8 = null, sys_include_dir: ?[]const u8 = null, @@ -167,7 +168,7 @@ pub fn render(self: LibCInstallation, out: *std.Io.Writer) !void { pub const FindNativeOptions = struct { target: *const std.Target, - env_map: *const std.process.Environ.Map, + env_map: *const Environ.Map, /// If enabled, will print human-friendly errors to stderr. verbose: bool = false, @@ -192,7 +193,7 @@ pub fn findNative(gpa: Allocator, io: Io, args: FindNativeOptions) FindError!Lib }); return self; } else if (is_windows) { - const sdk = std.zig.WindowsSdk.find(gpa, io, args.target.cpu.arch) catch |err| switch (err) { + const sdk = std.zig.WindowsSdk.find(gpa, io, args.target.cpu.arch, args.env_map) catch |err| switch (err) { error.NotFound => return error.WindowsSdkNotFound, error.PathTooLong => return error.WindowsSdkNotFound, error.OutOfMemory => return error.OutOfMemory, @@ -552,7 +553,7 @@ fn findNativeMsvcLibDir( } pub const CCPrintFileNameOptions = struct { - env_map: *const std.process.Environ.Map, + env_map: *const Environ.Map, search_basename: []const u8, want_dirname: enum { full_path, only_dir }, verbose: bool = false, @@ -672,7 +673,7 @@ const inf_loop_env_key = "ZIG_IS_DETECTING_LIBC_PATHS"; fn appendCcExe( args: *std.array_list.Managed([]const u8), skip_cc_env_var: bool, - env_map: *const std.process.Environ.Map, + env_map: *const Environ.Map, ) !void { const default_cc_exe = if (is_windows) "cc.exe" else "cc"; try args.ensureUnusedCapacity(1); diff --git a/lib/std/zig/WindowsSdk.zig b/lib/std/zig/WindowsSdk.zig index d9c4f1b868520ab6ccddf341349dcdab66099934..cbffff4af76d6c09fac28188862f8b9a8a76ebf1 100644 --- a/lib/std/zig/WindowsSdk.zig +++ b/lib/std/zig/WindowsSdk.zig @@ -6,6 +6,7 @@ const Io = std.Io; const Dir = std.Io.Dir; const Writer = std.Io.Writer; const Allocator = std.mem.Allocator; +const Environ = std.process.Environ; windows10sdk: ?Installation, windows81sdk: ?Installation, @@ -24,7 +25,12 @@ const product_version_max_length = version_major_minor_max_length + ".65535".len /// Find path and version of Windows 10 SDK and Windows 8.1 SDK, and find path to MSVC's `lib/` directory. /// Caller owns the result's fields. /// Returns memory allocated by `gpa` -pub fn find(gpa: Allocator, io: Io, arch: std.Target.Cpu.Arch) error{ OutOfMemory, NotFound, PathTooLong }!WindowsSdk { +pub fn find( + gpa: Allocator, + io: Io, + arch: std.Target.Cpu.Arch, + env_map: *const Environ.Map, +) error{ OutOfMemory, NotFound, PathTooLong }!WindowsSdk { if (builtin.os.tag != .windows) return error.NotFound; //note(dimenus): If this key doesn't exist, neither the Win 8 SDK nor the Win 10 SDK is installed @@ -49,7 +55,7 @@ pub fn find(gpa: Allocator, io: Io, arch: std.Target.Cpu.Arch) error{ OutOfMemor }; errdefer if (windows81sdk) |*w| w.free(gpa); - const msvc_lib_dir: ?[]const u8 = MsvcLibDir.find(gpa, io, arch) catch |err| switch (err) { + const msvc_lib_dir: ?[]const u8 = MsvcLibDir.find(gpa, io, arch, env_map) catch |err| switch (err) { error.MsvcLibDirNotFound => null, error.OutOfMemory => return error.OutOfMemory, }; @@ -671,7 +677,11 @@ const MsvcLibDir = struct { return Dir.openDirAbsolute(io, instances_path, .{ .iterate = true }) catch return error.PathNotFound; } - fn findInstancesDir(gpa: Allocator, io: Io) error{ OutOfMemory, PathNotFound }!Dir { + fn findInstancesDir( + gpa: Allocator, + io: Io, + env_map: *const Environ.Map, + ) error{ OutOfMemory, PathNotFound }!Dir { // First, try getting the packages cache path from the registry. // This only seems to exist when the path is different from the default. method1: { @@ -691,16 +701,13 @@ const MsvcLibDir = struct { // If that can't be found, fall back to manually appending // `Microsoft\VisualStudio\Packages\_Instances` to %PROGRAMDATA% method3: { - const program_data = std.process.getEnvVarOwned(gpa, "PROGRAMDATA") catch |err| switch (err) { - error.OutOfMemory => |e| return e, - error.InvalidWtf8 => unreachable, - error.EnvironmentVariableNotFound => break :method3, - }; - defer gpa.free(program_data); + const program_data = std.zig.EnvVar.PROGRAMDATA.get(env_map) orelse break :method3; if (!Dir.path.isAbsolute(program_data)) break :method3; - const instances_path = try Dir.path.join(gpa, &.{ program_data, "Microsoft", "VisualStudio", "Packages", "_Instances" }); + const instances_path = try Dir.path.join(gpa, &.{ + program_data, "Microsoft", "VisualStudio", "Packages", "_Instances", + }); defer gpa.free(instances_path); return Dir.openDirAbsolute(io, instances_path, .{ .iterate = true }) catch break :method3; @@ -754,12 +761,17 @@ const MsvcLibDir = struct { /// /// The logic in this function is intended to match what ISetupConfiguration does /// under-the-hood, as verified using Procmon. - fn findViaCOM(gpa: Allocator, io: Io, arch: std.Target.Cpu.Arch) error{ OutOfMemory, PathNotFound }![]const u8 { + fn findViaCOM( + gpa: Allocator, + io: Io, + arch: std.Target.Cpu.Arch, + env_map: *const Environ.Map, + ) error{ OutOfMemory, PathNotFound }![]const u8 { // Typically `%PROGRAMDATA%\Microsoft\VisualStudio\Packages\_Instances` // This will contain directories with names of instance IDs like 80a758ca, // which will contain `state.json` files that have the version and // installation directory. - var instances_dir = try findInstancesDir(gpa, io); + var instances_dir = try findInstancesDir(gpa, io, env_map); defer instances_dir.close(io); var state_subpath_buf: [Dir.max_name_bytes + 32]u8 = undefined; @@ -856,15 +868,16 @@ const MsvcLibDir = struct { } // https://learn.microsoft.com/en-us/visualstudio/install/tools-for-managing-visual-studio-instances?view=vs-2022#editing-the-registry-for-a-visual-studio-instance - fn findViaRegistry(gpa: Allocator, io: Io, arch: std.Target.Cpu.Arch) error{ OutOfMemory, PathNotFound }![]const u8 { + fn findViaRegistry( + gpa: Allocator, + io: Io, + arch: std.Target.Cpu.Arch, + env_map: *const Environ.Map, + ) error{ OutOfMemory, PathNotFound }![]const u8 { // %localappdata%\Microsoft\VisualStudio\ // %appdata%\Local\Microsoft\VisualStudio\ - const local_app_data_path = (std.zig.EnvVar.LOCALAPPDATA.get(gpa) catch |err| switch (err) { - error.OutOfMemory => |e| return e, - error.InvalidWtf8 => return error.PathNotFound, - }) orelse return error.PathNotFound; - defer gpa.free(local_app_data_path); + const local_app_data_path = std.zig.EnvVar.LOCALAPPDATA.get(env_map) orelse return error.PathNotFound; const visualstudio_folder_path = try Dir.path.join(gpa, &.{ local_app_data_path, "Microsoft\\VisualStudio\\", }); @@ -955,7 +968,7 @@ const MsvcLibDir = struct { gpa: Allocator, io: Io, arch: std.Target.Cpu.Arch, - env_map: *const std.process.Environ.Map, + env_map: *const Environ.Map, ) error{ OutOfMemory, PathNotFound }![]const u8 { var base_path: std.array_list.Managed(u8) = base_path: { try_env: { @@ -1029,12 +1042,17 @@ const MsvcLibDir = struct { /// Find path to MSVC's `lib/` directory. /// Caller owns the result. - pub fn find(gpa: Allocator, io: Io, arch: std.Target.Cpu.Arch) error{ OutOfMemory, MsvcLibDirNotFound }![]const u8 { - const full_path = MsvcLibDir.findViaCOM(gpa, io, arch) catch |err1| switch (err1) { + pub fn find( + gpa: Allocator, + io: Io, + arch: std.Target.Cpu.Arch, + env_map: *const Environ.Map, + ) error{ OutOfMemory, MsvcLibDirNotFound }![]const u8 { + const full_path = MsvcLibDir.findViaCOM(gpa, io, arch, env_map) catch |err1| switch (err1) { error.OutOfMemory => return error.OutOfMemory, - error.PathNotFound => MsvcLibDir.findViaRegistry(gpa, io, arch) catch |err2| switch (err2) { + error.PathNotFound => MsvcLibDir.findViaRegistry(gpa, io, arch, env_map) catch |err2| switch (err2) { error.OutOfMemory => return error.OutOfMemory, - error.PathNotFound => MsvcLibDir.findViaVs7Key(gpa, io, arch) catch |err3| switch (err3) { + error.PathNotFound => MsvcLibDir.findViaVs7Key(gpa, io, arch, env_map) catch |err3| switch (err3) { error.OutOfMemory => return error.OutOfMemory, error.PathNotFound => return error.MsvcLibDirNotFound, }, diff --git a/src/Compilation.zig b/src/Compilation.zig index bf35be165c26a1ba4e9c0643dc8e5056e8b302c5..ac5df44316b3edc3d262ce93c3c91a227d2e4425 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -2128,6 +2128,7 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic, .manifest_dir = options.dirs.local_cache.handle.createDirPathOpen(io, "h", .{}) catch |err| { return diag.fail(.{ .create_cache_path = .{ .which = .local, .sub = "h", .err = err } }); }, + .cwd = options.dirs.cwd, }; // These correspond to std.zig.Server.Message.PathPrefix. cache.addPrefix(.{ .path = null, .handle = Io.Dir.cwd() }); diff --git a/src/Zcu/PerThread.zig b/src/Zcu/PerThread.zig index 103cbaaaaec0e1b14c0a281fa752ccbcfda26101..9825c1e87e7f16178a87ff1238b7dc8985fbeb7a 100644 --- a/src/Zcu/PerThread.zig +++ b/src/Zcu/PerThread.zig @@ -2571,10 +2571,7 @@ fn newEmbedFile( try whole.cache_manifest_mutex.lock(io); defer whole.cache_manifest_mutex.unlock(io); - man.addFilePostContents(path_str, contents, new_file.stat) catch |err| switch (err) { - error.Unexpected => unreachable, - else => |e| return e, - }; + try man.addFilePostContents(path_str, contents, new_file.stat); } return new_file; diff --git a/src/introspect.zig b/src/introspect.zig index d30b4ba067bf3bdd02b90519006120ff12873165..574bf9628e9909f0e8833f014b0577d808c15c0e 100644 --- a/src/introspect.zig +++ b/src/introspect.zig @@ -6,6 +6,7 @@ const Dir = std.Io.Dir; const mem = std.mem; const Allocator = std.mem.Allocator; const Cache = std.Build.Cache; +const assert = std.debug.assert; const build_options = @import("build_options"); @@ -62,14 +63,14 @@ pub fn getResolvedCwd(gpa: Allocator) error{ if (std.debug.runtime_safety) { const cwd = try std.process.getCwdAlloc(gpa); defer gpa.free(cwd); - std.debug.assert(mem.eql(u8, cwd, ".")); + assert(mem.eql(u8, cwd, ".")); } return ""; } const cwd = try std.process.getCwdAlloc(gpa); defer gpa.free(cwd); const resolved = try Dir.path.resolve(gpa, &.{cwd}); - std.debug.assert(Dir.path.isAbsolute(resolved)); + assert(Dir.path.isAbsolute(resolved)); return resolved; } @@ -140,7 +141,7 @@ pub fn resolvePath( paths: []const []const u8, ) Allocator.Error![]u8 { if (builtin.target.os.tag == .wasi) { - std.debug.assert(mem.eql(u8, cwd_resolved, "")); + assert(mem.eql(u8, cwd_resolved, "")); const res = try Dir.path.resolve(gpa, paths); if (mem.eql(u8, res, ".")) { gpa.free(res); @@ -160,8 +161,8 @@ pub fn resolvePath( gpa.free(res); return ""; } - std.debug.assert(!Dir.path.isAbsolute(res)); - std.debug.assert(!isUpDir(res)); + assert(!Dir.path.isAbsolute(res)); + assert(!isUpDir(res)); return res; } @@ -180,8 +181,8 @@ pub fn resolvePath( }; errdefer gpa.free(path_resolved); - std.debug.assert(Dir.path.isAbsolute(path_resolved)); - std.debug.assert(Dir.path.isAbsolute(cwd_resolved)); + assert(Dir.path.isAbsolute(path_resolved)); + assert(Dir.path.isAbsolute(cwd_resolved)); if (!std.mem.startsWith(u8, path_resolved, cwd_resolved)) return path_resolved; // not in cwd if (path_resolved.len == cwd_resolved.len) { diff --git a/src/libs/mingw.zig b/src/libs/mingw.zig index 03ed917c4fe7830d42fe59d5ce82f9b6118b39a6..752c62312f1fbed7fdd489fe077181a1a8d6be67 100644 --- a/src/libs/mingw.zig +++ b/src/libs/mingw.zig @@ -259,6 +259,7 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void { .gpa = gpa, .io = io, .manifest_dir = try comp.dirs.global_cache.handle.createDirPathOpen(io, "h", .{}), + .cwd = comp.dirs.cwd, }; cache.addPrefix(.{ .path = null, .handle = Io.Dir.cwd() }); cache.addPrefix(comp.dirs.zig_lib); diff --git a/test/standalone/child_process/main.zig b/test/standalone/child_process/main.zig index 0bd96061b44433ffd225c0d5cb5642a81b05242f..c2ae038f5ad5558ed40b241f4a2cc96dc054693e 100644 --- a/test/standalone/child_process/main.zig +++ b/test/standalone/child_process/main.zig @@ -1,15 +1,15 @@ const std = @import("std"); const Io = std.Io; -pub fn main() !void { +pub fn main(init: std.process.Init.Minimal) !void { // make sure safety checks are enabled even in release modes - var gpa_state = std.heap.GeneralPurposeAllocator(.{ .safety = true }){}; + var gpa_state: std.heap.GeneralPurposeAllocator(.{ .safety = true }) = .{}; defer if (gpa_state.deinit() != .ok) { @panic("found memory leaks"); }; const gpa = gpa_state.allocator(); - var it = try std.process.argsWithAllocator(gpa); + var it = try init.iterateAllocator(gpa); defer it.deinit(); _ = it.next() orelse unreachable; // skip binary name const child_path, const needs_free = child_path: { @@ -21,7 +21,10 @@ pub fn main() !void { }; defer if (needs_free) gpa.free(child_path); - var threaded: Io.Threaded = .init(gpa, .{}); + var threaded: Io.Threaded = .init(gpa, .{ + .argv0 = .init(init.args), + .environ = init.environ, + }); defer threaded.deinit(); const io = threaded.io(); diff --git a/test/standalone/env_vars/main.zig b/test/standalone/env_vars/main.zig index b85105642e1da6a5530246195658f04189e027a2..9069f93f42fb48d4fad5f229b385252fe121f563 100644 --- a/test/standalone/env_vars/main.zig +++ b/test/standalone/env_vars/main.zig @@ -2,16 +2,11 @@ const std = @import("std"); const builtin = @import("builtin"); // Note: the environment variables under test are set by the build.zig -pub fn main() !void { +pub fn main(init: std.process.Init) !void { @setEvalBranchQuota(10000); - var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init; - defer _ = gpa.deinit(); - const allocator = gpa.allocator(); - - var arena_state = std.heap.ArenaAllocator.init(allocator); - defer arena_state.deinit(); - const arena = arena_state.allocator(); + const allocator = init.gpa; + const arena = init.arena.allocator(); // hasNonEmptyEnvVar { diff --git a/test/standalone/run_output_paths/create_file.zig b/test/standalone/run_output_paths/create_file.zig index 7efa8e051a29d0f0e380fc092827859090fa4b5b..5281e6675a9269123fc6049e4ac3f023486c4e84 100644 --- a/test/standalone/run_output_paths/create_file.zig +++ b/test/standalone/run_output_paths/create_file.zig @@ -1,8 +1,8 @@ const std = @import("std"); -pub fn main() !void { - const io = std.Io.Threaded.global_single_threaded.ioBasic(); - var args = try std.process.argsWithAllocator(std.heap.page_allocator); +pub fn main(init: std.process.Init) !void { + const io = init.io; + var args = try init.args.iterateAllocator(init.arena.allocator()); _ = args.skip(); const dir_name = args.next().?; const dir = try std.Io.Dir.cwd().openDir(io, if (std.mem.startsWith(u8, dir_name, "--dir=")) diff --git a/test/standalone/self_exe_symlink/create-symlink.zig b/test/standalone/self_exe_symlink/create-symlink.zig index d725207320811408d453104f022b4c6409b67681..cf6a1c81dddcdaa856624d85374d22f0859795c6 100644 --- a/test/standalone/self_exe_symlink/create-symlink.zig +++ b/test/standalone/self_exe_symlink/create-symlink.zig @@ -1,21 +1,17 @@ const std = @import("std"); -pub fn main() anyerror!void { - var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init; - defer if (gpa.deinit() == .leak) @panic("found memory leaks"); - const allocator = gpa.allocator(); - - var it = try std.process.argsWithAllocator(allocator); +pub fn main(init: std.process.Init) !void { + const io = init.io; + const gpa = init.gpa; + var it = try init.args.iterateAllocator(gpa); defer it.deinit(); _ = it.next() orelse unreachable; // skip binary name const exe_path = it.next() orelse unreachable; const symlink_path = it.next() orelse unreachable; // If `exe_path` is relative to our cwd, we need to convert it to be relative to the dirname of `symlink_path`. - const exe_rel_path = try std.fs.path.relative(allocator, std.fs.path.dirname(symlink_path) orelse ".", exe_path); - defer allocator.free(exe_rel_path); - - const io = std.Io.Threaded.global_single_threaded.ioBasic(); + const exe_rel_path = try std.fs.path.relative(gpa, std.fs.path.dirname(symlink_path) orelse ".", exe_path); + defer gpa.free(exe_rel_path); try std.Io.Dir.cwd().symLink(io, exe_rel_path, symlink_path, .{}); } diff --git a/test/standalone/simple/cat/main.zig b/test/standalone/simple/cat/main.zig index 0135ac4b50943e5fc4c2fe136ea611fc20bc70cf..adab3aead843e135d06120cccec04b6f5e2b0bf7 100644 --- a/test/standalone/simple/cat/main.zig +++ b/test/standalone/simple/cat/main.zig @@ -4,16 +4,10 @@ const mem = std.mem; const warn = std.log.warn; const fatal = std.process.fatal; -pub fn main() !void { - var arena_instance = std.heap.ArenaAllocator.init(std.heap.page_allocator); - defer arena_instance.deinit(); - const arena = arena_instance.allocator(); - - var threaded: std.Io.Threaded = .init(arena, .{}); - defer threaded.deinit(); - const io = threaded.io(); - - const args = try std.process.argsAlloc(arena); +pub fn main(init: std.process.Init) !void { + const arena = init.arena.allocator(); + const io = init.io; + const args = try init.args.toSlice(arena); const exe = args[0]; var catted_anything = false; -- 2.54.0 From f28802a9c6c3bd36368101981243aab7cf4f453f Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 1 Jan 2026 20:20:58 -0800 Subject: [PATCH 20/60] zig libc: fix subcommand This branch regressed the child process "run" mechanism because it didn't pass the correct stdin, stdout, stderr values to process.spawn Fixed now. --- lib/compiler/aro/main.zig | 4 +- lib/compiler/libc.zig | 25 +++++---- lib/compiler/reduce.zig | 41 ++++++--------- lib/compiler/resinator/main.zig | 4 +- lib/compiler/translate-c/main.zig | 2 +- lib/std/Build/Step.zig | 4 +- lib/std/Random/benchmark.zig | 11 ++-- lib/std/crypto/benchmark.zig | 20 +++---- lib/std/hash/benchmark.zig | 11 ++-- lib/std/process.zig | 42 ++++++++++++++- lib/std/zig/LibCInstallation.zig | 52 +++++++++---------- lib/std/zig/system/darwin.zig | 6 +-- test/standalone/child_process/child.zig | 24 +++------ .../install_headers/check_exists.zig | 12 ++--- test/standalone/libfuzzer/main.zig | 13 ++--- test/standalone/run_output_caching/main.zig | 6 +-- test/standalone/windows_bat_args/fuzz.zig | 13 ++--- test/standalone/windows_bat_args/test.zig | 12 ++--- test/standalone/windows_spawn/main.zig | 13 ++--- tools/docgen.zig | 16 ++---- tools/doctest.zig | 16 ++---- tools/incr-check.zig | 2 +- tools/update_cpu_features.zig | 17 ++---- 23 files changed, 167 insertions(+), 199 deletions(-) diff --git a/lib/compiler/aro/main.zig b/lib/compiler/aro/main.zig index ca079c0e5f7e12dcba64de54a07cb807cb72b4ae..dcc07eaca64c9c69ce67ef7121128c026e4c3cda 100644 --- a/lib/compiler/aro/main.zig +++ b/lib/compiler/aro/main.zig @@ -18,7 +18,7 @@ var debug_allocator: std.heap.DebugAllocator(.{ .canary = @truncate(0xc647026dc6875134), }) = .{}; -pub fn main() u8 { +pub fn main(init: std.process.Init.Minimal) u8 { const gpa = if (@import("builtin").link_libc) std.heap.c_allocator else @@ -37,7 +37,7 @@ pub fn main() u8 { const fast_exit = @import("builtin").mode != .Debug; - const args = process.argsAlloc(arena) catch { + const args = init.args.toSlice(arena) catch { std.debug.print("out of memory\n", .{}); if (fast_exit) process.exit(1); return 1; diff --git a/lib/compiler/libc.zig b/lib/compiler/libc.zig index eb4614c95db3552f2e36342c4ce1ea7161347f31..a27e96dab6fd94864ba229e8ff4ff6903b665f25 100644 --- a/lib/compiler/libc.zig +++ b/lib/compiler/libc.zig @@ -24,23 +24,19 @@ const usage_libc = var stdout_buffer: [4096]u8 = undefined; -pub fn main() !void { - var arena_instance = std.heap.ArenaAllocator.init(std.heap.page_allocator); - defer arena_instance.deinit(); - const arena = arena_instance.allocator(); - const gpa = arena; +pub fn main(init: std.process.Init) !void { + const arena = init.arena.allocator(); + const gpa = init.gpa; + const io = init.io; + const args = try init.minimal.args.toSlice(arena); + const env_map = init.env_map; - var threaded: std.Io.Threaded = .init(gpa, .{}); - defer threaded.deinit(); - const io = threaded.io(); - - const args = try std.process.argsAlloc(arena); const zig_lib_directory = args[1]; var input_file: ?[]const u8 = null; var target_arch_os_abi: []const u8 = "native"; var print_includes: bool = false; - var stdout_writer = Io.File.stdout().writer(&stdout_buffer); + var stdout_writer = Io.File.stdout().writer(io, &stdout_buffer); const stdout = &stdout_writer.interface; { var i: usize = 2; @@ -77,7 +73,7 @@ pub fn main() !void { const libc_installation: ?*LibCInstallation = libc: { if (input_file) |libc_file| { const libc = try arena.create(LibCInstallation); - libc.* = LibCInstallation.parse(arena, libc_file, &target) catch |err| { + libc.* = LibCInstallation.parse(arena, io, libc_file, &target) catch |err| { fatal("unable to parse libc file at path {s}: {t}", .{ libc_file, err }); }; break :libc libc; @@ -90,11 +86,13 @@ pub fn main() !void { const libc_dirs = std.zig.LibCDirs.detect( arena, + io, zig_lib_directory, &target, is_native_abi, true, libc_installation, + env_map, ) catch |err| { const zig_target = try target.zigTriple(arena); fatal("unable to detect libc for target {s}: {t}", .{ zig_target, err }); @@ -114,7 +112,7 @@ pub fn main() !void { } if (input_file) |libc_file| { - var libc = LibCInstallation.parse(gpa, libc_file, &target) catch |err| { + var libc = LibCInstallation.parse(gpa, io, libc_file, &target) catch |err| { fatal("unable to parse libc file at path {s}: {t}", .{ libc_file, err }); }; defer libc.deinit(gpa); @@ -125,6 +123,7 @@ pub fn main() !void { var libc = LibCInstallation.findNative(gpa, io, .{ .verbose = true, .target = &target, + .env_map = env_map, }) catch |err| { fatal("unable to detect native libc: {t}", .{err}); }; diff --git a/lib/compiler/reduce.zig b/lib/compiler/reduce.zig index b0fa94cb06cf39b068e5d806af2d81e4efd3b5f2..abdd9f84362292f50dc1c67d04cbc707d2e30888 100644 --- a/lib/compiler/reduce.zig +++ b/lib/compiler/reduce.zig @@ -47,19 +47,11 @@ const Interestingness = enum { interesting, unknown, boring }; // - reduce flags sent to the compiler // - integrate with the build system? -pub fn main() !void { - var arena_instance = std.heap.ArenaAllocator.init(std.heap.page_allocator); - defer arena_instance.deinit(); - const arena = arena_instance.allocator(); - - var general_purpose_allocator: std.heap.GeneralPurposeAllocator(.{}) = .init; - const gpa = general_purpose_allocator.allocator(); - - var threaded: std.Io.Threaded = .init(gpa, .{}); - defer threaded.deinit(); - const io = threaded.io(); - - const args = try std.process.argsAlloc(arena); +pub fn main(init: std.process.Init) !void { + const arena = init.arena.allocator(); + const gpa = init.gpa; + const io = init.io; + const args = try init.minimal.args.toSlice(arena); var opt_checker_path: ?[]const u8 = null; var opt_root_source_file_path: ?[]const u8 = null; @@ -73,8 +65,7 @@ pub fn main() !void { const arg = args[i]; if (mem.startsWith(u8, arg, "-")) { if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) { - const stdout = Io.File.stdout(); - try stdout.writeAll(usage); + try Io.File.stdout().writeStreamingAll(io, usage); return std.process.cleanExit(io); } else if (mem.eql(u8, arg, "--")) { argv = args[i + 1 ..]; @@ -131,12 +122,10 @@ pub fn main() !void { if (!skip_smoke_test) { std.debug.print("smoke testing the interestingness check...\n", .{}); - switch (try runCheck(arena, interestingness_argv.items)) { + switch (try runCheck(arena, io, interestingness_argv.items)) { .interesting => {}, .boring, .unknown => |t| { - fatal("interestingness check returned {s} for unmodified input\n", .{ - @tagName(t), - }); + fatal("interestingness check returned {t} for unmodified input\n", .{t}); }, } } @@ -238,7 +227,7 @@ pub fn main() !void { try Io.Dir.cwd().writeFile(io, .{ .sub_path = root_source_file_path, .data = rendered.written() }); // std.debug.print("trying this code:\n{s}\n", .{rendered.items}); - const interestingness = try runCheck(arena, interestingness_argv.items); + const interestingness = try runCheck(arena, io, interestingness_argv.items); std.debug.print("{d} random transformations: {t}. {d}/{d}\n", .{ subset_size, interestingness, start_index, transformations.items.len, }); @@ -293,20 +282,24 @@ fn sortTransformations(transformations: []Walk.Transformation, rng: std.Random) fn termToInteresting(term: std.process.Child.Term) Interestingness { return switch (term) { - .Exited => |code| switch (code) { + .exited => |code| switch (code) { 0 => .interesting, 1 => .unknown, else => .boring, }, - else => b: { + .signal => |sig| { + std.debug.print("interestingness check terminated with signal {t}\n", .{sig}); + return .boring; + }, + else => { std.debug.print("interestingness check aborted unexpectedly\n", .{}); - break :b .boring; + return .boring; }, }; } fn runCheck(arena: Allocator, io: Io, argv: []const []const u8) !Interestingness { - const result = try std.process.run(arena, io, .{ .spawn_options = .{ .argv = argv } }); + const result = try std.process.run(arena, io, .{ .argv = argv }); if (result.stderr.len != 0) std.debug.print("{s}", .{result.stderr}); return termToInteresting(result.term); diff --git a/lib/compiler/resinator/main.zig b/lib/compiler/resinator/main.zig index e286a5f4b9d8047d5a77935567b24d71b9aee69c..562fb865419d609f0e4f7ce39f4c178f99a8211a 100644 --- a/lib/compiler/resinator/main.zig +++ b/lib/compiler/resinator/main.zig @@ -19,7 +19,7 @@ const fmtResourceType = @import("res.zig").NameOrOrdinal.fmtResourceType; const aro = @import("aro"); const compiler_util = @import("../util.zig"); -pub fn main() !void { +pub fn main(init: std.process.Init.Minimal) !void { var debug_allocator: std.heap.DebugAllocator(.{}) = .init; defer std.debug.assert(debug_allocator.deinit() == .ok); const gpa = debug_allocator.allocator(); @@ -32,7 +32,7 @@ pub fn main() !void { defer arena_state.deinit(); const arena = arena_state.allocator(); - const args = try std.process.argsAlloc(arena); + const args = try init.args.toSlice(arena); if (args.len < 2) { const stderr = try io.lockStderr(&.{}, null); diff --git a/lib/compiler/translate-c/main.zig b/lib/compiler/translate-c/main.zig index 825d13277662d060bb7504586de8a76a97c8773e..a257f65ce4cf690f2b23be5323f6815a65c33301 100644 --- a/lib/compiler/translate-c/main.zig +++ b/lib/compiler/translate-c/main.zig @@ -14,7 +14,7 @@ pub fn main(init: std.process.Init) u8 { const arena = init.arena.allocator(); const io = init.io; - const args = process.argsAlloc(arena) catch { + const args = init.minimal.args.toSlice(arena) catch { std.debug.print("ran out of memory allocating arguments\n", .{}); if (fast_exit) process.exit(1); return 1; diff --git a/lib/std/Build/Step.zig b/lib/std/Build/Step.zig index 93d6b9cdd63923a17e0cdacdfbfd0c00bb0085e3..745894551dd7f9e4239daba15b216e7a36688f24 100644 --- a/lib/std/Build/Step.zig +++ b/lib/std/Build/Step.zig @@ -360,11 +360,11 @@ pub fn captureChildProcess( try handleChildProcUnsupported(s); try handleVerbose(s.owner, null, argv); - const result = std.process.run(arena, io, .{ .spawn_options = .{ + const result = std.process.run(arena, io, .{ .argv = argv, .env_map = &graph.env_map, .progress_node = progress_node, - } }) catch |err| return s.fail("failed to run {s}: {t}", .{ argv[0], err }); + }) catch |err| return s.fail("failed to run {s}: {t}", .{ argv[0], err }); if (result.stderr.len > 0) { try s.result_error_msgs.append(arena, result.stderr); diff --git a/lib/std/Random/benchmark.zig b/lib/std/Random/benchmark.zig index 97afe23b95ef45a6468e84b9f72ce00031da6380..a545cda6d5dbfdccb1d6122d10d71354b5ed2654 100644 --- a/lib/std/Random/benchmark.zig +++ b/lib/std/Random/benchmark.zig @@ -123,14 +123,15 @@ fn mode(comptime x: comptime_int) comptime_int { return if (builtin.mode == .Debug) x / 64 else x; } -pub fn main() !void { +pub fn main(init: std.process.Init) !void { + const io = init.io; + const arena = init.arena.allocator(); + var stdout_buffer: [0x100]u8 = undefined; - var stdout_writer = Io.File.stdout().writer(&stdout_buffer); + var stdout_writer = Io.File.stdout().writer(io, &stdout_buffer); const stdout = &stdout_writer.interface; - var buffer: [1024]u8 = undefined; - var fixed = std.heap.FixedBufferAllocator.init(buffer[0..]); - const args = try std.process.argsAlloc(fixed.allocator()); + const args = try init.minimal.args.toSlice(arena); var filter: ?[]u8 = ""; var count: usize = mode(128 * MiB); diff --git a/lib/std/crypto/benchmark.zig b/lib/std/crypto/benchmark.zig index 23154324bc280d8b54d45c032de56c60670ac5ba..22f2b6fd106cf0e8963fec9c0d4b5e3025b0f3c0 100644 --- a/lib/std/crypto/benchmark.zig +++ b/lib/std/crypto/benchmark.zig @@ -503,16 +503,16 @@ fn mode(comptime x: comptime_int) comptime_int { return if (builtin.mode == .Debug) x / 64 else x; } -pub fn main() !void { +pub fn main(init: std.process.Init) !void { + const io = init.io; + const arena = init.arena.allocator(); + // Size of buffer is about size of printed message. var stdout_buffer: [0x100]u8 = undefined; - var stdout_writer = Io.File.stdout().writer(&stdout_buffer); + var stdout_writer = Io.File.stdout().writer(io, &stdout_buffer); const stdout = &stdout_writer.interface; - var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); - defer arena.deinit(); - const arena_allocator = arena.allocator(); - const args = try std.process.argsAlloc(arena_allocator); + const args = try init.minimal.args.toSlice(arena); var filter: ?[]u8 = ""; @@ -556,13 +556,9 @@ pub fn main() !void { } } - var io_threaded = std.Io.Threaded.init(arena_allocator, .{}); - defer io_threaded.deinit(); - const io = io_threaded.io(); - inline for (parallel_hashes) |H| { if (filter == null or std.mem.find(u8, H.name, filter.?) != null) { - const throughput = try benchmarkHashParallel(H.ty, mode(128 * MiB), arena_allocator, io); + const throughput = try benchmarkHashParallel(H.ty, mode(128 * MiB), arena, io); try stdout.print("{s:>17}: {:10} MiB/s\n", .{ H.name, throughput / (1 * MiB) }); try stdout.flush(); } @@ -634,7 +630,7 @@ pub fn main() !void { inline for (pwhashes) |H| { if (filter == null or std.mem.find(u8, H.name, filter.?) != null) { - const throughput = try benchmarkPwhash(arena_allocator, H.ty, H.params, mode(64), io); + const throughput = try benchmarkPwhash(arena, H.ty, H.params, mode(64), io); try stdout.print("{s:>17}: {d:10.3} s/ops\n", .{ H.name, throughput }); try stdout.flush(); } diff --git a/lib/std/hash/benchmark.zig b/lib/std/hash/benchmark.zig index 6744b87facaed5a65d6e1a987766af3e638931e7..76538be4a8fb116d9f36e0816770cc647038848c 100644 --- a/lib/std/hash/benchmark.zig +++ b/lib/std/hash/benchmark.zig @@ -353,14 +353,15 @@ fn mode(comptime x: comptime_int) comptime_int { return if (builtin.mode == .Debug) x / 64 else x; } -pub fn main() !void { +pub fn main(init: std.process.Init) !void { + const io = init.io; + const arena = init.arena.allocator(); + var stdout_buffer: [0x100]u8 = undefined; - var stdout_writer = Io.File.stdout().writer(&stdout_buffer); + var stdout_writer = Io.File.stdout().writer(io, &stdout_buffer); const stdout = &stdout_writer.interface; - var buffer: [1024]u8 = undefined; - var fixed = std.heap.FixedBufferAllocator.init(buffer[0..]); - const args = try std.process.argsAlloc(fixed.allocator()); + const args = try init.minimal.args.toSlice(arena); var filter: ?[]u8 = ""; var count: usize = mode(128 * MiB); diff --git a/lib/std/process.zig b/lib/std/process.zig index 1fd31d263494db052a90b45ad82ac0dce561fb57..c226b0c453a33bdfc87f13d8681016aadb972705 100644 --- a/lib/std/process.zig +++ b/lib/std/process.zig @@ -463,8 +463,33 @@ pub const RunError = posix.GetCwdError || posix.ReadError || SpawnError || posix }; pub const RunOptions = struct { - spawn_options: SpawnOptions, + argv: []const []const u8, max_output_bytes: usize = 50 * 1024, + + /// Set to change the current working directory when spawning the child process. + cwd: ?[]const u8 = null, + /// Set to change the current working directory when spawning the child process. + /// This is not yet implemented for Windows. See https://github.com/ziglang/zig/issues/5190 + /// Once that is done, `cwd` will be deprecated in favor of this field. + cwd_dir: ?Io.Dir = null, + /// Replaces the child environment when provided. The PATH value from here + /// is not used to resolve `argv[0]`; that resolution always uses parent + /// environment. + env_map: ?*const Environ.Map = null, + expand_arg0: ArgExpansion = .no_expand, + /// When populated, a pipe will be created for the child process to + /// communicate progress back to the parent. The file descriptor of the + /// write end of the pipe will be specified in the `ZIG_PROGRESS` + /// environment variable inside the child process. The progress reported by + /// the child will be attached to this progress node in the parent process. + /// + /// The child's progress tree will be grafted into the parent's progress tree, + /// by substituting this node with the child's root node. + progress_node: std.Progress.Node = std.Progress.Node.none, + /// Windows-only. Sets the CREATE_NO_WINDOW flag in CreateProcess. + create_no_window: bool = true, + /// Darwin-only. Disable ASLR for the child process. + disable_aslr: bool = false, }; pub const RunResult = struct { @@ -476,7 +501,20 @@ pub const RunResult = struct { /// Spawns a child process, waits for it, collecting stdout and stderr, and then returns. /// If it succeeds, the caller owns result.stdout and result.stderr memory. pub fn run(gpa: Allocator, io: Io, options: RunOptions) RunError!RunResult { - var child = try spawn(io, options.spawn_options); + var child = try spawn(io, .{ + .argv = options.argv, + .cwd = options.cwd, + .cwd_dir = options.cwd_dir, + .env_map = options.env_map, + .expand_arg0 = options.expand_arg0, + .progress_node = options.progress_node, + .create_no_window = options.create_no_window, + .disable_aslr = options.disable_aslr, + + .stdin = .ignore, + .stdout = .pipe, + .stderr = .pipe, + }); defer child.kill(io); var stdout: std.ArrayList(u8) = .empty; diff --git a/lib/std/zig/LibCInstallation.zig b/lib/std/zig/LibCInstallation.zig index 80c41c594b280d9b3a91aab92807d73fad89bb6e..2f7650b0fea25a95a6ab320f9213e8e398e28397 100644 --- a/lib/std/zig/LibCInstallation.zig +++ b/lib/std/zig/LibCInstallation.zig @@ -208,16 +208,16 @@ pub fn findNative(gpa: Allocator, io: Io, args: FindNativeOptions) FindError!Lib } else if (is_haiku) { try self.findNativeIncludeDirPosix(gpa, io, args); try self.findNativeGccDirHaiku(gpa, io, args); - self.crt_dir = try gpa.dupeZ(u8, "/system/develop/lib"); + self.crt_dir = try gpa.dupe(u8, "/system/develop/lib"); } else if (builtin.target.os.tag == .illumos) { // There is only one libc, and its headers/libraries are always in the same spot. - self.include_dir = try gpa.dupeZ(u8, "/usr/include"); - self.sys_include_dir = try gpa.dupeZ(u8, "/usr/include"); - self.crt_dir = try gpa.dupeZ(u8, "/usr/lib/64"); + self.include_dir = try gpa.dupe(u8, "/usr/include"); + self.sys_include_dir = try gpa.dupe(u8, "/usr/include"); + self.crt_dir = try gpa.dupe(u8, "/usr/lib/64"); } else if (std.process.can_spawn) { try self.findNativeIncludeDirPosix(gpa, io, args); switch (builtin.target.os.tag) { - .freebsd, .netbsd, .openbsd, .dragonfly => self.crt_dir = try gpa.dupeZ(u8, "/usr/lib"), + .freebsd, .netbsd, .openbsd, .dragonfly => self.crt_dir = try gpa.dupe(u8, "/usr/lib"), .linux => try self.findNativeCrtDirPosix(gpa, io, args), else => {}, } @@ -269,15 +269,13 @@ fn findNativeIncludeDirPosix(self: *LibCInstallation, gpa: Allocator, io: Io, ar const run_res = std.process.run(gpa, io, .{ .max_output_bytes = 1024 * 1024, - .spawn_options = .{ - .argv = argv.items, - .env_map = &env_map, - // Some C compilers, such as Clang, are known to rely on argv[0] to find the path - // to their own executable, without even bothering to resolve PATH. This results in the message: - // error: unable to execute command: Executable "" doesn't exist! - // So we use the expandArg0 variant of ChildProcess to give them a helping hand. - .expand_arg0 = .expand, - }, + .argv = argv.items, + .env_map = &env_map, + // Some C compilers, such as Clang, are known to rely on argv[0] to find the path + // to their own executable, without even bothering to resolve PATH. This results in the message: + // error: unable to execute command: Executable "" doesn't exist! + // So we use the expandArg0 variant of ChildProcess to give them a helping hand. + .expand_arg0 = .expand, }) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, else => { @@ -337,7 +335,7 @@ fn findNativeIncludeDirPosix(self: *LibCInstallation, gpa: Allocator, io: Io, ar if (self.include_dir == null) { if (search_dir.access(io, include_dir_example_file, .{})) |_| { - self.include_dir = try gpa.dupeZ(u8, search_path); + self.include_dir = try gpa.dupe(u8, search_path); } else |err| switch (err) { error.FileNotFound => {}, else => return error.FileSystem, @@ -346,7 +344,7 @@ fn findNativeIncludeDirPosix(self: *LibCInstallation, gpa: Allocator, io: Io, ar if (self.sys_include_dir == null) { if (search_dir.access(io, sys_include_dir_example_file, .{})) |_| { - self.sys_include_dir = try gpa.dupeZ(u8, search_path); + self.sys_include_dir = try gpa.dupe(u8, search_path); } else |err| switch (err) { error.FileNotFound => {}, else => return error.FileSystem, @@ -560,7 +558,7 @@ pub const CCPrintFileNameOptions = struct { }; /// caller owns returned memory -fn ccPrintFileName(gpa: Allocator, io: Io, args: CCPrintFileNameOptions) ![:0]u8 { +fn ccPrintFileName(gpa: Allocator, io: Io, args: CCPrintFileNameOptions) ![]u8 { // Detect infinite loops. var env_map = try args.env_map.clone(gpa); defer env_map.deinit(); @@ -587,15 +585,13 @@ fn ccPrintFileName(gpa: Allocator, io: Io, args: CCPrintFileNameOptions) ![:0]u8 const run_res = std.process.run(gpa, io, .{ .max_output_bytes = 1024 * 1024, - .spawn_options = .{ - .argv = argv.items, - .env_map = &env_map, - // Some C compilers, such as Clang, are known to rely on argv[0] to find the path - // to their own executable, without even bothering to resolve PATH. This results in the message: - // error: unable to execute command: Executable "" doesn't exist! - // So we use the expandArg0 variant of ChildProcess to give them a helping hand. - .expand_arg0 = .expand, - }, + .argv = argv.items, + .env_map = &env_map, + // Some C compilers, such as Clang, are known to rely on argv[0] to find the path + // to their own executable, without even bothering to resolve PATH. This results in the message: + // error: unable to execute command: Executable "" doesn't exist! + // So we use the expandArg0 variant of ChildProcess to give them a helping hand. + .expand_arg0 = .expand, }) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, else => return error.UnableToSpawnCCompiler, @@ -621,10 +617,10 @@ fn ccPrintFileName(gpa: Allocator, io: Io, args: CCPrintFileNameOptions) ![:0]u8 // So we detect failure by checking if the output matches exactly the input. if (std.mem.eql(u8, line, args.search_basename)) return error.LibCRuntimeNotFound; switch (args.want_dirname) { - .full_path => return gpa.dupeZ(u8, line), + .full_path => return gpa.dupe(u8, line), .only_dir => { const dirname = fs.path.dirname(line) orelse return error.LibCRuntimeNotFound; - return gpa.dupeZ(u8, dirname); + return gpa.dupe(u8, dirname); }, } } diff --git a/lib/std/zig/system/darwin.zig b/lib/std/zig/system/darwin.zig index df07c68cbaa7fad5d65d802a2de0c63eea3694e4..b31dc484ceb3ce3a87a049610114a547ef5cdf2f 100644 --- a/lib/std/zig/system/darwin.zig +++ b/lib/std/zig/system/darwin.zig @@ -17,9 +17,9 @@ pub const macos = @import("darwin/macos.zig"); /// /// If error.OutOfMemory occurs in Allocator, this function returns null. pub fn isSdkInstalled(gpa: Allocator, io: Io) bool { - const result = std.process.run(gpa, io, .{ .spawn_options = .{ + const result = std.process.run(gpa, io, .{ .argv = &.{ "xcode-select", "--print-path" }, - } }) catch return false; + }) catch return false; defer { gpa.free(result.stderr); gpa.free(result.stdout); @@ -47,7 +47,7 @@ pub fn getSdk(gpa: Allocator, io: Io, target: *const Target) ?[]const u8 { else => return null, }; const argv = &[_][]const u8{ "xcrun", "--sdk", sdk, "--show-sdk-path" }; - const result = std.process.run(gpa, io, .{ .spawn_options = .{ .argv = argv } }) catch return null; + const result = std.process.run(gpa, io, .{ .argv = argv }) catch return null; defer { gpa.free(result.stderr); gpa.free(result.stdout); diff --git a/test/standalone/child_process/child.zig b/test/standalone/child_process/child.zig index 80e2edaa7f3bed3af38b9961fb364ec5dd0d422c..8e19d53b3c625c39de7ac19347270888200983a4 100644 --- a/test/standalone/child_process/child.zig +++ b/test/standalone/child_process/child.zig @@ -4,31 +4,23 @@ const Io = std.Io; // 42 is expected by parent; other values result in test failure var exit_code: u8 = 42; -pub fn main() !void { - var arena_state = std.heap.ArenaAllocator.init(std.heap.page_allocator); - const arena = arena_state.allocator(); - - var threaded: std.Io.Threaded = .init(arena, .{}); - defer threaded.deinit(); - const io = threaded.io(); - - try run(arena, io); - arena_state.deinit(); +pub fn main(init: std.process.Init) !void { + try run(init.arena.allocator(), init.io, init.minimal.args); std.process.exit(exit_code); } -fn run(allocator: std.mem.Allocator, io: Io) !void { - var args = try std.process.argsWithAllocator(allocator); - defer args.deinit(); - _ = args.next() orelse unreachable; // skip binary name +fn run(arena: std.mem.Allocator, io: Io, args: std.process.Args) !void { + var it = try args.iterateAllocator(arena); + defer it.deinit(); + _ = it.next() orelse unreachable; // skip binary name // test cmd args const hello_arg = "hello arg"; - const a1 = args.next() orelse unreachable; + const a1 = it.next() orelse unreachable; if (!std.mem.eql(u8, a1, hello_arg)) { testError(io, "first arg: '{s}'; want '{s}'", .{ a1, hello_arg }); } - if (args.next()) |a2| { + if (it.next()) |a2| { testError(io, "expected only one arg; got more: {s}", .{a2}); } diff --git a/test/standalone/install_headers/check_exists.zig b/test/standalone/install_headers/check_exists.zig index 50ad4d08188c5d333411f2c7720b72695c74a40f..ac7dc62592da91dea3bf4582ef3da04f0c268853 100644 --- a/test/standalone/install_headers/check_exists.zig +++ b/test/standalone/install_headers/check_exists.zig @@ -2,17 +2,13 @@ const std = @import("std"); /// Checks the existence of files relative to cwd. /// A path starting with ! should not exist. -pub fn main() !void { - var arena_state = std.heap.ArenaAllocator.init(std.heap.page_allocator); - defer arena_state.deinit(); +pub fn main(init: std.process.Init) !void { + const arena = init.arena.allocator(); + const io = init.io; - const arena = arena_state.allocator(); - - var arg_it = try std.process.argsWithAllocator(arena); + var arg_it = try init.minimal.args.iterateAllocator(arena); _ = arg_it.next(); - const io = std.Io.Threaded.global_single_threaded.ioBasic(); - const cwd = std.Io.Dir.cwd(); const cwd_realpath = try cwd.realPathFileAlloc(io, ".", arena); diff --git a/test/standalone/libfuzzer/main.zig b/test/standalone/libfuzzer/main.zig index 0bc093d8705ba63221cd87114931a0fc3e8f0bbc..836f25285b45d89f58b2c924ee0621cc65243e6e 100644 --- a/test/standalone/libfuzzer/main.zig +++ b/test/standalone/libfuzzer/main.zig @@ -6,19 +6,14 @@ fn testOne(in: abi.Slice) callconv(.c) void { std.debug.assertReadable(in.toSlice()); } -pub fn main() !void { - var debug_gpa_ctx: std.heap.DebugAllocator(.{}) = .init; - defer _ = debug_gpa_ctx.deinit(); - const gpa = debug_gpa_ctx.allocator(); +pub fn main(init: std.process.Init) !void { + const gpa = init.gpa; + const io = init.io; - var args = try std.process.argsWithAllocator(gpa); + var args = try init.minimal.args.iterateAllocator(gpa); defer args.deinit(); _ = args.skip(); // executable name - var threaded: std.Io.Threaded = .init(gpa, .{}); - defer threaded.deinit(); - const io = threaded.io(); - const cache_dir_path = args.next() orelse @panic("expected cache directory path argument"); var cache_dir = try std.Io.Dir.cwd().openDir(io, cache_dir_path, .{}); defer cache_dir.close(io); diff --git a/test/standalone/run_output_caching/main.zig b/test/standalone/run_output_caching/main.zig index 9786101d325ebf6098dc4416e47257279febdf05..d7838cab9930f9d6fd7739ec7a554247c6c6ac7d 100644 --- a/test/standalone/run_output_caching/main.zig +++ b/test/standalone/run_output_caching/main.zig @@ -1,8 +1,8 @@ const std = @import("std"); -pub fn main() !void { - const io = std.Io.Threaded.global_single_threaded.ioBasic(); - var args = try std.process.argsWithAllocator(std.heap.page_allocator); +pub fn main(init: std.process.Init) !void { + const io = init.io; + var args = try init.minimal.argsAllocator(init.arena.allocator()); _ = args.skip(); const filename = args.next().?; const file = try std.Io.Dir.cwd().createFile(io, filename, .{}); diff --git a/test/standalone/windows_bat_args/fuzz.zig b/test/standalone/windows_bat_args/fuzz.zig index f0da2321b13870302594fb1446142ce86188f479..123c0be31454ccedb32d6ff2816745382013831c 100644 --- a/test/standalone/windows_bat_args/fuzz.zig +++ b/test/standalone/windows_bat_args/fuzz.zig @@ -4,16 +4,11 @@ const std = @import("std"); const Io = std.Io; const Allocator = std.mem.Allocator; -pub fn main() anyerror!void { - var debug_alloc_inst: std.heap.DebugAllocator(.{}) = .init; - defer std.debug.assert(debug_alloc_inst.deinit() == .ok); - const gpa = debug_alloc_inst.allocator(); +pub fn main(init: std.process.Init) !void { + const gpa = init.gpa; + const io = init.io; - var threaded: Io.Threaded = .init(gpa, .{}); - defer threaded.deinit(); - const io = threaded.io(); - - var it = try std.process.argsWithAllocator(gpa); + var it = try init.minimal.argsAllocator(gpa); defer it.deinit(); _ = it.next() orelse unreachable; // skip binary name const child_exe_path_orig = it.next() orelse unreachable; diff --git a/test/standalone/windows_bat_args/test.zig b/test/standalone/windows_bat_args/test.zig index ac851cf8f6cd58f01025ef5a7d8aa2653452cbe5..38ecb5e2f897a001c49b6c3eb6bc3cc9aa0fb64d 100644 --- a/test/standalone/windows_bat_args/test.zig +++ b/test/standalone/windows_bat_args/test.zig @@ -2,15 +2,11 @@ const std = @import("std"); const Io = std.Io; const Allocator = std.mem.Allocator; -pub fn main() anyerror!void { - var debug_alloc_inst: std.heap.DebugAllocator(.{}) = .init; - defer std.debug.assert(debug_alloc_inst.deinit() == .ok); - const gpa = debug_alloc_inst.allocator(); +pub fn main(init: std.process.Init) !void { + const gpa = init.gpa; + const io = init.io; - var threaded: Io.Threaded = .init(gpa, .{}); - const io = threaded.io(); - - var it = try std.process.argsWithAllocator(gpa); + var it = try init.minimal.argsAllocator(gpa); defer it.deinit(); _ = it.next() orelse unreachable; // skip binary name const child_exe_path_orig = it.next() orelse unreachable; diff --git a/test/standalone/windows_spawn/main.zig b/test/standalone/windows_spawn/main.zig index c9522bf4de301022d4b8e39d0f5e71fb97040ba2..32db164a5a53a6317e3856a39bf170376093d235 100644 --- a/test/standalone/windows_spawn/main.zig +++ b/test/standalone/windows_spawn/main.zig @@ -5,16 +5,11 @@ const Allocator = std.mem.Allocator; const windows = std.os.windows; const utf16Literal = std.unicode.utf8ToUtf16LeStringLiteral; -pub fn main() anyerror!void { - var debug_allocator: std.heap.DebugAllocator(.{}) = .init; - defer if (debug_allocator.deinit() == .leak) @panic("found memory leaks"); - const gpa = debug_allocator.allocator(); +pub fn main(init: std.process.Init) !void { + const gpa = init.gpa; + const io = init.io; - var threaded: std.Io.Threaded = .init(gpa, .{}); - defer threaded.deinit(); - const io = threaded.io(); - - var it = try std.process.argsWithAllocator(gpa); + var it = try init.minimal.argsAllocator(gpa); defer it.deinit(); _ = it.next() orelse unreachable; // skip binary name const hello_exe_cache_path = it.next() orelse unreachable; diff --git a/tools/docgen.zig b/tools/docgen.zig index ac0d26b995c9ed8cfb9a2c2f13dff2082550b093..c526233f517c2a570b9f300a240d5a0da3312df9 100644 --- a/tools/docgen.zig +++ b/tools/docgen.zig @@ -28,21 +28,13 @@ const usage = \\ ; -pub fn main() !void { - var arena_instance = std.heap.ArenaAllocator.init(std.heap.page_allocator); - defer arena_instance.deinit(); +pub fn main(init: std.process.Init) !void { + const arena = init.arena.allocator(); + const io = init.io; - const arena = arena_instance.allocator(); - - var args_it = try process.argsWithAllocator(arena); + var args_it = try init.minimal.args.iterateAllocator(arena); if (!args_it.skip()) @panic("expected self arg"); - const gpa = arena; - - var threaded: std.Io.Threaded = .init(gpa, .{}); - defer threaded.deinit(); - const io = threaded.io(); - var opt_code_dir: ?[]const u8 = null; var opt_input: ?[]const u8 = null; var opt_output: ?[]const u8 = null; diff --git a/tools/doctest.zig b/tools/doctest.zig index cd836c624e6663ee888073bd847306bf6b64856a..44d5954f7e845b88168b2fa07ba01bb926421c11 100644 --- a/tools/doctest.zig +++ b/tools/doctest.zig @@ -29,21 +29,13 @@ const usage = \\ ; -pub fn main() !void { - var arena_instance = std.heap.ArenaAllocator.init(std.heap.page_allocator); - defer arena_instance.deinit(); +pub fn main(init: std.process.Init) !void { + const arena = init.arena.allocator(); + const io = init.io; - const arena = arena_instance.allocator(); - - var args_it = try process.argsWithAllocator(arena); + var args_it = try init.minimal.args.iterateAllocator(arena); if (!args_it.skip()) fatal("missing argv[0]", .{}); - const gpa = arena; - - var threaded: std.Io.Threaded = .init(gpa, .{}); - defer threaded.deinit(); - const io = threaded.io(); - var opt_input: ?[]const u8 = null; var opt_output: ?[]const u8 = null; var opt_zig: ?[]const u8 = null; diff --git a/tools/incr-check.zig b/tools/incr-check.zig index 06b6daa648f352eb5487a014d59e88a8925a5cbb..d9664be7108a6f650d9de3dffcf84ff7021a52b1 100644 --- a/tools/incr-check.zig +++ b/tools/incr-check.zig @@ -44,7 +44,7 @@ pub fn main(init: std.process.Init) !void { var debug_log_args: std.ArrayList([]const u8) = .empty; - var arg_it = try std.process.argsWithAllocator(arena); + var arg_it = try init.minimal.argsIterator(arena); _ = arg_it.skip(); while (arg_it.next()) |arg| { if (arg.len > 0 and arg[0] == '-') { diff --git a/tools/update_cpu_features.zig b/tools/update_cpu_features.zig index 3852a85193f059e74fb7befb118d68cee5e2b10b..6def3db6baf18782e28e34fa781e0efde7030623 100644 --- a/tools/update_cpu_features.zig +++ b/tools/update_cpu_features.zig @@ -1883,20 +1883,11 @@ const targets = [_]ArchTarget{ }, }; -pub fn main() anyerror!void { - var debug_allocator: std.heap.DebugAllocator(.{}) = .init; - defer _ = debug_allocator.deinit(); - const gpa = debug_allocator.allocator(); +pub fn main(init: std.process.Init) !void { + const arena = init.arena_allocator.allocator(); + const io = init.io; - var arena_state: std.heap.ArenaAllocator = .init(gpa); - defer arena_state.deinit(); - const arena = arena_state.allocator(); - - var threaded: std.Io.Threaded = .init(gpa, .{}); - defer threaded.deinit(); - const io = threaded.io(); - - var args = try std.process.argsWithAllocator(arena); + var args = try init.minimal.args.iterateAllocator(arena); const args0 = args.next().?; const llvm_tblgen_exe = args.next() orelse -- 2.54.0 From 85fe35d246d0076d403fc5ee90f4434da7206fd5 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 1 Jan 2026 20:39:16 -0800 Subject: [PATCH 21/60] compiler: fix -Denable-llvm compilation failures --- lib/std/Io/Threaded.zig | 11 ----- src/Compilation.zig | 93 +++++++++++++++++++++++------------------ src/introspect.zig | 2 +- src/libs/freebsd.zig | 2 + src/libs/glibc.zig | 2 + src/libs/libcxx.zig | 2 + src/libs/libtsan.zig | 1 + src/libs/libunwind.zig | 1 + src/libs/musl.zig | 1 + src/libs/netbsd.zig | 2 + src/main.zig | 4 +- 11 files changed, 67 insertions(+), 54 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 965266aee915d7c9b14c1445ef5fb0a5fb2e77f5..b6715d03b53428e2a281e7c0ec2c4dd8bdaad840 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -14279,17 +14279,6 @@ fn testArgvToCommandLineWindows(argv: []const []const u8, expected_cmd_line: []c try std.testing.expectEqualStrings(expected_cmd_line, cmd_line); } -/// Replaces the current process image with the executed process. If this -/// function succeeds, it does not return. -/// -/// This operation is not available on all targets. `can_execv` -/// -/// This function also uses the PATH environment variable to get the full path to the executable. -/// If `file` is an absolute path, this is the same as `execveZ`. -/// -/// Like `execvpeZ` except if `arg0_expand` is `.expand`, then `argv` is mutable, -/// and `argv[0]` is expanded to be the same absolute path that is passed to the execve syscall. -/// If this function returns with an error, `argv[0]` will be restored to the value it was when it was passed in. fn execvpeZ_expandArg0( arg0_expand: process.ArgExpansion, file: [*:0]const u8, diff --git a/src/Compilation.zig b/src/Compilation.zig index ac5df44316b3edc3d262ce93c3c91a227d2e4425..d55f5ad48fd65164d431995c78a886999adea23d 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -54,7 +54,7 @@ gpa: Allocator, /// threads at once. arena: Allocator, io: Io, -environ_map: *std.process.Environ.Map, +environ_map: *const std.process.Environ.Map, thread_limit: usize, /// Not every Compilation compiles .zig code! For example you could do `zig build-exe foo.o`. zcu: ?*Zcu, @@ -762,12 +762,12 @@ pub const Directories = struct { .wasi => void, else => []const u8, }, - env_map: *std.process.Environ.Map, + env_map: *const std.process.Environ.Map, ) Directories { const wasi = builtin.target.os.tag == .wasi; const cwd = introspect.getResolvedCwd(arena) catch |err| { - fatal("unable to get cwd: {s}", .{@errorName(err)}); + fatal("unable to get cwd: {t}", .{err}); }; const zig_lib: Cache.Directory = d: { @@ -1799,7 +1799,7 @@ pub const CreateOptions = struct { parent_whole_cache: ?ParentWholeCache = null, - environ_map: *std.process.Environ.Map, + environ_map: *const std.process.Environ.Map, pub const Entry = link.File.OpenOptions.Entry; @@ -5713,7 +5713,7 @@ pub fn translateC( translated_basename: []const u8, owner_mod: *Package.Module, prog_node: std.Progress.Node, - env_map: *std.process.Environ.Map, + env_map: *const std.process.Environ.Map, ) !CImportResult { dev.check(.translate_c_command); @@ -6260,7 +6260,7 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr // that we could "tail call" clang by doing an execve, and any use of // the caching system would actually be problematic since the user is // presumably doing their own caching by using dep file flags. - if (std.process.can_execv and direct_o and + if (std.process.can_replace and direct_o and comp.disable_c_depfile and comp.clang_passthrough_mode) { try comp.addCCArgs(arena, &argv, ext, null, c_object.src.owner); @@ -6292,8 +6292,8 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr try dumpArgv(io, argv.items); } - const err = std.process.execv(arena, argv.items); - fatal("unable to execv clang: {s}", .{@errorName(err)}); + const err = std.process.replace(io, .{ .argv = argv.items }); + fatal("unable to replace process with clang: {t}", .{err}); } // We can't know the digest until we do the C compiler invocation, @@ -6348,14 +6348,21 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr else => log.warn("failed to delete '{s}': {s}", .{ dep_file_path, @errorName(err) }), }; if (std.process.can_spawn) { - var child = std.process.Child.init(argv.items, arena); if (comp.clang_passthrough_mode) { - child.stdin_behavior = .inherit; - child.stdout_behavior = .inherit; - child.stderr_behavior = .inherit; - - const term = child.spawnAndWait(io) catch |err| { - return comp.failCObj(c_object, "failed to spawn zig clang (passthrough mode) {s}: {s}", .{ argv.items[0], @errorName(err) }); + var child = std.process.spawn(io, .{ + .argv = argv.items, + .stdin = .inherit, + .stdout = .inherit, + .stderr = .inherit, + }) catch |err| { + return comp.failCObj(c_object, "failed to spawn zig clang (passthrough mode) {s}: {t}", .{ + argv.items[0], err, + }); + }; + const term = child.wait(io) catch |err| { + return comp.failCObj(c_object, "failed to wait zig clang (passthrough mode) {s}: {t}", .{ + argv.items[0], err, + }); }; switch (term) { .exited => |code| { @@ -6367,36 +6374,41 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr }, else => std.process.abort(), } - } else { - child.stdin_behavior = .ignore; - child.stdout_behavior = .ignore; - child.stderr_behavior = .pipe; + unreachable; + } - try child.spawn(io); + var child = try std.process.spawn(io, .{ + .argv = argv.items, + .stdin = .ignore, + .stdout = .ignore, + .stderr = .pipe, + }); - var stderr_reader = child.stderr.?.readerStreaming(io, &.{}); - const stderr = try stderr_reader.interface.allocRemaining(arena, .limited(std.math.maxInt(u32))); + var stderr_reader = child.stderr.?.readerStreaming(io, &.{}); + const stderr = try stderr_reader.interface.allocRemaining(arena, .limited(std.math.maxInt(u32))); - const term = child.wait(io) catch |err| { - return comp.failCObj(c_object, "failed to spawn zig clang {s}: {s}", .{ argv.items[0], @errorName(err) }); - }; + const term = child.wait(io) catch |err| + return comp.failCObj(c_object, "failed to spawn zig clang {s}: {t}", .{ argv.items[0], err }); - switch (term) { - .exited => |code| if (code != 0) if (out_diag_path) |diag_file_path| { - const bundle = CObject.Diag.Bundle.parse(gpa, io, diag_file_path) catch |err| { - log.err("{}: failed to parse clang diagnostics: {s}", .{ err, stderr }); - return comp.failCObj(c_object, "clang exited with code {d}", .{code}); - }; - return comp.failCObjWithOwnedDiagBundle(c_object, bundle); - } else { - log.err("clang failed with stderr: {s}", .{stderr}); + switch (term) { + .exited => |code| if (code != 0) if (out_diag_path) |diag_file_path| { + const bundle = CObject.Diag.Bundle.parse(gpa, io, diag_file_path) catch |err| { + log.err("{}: failed to parse clang diagnostics: {s}", .{ err, stderr }); return comp.failCObj(c_object, "clang exited with code {d}", .{code}); - }, - else => { - log.err("clang terminated with stderr: {s}", .{stderr}); - return comp.failCObj(c_object, "clang terminated unexpectedly", .{}); - }, - } + }; + return comp.failCObjWithOwnedDiagBundle(c_object, bundle); + } else { + log.err("clang failed with stderr: {s}", .{stderr}); + return comp.failCObj(c_object, "clang exited with code {d}", .{code}); + }, + .signal => |sig| { + log.err("clang failed with stderr: {s}", .{stderr}); + return comp.failCObj(c_object, "clang terminated with signal {t}", .{sig}); + }, + else => { + log.err("clang terminated with stderr: {s}", .{stderr}); + return comp.failCObj(c_object, "clang terminated unexpectedly", .{}); + }, } } else { const exit_code = try clangMain(arena, argv.items); @@ -8113,6 +8125,7 @@ pub fn build_crt_file( .verbose_llvm_cpu_features = comp.verbose_llvm_cpu_features, .clang_passthrough_mode = comp.clang_passthrough_mode, .skip_linker_dependencies = true, + .environ_map = comp.environ_map, }) catch |err| switch (err) { error.CreateFail => { comp.lockAndSetMiscFailure(misc_task_tag, "sub-compilation of {t} failed: {f}", .{ misc_task_tag, sub_create_diag }); diff --git a/src/introspect.zig b/src/introspect.zig index 574bf9628e9909f0e8833f014b0577d808c15c0e..7c5a2e17e763a45afaa626acba62f51b9a3ca85e 100644 --- a/src/introspect.zig +++ b/src/introspect.zig @@ -102,7 +102,7 @@ pub fn findZigLibDirFromSelfExe( return error.FileNotFound; } -pub fn resolveGlobalCacheDir(arena: Allocator, env_map: *std.process.Environ.Map) ![]const u8 { +pub fn resolveGlobalCacheDir(arena: Allocator, env_map: *const std.process.Environ.Map) ![]const u8 { if (std.zig.EnvVar.ZIG_GLOBAL_CACHE_DIR.get(env_map)) |value| return value; const app_name = "zig"; diff --git a/src/libs/freebsd.zig b/src/libs/freebsd.zig index ba85f45830c26385f7d9cbc6af73fa735d6f2eb3..9ad7eb7e1a47b8788b9a6cd48a21032f6712822c 100644 --- a/src/libs/freebsd.zig +++ b/src/libs/freebsd.zig @@ -445,6 +445,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye .gpa = gpa, .io = io, .manifest_dir = try comp.dirs.global_cache.handle.createDirPathOpen(io, "h", .{}), + .cwd = comp.dirs.cwd, }; cache.addPrefix(.{ .path = null, .handle = Io.Dir.cwd() }); cache.addPrefix(comp.dirs.zig_lib); @@ -1119,6 +1120,7 @@ fn buildSharedLib( .soname = soname, .c_source_files = &c_source_files, .skip_linker_dependencies = true, + .environ_map = comp.environ_map, }) catch |err| switch (err) { error.CreateFail => { comp.lockAndSetMiscFailure(misc_task, "sub-compilation of {t} failed: {f}", .{ misc_task, sub_create_diag }); diff --git a/src/libs/glibc.zig b/src/libs/glibc.zig index e9b6ce18823fe6b684f3636a0219e8840c607f54..10e8446fa4ebde278042bb0da18fc79d858850ba 100644 --- a/src/libs/glibc.zig +++ b/src/libs/glibc.zig @@ -680,6 +680,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye .gpa = gpa, .io = io, .manifest_dir = try comp.dirs.global_cache.handle.createDirPathOpen(io, "h", .{}), + .cwd = comp.dirs.cwd, }; cache.addPrefix(.{ .path = null, .handle = Io.Dir.cwd() }); cache.addPrefix(comp.dirs.zig_lib); @@ -1258,6 +1259,7 @@ fn buildSharedLib( .soname = soname, .c_source_files = &c_source_files, .skip_linker_dependencies = true, + .environ_map = comp.environ_map, }) catch |err| switch (err) { error.CreateFail => { comp.lockAndSetMiscFailure(misc_task, "sub-compilation of {t} failed: {f}", .{ misc_task, sub_create_diag }); diff --git a/src/libs/libcxx.zig b/src/libs/libcxx.zig index d293a3b8999a2b44fdfd3c223eef2f5af352b872..bd8863991d63db6ed10d14cc426f0961fec0d676 100644 --- a/src/libs/libcxx.zig +++ b/src/libs/libcxx.zig @@ -275,6 +275,7 @@ pub fn buildLibCxx(comp: *Compilation, prog_node: std.Progress.Node) BuildError! .verbose_llvm_cpu_features = comp.verbose_llvm_cpu_features, .clang_passthrough_mode = comp.clang_passthrough_mode, .skip_linker_dependencies = true, + .environ_map = comp.environ_map, }) catch |err| { switch (err) { else => comp.lockAndSetMiscFailure(misc_task, "unable to build libc++: create compilation failed: {t}", .{err}), @@ -468,6 +469,7 @@ pub fn buildLibCxxAbi(comp: *Compilation, prog_node: std.Progress.Node) BuildErr .verbose_llvm_cpu_features = comp.verbose_llvm_cpu_features, .clang_passthrough_mode = comp.clang_passthrough_mode, .skip_linker_dependencies = true, + .environ_map = comp.environ_map, }) catch |err| { switch (err) { else => comp.lockAndSetMiscFailure(misc_task, "unable to build libc++abi: create compilation failed: {t}", .{err}), diff --git a/src/libs/libtsan.zig b/src/libs/libtsan.zig index 3dcbc132b13633e4b840030521a45b9ccade7c24..2f9574c47343aad0bf7b90013cbdd8c49f46c6cc 100644 --- a/src/libs/libtsan.zig +++ b/src/libs/libtsan.zig @@ -301,6 +301,7 @@ pub fn buildTsan(comp: *Compilation, prog_node: std.Progress.Node) BuildError!vo .linker_allow_shlib_undefined = linker_allow_shlib_undefined, .install_name = install_name, .headerpad_size = headerpad_size, + .environ_map = comp.environ_map, }) catch |err| { switch (err) { else => comp.lockAndSetMiscFailure(misc_task, "unable to build {t}: create compilation failed: {t}", .{ misc_task, err }), diff --git a/src/libs/libunwind.zig b/src/libs/libunwind.zig index 7ab079b20511c5712f23c64fe5940c71d51ca4ed..787a87f951094245fe042a22da043b41b833373f 100644 --- a/src/libs/libunwind.zig +++ b/src/libs/libunwind.zig @@ -166,6 +166,7 @@ pub fn buildStaticLib(comp: *Compilation, prog_node: std.Progress.Node) BuildErr .verbose_llvm_cpu_features = comp.verbose_llvm_cpu_features, .clang_passthrough_mode = comp.clang_passthrough_mode, .skip_linker_dependencies = true, + .environ_map = comp.environ_map, }) catch |err| { switch (err) { else => comp.lockAndSetMiscFailure(misc_task, "unable to build {t}: create compilation failed: {t}", .{ misc_task, err }), diff --git a/src/libs/musl.zig b/src/libs/musl.zig index 1a1807f250c1fe96d118ec664225496e89558cd0..b847c279becb028610485e4dfefe0656f2f07e09 100644 --- a/src/libs/musl.zig +++ b/src/libs/musl.zig @@ -272,6 +272,7 @@ pub fn buildCrtFile(comp: *Compilation, in_crt_file: CrtFile, prog_node: std.Pro }, .skip_linker_dependencies = true, .soname = "libc.so", + .environ_map = comp.environ_map, }) catch |err| switch (err) { error.CreateFail => { comp.lockAndSetMiscFailure(misc_task, "sub-compilation of {t} failed: {f}", .{ misc_task, sub_create_diag }); diff --git a/src/libs/netbsd.zig b/src/libs/netbsd.zig index 9e4213d23726dd67a1b27fa4dcefe5c01ca347c2..87d2909e0782d2d1a9be2ec5c29f057bef03b6dc 100644 --- a/src/libs/netbsd.zig +++ b/src/libs/netbsd.zig @@ -386,6 +386,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye .gpa = gpa, .io = io, .manifest_dir = try comp.dirs.global_cache.handle.createDirPathOpen(io, "h", .{}), + .cwd = comp.dirs.cwd, }; cache.addPrefix(.{ .path = null, .handle = Io.Dir.cwd() }); cache.addPrefix(comp.dirs.zig_lib); @@ -761,6 +762,7 @@ fn buildSharedLib( .soname = soname, .c_source_files = &c_source_files, .skip_linker_dependencies = true, + .environ_map = comp.environ_map, }) catch |err| switch (err) { error.CreateFail => { comp.lockAndSetMiscFailure(misc_task, "sub-compilation of {t} failed: {f}", .{ misc_task, sub_create_diag }); diff --git a/src/main.zig b/src/main.zig index a7b6efa33fe4f53d5823ae44b8a32ceecda81140..3d90e031d1f85902bafae21ea6f055d249527108 100644 --- a/src/main.zig +++ b/src/main.zig @@ -4708,7 +4708,7 @@ pub fn translateC( arena: Allocator, io: Io, argv: []const []const u8, - env_map: *process.Environ.Map, + env_map: *const process.Environ.Map, prog_node: std.Progress.Node, capture: ?*[]u8, ) !void { @@ -5516,7 +5516,7 @@ fn jitCmd( arena: Allocator, io: Io, args: []const []const u8, - env_map: *process.Environ.Map, + env_map: *const process.Environ.Map, options: JitCmdOptions, ) !void { dev.check(.jit_command); -- 2.54.0 From 0ca83dd9d2d3fb38bc1e7baf773fb67a37a2bd2a Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 1 Jan 2026 21:34:14 -0800 Subject: [PATCH 22/60] start: fix compilation with -lc on windows --- lib/std/start.zig | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/lib/std/start.zig b/lib/std/start.zig index 8a33053634ef5f652c60e1a32bb452b128efb759..543e871d277d8ec531b84a5c7339c56c87e28340 100644 --- a/lib/std/start.zig +++ b/lib/std/start.zig @@ -623,22 +623,33 @@ inline fn callMainWithArgs(argc: usize, argv: [*][*:0]u8, envp: [:null]?[*:0]u8) return callMain(argv[0..argc], envp); } -fn main(c_argc: c_int, c_argv: [*][*:0]u8, c_envp: [*:null]?[*:0]u8) callconv(.c) c_int { +fn main(c_argc: c_int, c_argv: [*][*:0]c_char, c_envp: [*:null]?[*:0]c_char) callconv(.c) c_int { var env_count: usize = 0; while (c_envp[env_count] != null) : (env_count += 1) {} const envp = c_envp[0..env_count :null]; - if (builtin.os.tag == .linux) { - const at_phdr = std.c.getauxval(elf.AT_PHDR); - const at_phnum = std.c.getauxval(elf.AT_PHNUM); - const phdrs = (@as([*]elf.Phdr, @ptrFromInt(at_phdr)))[0..at_phnum]; - expandStackSize(phdrs); + switch (builtin.os.tag) { + .linux => { + const at_phdr = std.c.getauxval(elf.AT_PHDR); + const at_phnum = std.c.getauxval(elf.AT_PHNUM); + const phdrs = (@as([*]elf.Phdr, @ptrFromInt(at_phdr)))[0..at_phnum]; + expandStackSize(phdrs); + }, + .windows => { + // On Windows, we ignore libc environment and argv and get those + // values in their intended encoding from the PEB instead. + std.debug.maybeEnableSegfaultHandler(); + const cmd_line = std.os.windows.peb().ProcessParameters.CommandLine; + const cmd_line_w = cmd_line.Buffer.?[0..@divExact(cmd_line.Length, 2)]; + return callMain(cmd_line_w, {}); + }, + else => {}, } - return callMainWithArgs(@as(usize, @intCast(c_argc)), @as([*][*:0]u8, @ptrCast(c_argv)), envp); + return callMainWithArgs(@as(usize, @intCast(c_argc)), @as([*][*:0]u8, @ptrCast(c_argv)), @ptrCast(envp)); } -fn mainWithoutEnv(c_argc: c_int, c_argv: [*][*:0]u8) callconv(.c) c_int { +fn mainWithoutEnv(c_argc: c_int, c_argv: [*][*:0]c_char) callconv(.c) c_int { const argv = @as([*][*:0]u8, @ptrCast(c_argv))[0..@intCast(c_argc)]; if (@sizeOf(std.Io.Threaded.Argv0) != 0) { if (std.Options.debug_threaded_io) |t| t.argv0.value = argv[0]; -- 2.54.0 From f2cf7b538f5c8ec0da69715d39ad6d433f3168d6 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 1 Jan 2026 23:43:12 -0800 Subject: [PATCH 23/60] std.Io.Threaded: fix the child process error fd mechanism --- lib/std/Io/Threaded.zig | 68 ++++++++++++++++++++++++++++------------- 1 file changed, 46 insertions(+), 22 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index b6715d03b53428e2a281e7c0ec2c4dd8bdaad840..e9190f590b0ba1d514b1a9fa9c768a0efb0dffba 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -12790,9 +12790,15 @@ fn processSpawnUnsupported(userdata: ?*anyopaque, options: process.SpawnOptions) return error.Unexpected; } -fn processSpawnPosix(userdata: ?*anyopaque, options: process.SpawnOptions) process.SpawnError!process.Child { - const t: *Threaded = @ptrCast(@alignCast(userdata)); +const Spawned = struct { + pid: posix.pid_t, + err_fd: posix.fd_t, + stdin: ?File, + stdout: ?File, + stderr: ?File, +}; +fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Spawned { // The child process does need to access (one end of) these pipes. However, // we must initially set CLOEXEC to avoid a race condition. If another thread // is racing to spawn a different child process, we don't want it to inherit @@ -12947,9 +12953,9 @@ fn processSpawnPosix(userdata: ?*anyopaque, options: process.SpawnOptions) proce } const pid: posix.pid_t = @intCast(pid_result); // We are the parent. + errdefer comptime unreachable; // The child is forked; we must not error from now on posix.close(err_pipe[1]); // make sure only the child holds the write end open - defer posix.close(err_pipe[0]); if (options.stdin == .pipe) posix.close(stdin_pipe[0]); if (options.stdout == .pipe) posix.close(stdout_pipe[1]); @@ -12959,8 +12965,31 @@ fn processSpawnPosix(userdata: ?*anyopaque, options: process.SpawnOptions) proce options.progress_node.setIpcFd(prog_pipe[0]); + return .{ + .pid = pid, + .err_fd = err_pipe[0], + .stdin = switch (options.stdin) { + .pipe => .{ .handle = stdin_pipe[1] }, + else => null, + }, + .stdout = switch (options.stdout) { + .pipe => .{ .handle = stdout_pipe[0] }, + else => null, + }, + .stderr = switch (options.stderr) { + .pipe => .{ .handle = stderr_pipe[0] }, + else => null, + }, + }; +} + +fn processSpawnPosix(userdata: ?*anyopaque, options: process.SpawnOptions) process.SpawnError!process.Child { + const t: *Threaded = @ptrCast(@alignCast(userdata)); + const spawned = try spawnPosix(t, options); + defer posix.close(spawned.err_fd); + // Wait for the child to report any errors in or before `execvpe`. - if (readIntFd(t, err_pipe[0])) |child_err_int| { + if (readIntFd(t, spawned.err_fd)) |child_err_int| { const child_err: process.SpawnError = @errorCast(@errorFromInt(child_err_int)); return child_err; } else |read_err| switch (read_err) { @@ -12976,20 +13005,11 @@ fn processSpawnPosix(userdata: ?*anyopaque, options: process.SpawnOptions) proce } return .{ - .id = pid, + .id = spawned.pid, .thread_handle = {}, - .stdin = switch (options.stdin) { - .pipe => .{ .handle = stdin_pipe[1] }, - else => null, - }, - .stdout = switch (options.stdout) { - .pipe => .{ .handle = stdout_pipe[0] }, - else => null, - }, - .stderr = switch (options.stderr) { - .pipe => .{ .handle = stderr_pipe[0] }, - else => null, - }, + .stdin = spawned.stdin, + .stdout = spawned.stdout, + .stderr = spawned.stderr, .request_resource_usage_statistics = options.request_resource_usage_statistics, }; } @@ -13156,10 +13176,13 @@ fn forkBail(fd: posix.fd_t, err: ForkBailError) noreturn { // it caused a deadlock instead of doing an exit syscall. In the words of Avril Lavigne, // "Why'd you have to go and make things so complicated?" if (builtin.link_libc) { - // The _exit(2) function does nothing but make the exit syscall, unlike exit(3) + // The `_exit` function does nothing but make the exit syscall, unlike `exit`. std.c._exit(1); + } else if (native_os == .linux and !builtin.single_threaded) { + std.os.linux.exit_group(1); + } else { + posix.system.exit(1); } - posix.system.exit(1); } fn writeIntFd(fd: posix.fd_t, value: ErrInt) !void { @@ -13190,7 +13213,7 @@ fn readIntFd(t: *Threaded, fd: posix.fd_t) !ErrInt { switch (posix.errno(rc)) { .SUCCESS => { const n: usize = @intCast(rc); - if (n == 0) return error.EndOfStream; + if (n == 0) break; i += n; continue; }, @@ -13198,6 +13221,7 @@ fn readIntFd(t: *Threaded, fd: posix.fd_t) !ErrInt { else => |err| return posix.unexpectedErrno(err), } } + if (buffer.len - i != 0) return error.EndOfStream; return @intCast(std.mem.readInt(u64, &buffer, .little)); } @@ -14371,9 +14395,9 @@ pub fn execvpeZ( file: [*:0]const u8, argv_ptr: [*:null]const ?[*:0]const u8, envp: [*:null]const ?[*:0]const u8, - optional_PATH: ?[]const u8, + PATH: []const u8, ) process.ReplaceError { - return execvpeZ_expandArg0(.no_expand, file, argv_ptr, envp, optional_PATH); + return execvpeZ_expandArg0(.no_expand, file, argv_ptr, envp, PATH); } fn windowsMakePipeIn(rd: *?windows.HANDLE, wr: *?windows.HANDLE, sattr: *const windows.SECURITY_ATTRIBUTES) !void { -- 2.54.0 From 1b56686012c49b34f395c82741b03587633aaa31 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 1 Jan 2026 23:52:26 -0800 Subject: [PATCH 24/60] test-standalone: update cases to new main API --- lib/compiler/resinator/main.zig | 32 ++++++++++++++++--- lib/compiler/translate-c/main.zig | 4 +-- test/standalone/coff_dwarf/main.zig | 11 ++----- test/standalone/dirname/exists_in.zig | 2 +- test/standalone/dirname/has_basename.zig | 2 +- test/standalone/dirname/touch.zig | 2 +- test/standalone/load_dynamic_library/main.zig | 7 ++-- test/standalone/posix/cwd.zig | 12 ++----- test/standalone/posix/relpaths.zig | 10 ++---- test/standalone/run_cwd/check_file_exists.zig | 9 ++---- 10 files changed, 45 insertions(+), 46 deletions(-) diff --git a/lib/compiler/resinator/main.zig b/lib/compiler/resinator/main.zig index 562fb865419d609f0e4f7ce39f4c178f99a8211a..ec5b6fcbc2e1ac3155548409320adf7f2c014630 100644 --- a/lib/compiler/resinator/main.zig +++ b/lib/compiler/resinator/main.zig @@ -24,7 +24,10 @@ pub fn main(init: std.process.Init.Minimal) !void { defer std.debug.assert(debug_allocator.deinit() == .ok); const gpa = debug_allocator.allocator(); - var threaded: std.Io.Threaded = .init(gpa, .{}); + var threaded: std.Io.Threaded = .init(gpa, .{ + .environ = init.environ, + .argv0 = .init(init.args), + }); defer threaded.deinit(); const io = threaded.io(); @@ -536,13 +539,24 @@ const LazyIncludePaths = struct { target_machine_type: std.coff.IMAGE.FILE.MACHINE, resolved_include_paths: ?[]const []const u8 = null, - pub fn get(self: *LazyIncludePaths, error_handler: *ErrorHandler) ![]const []const u8 { + pub fn get( + self: *LazyIncludePaths, + error_handler: *ErrorHandler, + env_map: *const std.process.Environ.Map, + ) ![]const []const u8 { const io = self.io; if (self.resolved_include_paths) |include_paths| return include_paths; - return getIncludePaths(self.arena, io, self.auto_includes_option, self.zig_lib_dir, self.target_machine_type) catch |err| switch (err) { + return getIncludePaths( + self.arena, + io, + self.auto_includes_option, + self.zig_lib_dir, + self.target_machine_type, + env_map, + ) catch |err| switch (err) { error.OutOfMemory => |e| return e, else => |e| { switch (e) { @@ -569,6 +583,7 @@ fn getIncludePaths( auto_includes_option: cli.Options.AutoIncludes, zig_lib_dir: []const u8, target_machine_type: std.coff.IMAGE.FILE.MACHINE, + env_map: *const std.process.Environ.Map, ) ![]const []const u8 { if (auto_includes_option == .none) return &[_][]const u8{}; @@ -641,7 +656,16 @@ fn getIncludePaths( }; const target = std.zig.resolveTargetQueryOrFatal(io, target_query); const is_native_abi = target_query.isNativeAbi(); - const detected_libc = std.zig.LibCDirs.detect(arena, io, zig_lib_dir, &target, is_native_abi, true, null) catch |err| switch (err) { + const detected_libc = std.zig.LibCDirs.detect( + arena, + io, + zig_lib_dir, + &target, + is_native_abi, + true, + null, + env_map, + ) catch |err| switch (err) { error.OutOfMemory => |e| return e, else => return error.MingwIncludesNotFound, }; diff --git a/lib/compiler/translate-c/main.zig b/lib/compiler/translate-c/main.zig index a257f65ce4cf690f2b23be5323f6815a65c33301..0c7f8c10a2feb71c609e02ed4a456e80732d87f3 100644 --- a/lib/compiler/translate-c/main.zig +++ b/lib/compiler/translate-c/main.zig @@ -25,8 +25,8 @@ pub fn main(init: std.process.Init) u8 { zig_integration = true; } - const NO_COLOR = std.zig.EnvVar.NO_COLOR.isSet(); - const CLICOLOR_FORCE = std.zig.EnvVar.CLICOLOR_FORCE.isSet(); + const NO_COLOR = std.zig.EnvVar.NO_COLOR.isSet(init.env_map); + const CLICOLOR_FORCE = std.zig.EnvVar.CLICOLOR_FORCE.isSet(init.env_map); var stderr_buf: [1024]u8 = undefined; var stderr = Io.File.stderr().writer(io, &stderr_buf); diff --git a/test/standalone/coff_dwarf/main.zig b/test/standalone/coff_dwarf/main.zig index 7e314d5b287cb781791474a12e599ff5d6b0b175..79caaeb95a2f569e7a0a4e62e35083216b49cdec 100644 --- a/test/standalone/coff_dwarf/main.zig +++ b/test/standalone/coff_dwarf/main.zig @@ -3,18 +3,13 @@ const fatal = std.process.fatal; extern fn add(a: u32, b: u32, addr: *usize) u32; -pub fn main() void { - var debug_alloc_inst: std.heap.DebugAllocator(.{}) = .init; - defer std.debug.assert(debug_alloc_inst.deinit() == .ok); - const gpa = debug_alloc_inst.allocator(); +pub fn main(init: std.process.Init) void { + const gpa = init.gpa; + const io = init.io; var di: std.debug.SelfInfo = .init; defer di.deinit(gpa); - var threaded: std.Io.Threaded = .init(gpa, .{}); - defer threaded.deinit(); - const io = threaded.io(); - var add_addr: usize = undefined; _ = add(1, 2, &add_addr); diff --git a/test/standalone/dirname/exists_in.zig b/test/standalone/dirname/exists_in.zig index 1b6dedc0f6e6201b2a5e754dec3964696ac4e80b..5900e76b574b1dd5d1628bc38e51133423b785cd 100644 --- a/test/standalone/dirname/exists_in.zig +++ b/test/standalone/dirname/exists_in.zig @@ -12,7 +12,7 @@ const std = @import("std"); pub fn main(init: std.process.Init) !void { - var args = try init.args.iterateAllocator(init.arena); + var args = try init.minimal.args.iterateAllocator(init.gpa); defer args.deinit(); _ = args.next() orelse unreachable; // skip binary name diff --git a/test/standalone/dirname/has_basename.zig b/test/standalone/dirname/has_basename.zig index c8b769d9172fa6fa2b35ea594904c969ad5965f3..e0e408a3a491446cc17ed72c64d958bd88b62ec0 100644 --- a/test/standalone/dirname/has_basename.zig +++ b/test/standalone/dirname/has_basename.zig @@ -14,7 +14,7 @@ const std = @import("std"); pub fn main(init: std.process.Init) !void { - var args = try init.args.iterateAllocator(init.arena); + var args = try init.minimal.args.iterateAllocator(init.gpa); defer args.deinit(); _ = args.next() orelse unreachable; // skip binary name diff --git a/test/standalone/dirname/touch.zig b/test/standalone/dirname/touch.zig index bd47a95089da61508ae1eef96ad888a2b14da185..6b0dad536e4661244c70a5ecfb5c41035aec8f17 100644 --- a/test/standalone/dirname/touch.zig +++ b/test/standalone/dirname/touch.zig @@ -9,7 +9,7 @@ const std = @import("std"); pub fn main(init: std.process.Init) !void { - var args = try init.args.iterateAllocator(init.arena); + var args = try init.minimal.args.iterateAllocator(init.gpa); defer args.deinit(); _ = args.next() orelse unreachable; // skip binary name diff --git a/test/standalone/load_dynamic_library/main.zig b/test/standalone/load_dynamic_library/main.zig index b9d2340d4c745450d362b754b360e7bb071d2807..ac64e67cdaddf18d0904f5ecff78306542930fef 100644 --- a/test/standalone/load_dynamic_library/main.zig +++ b/test/standalone/load_dynamic_library/main.zig @@ -1,10 +1,7 @@ const std = @import("std"); -pub fn main() !void { - var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init; - defer _ = gpa.deinit(); - const args = try std.process.argsAlloc(gpa.allocator()); - defer std.process.argsFree(gpa.allocator(), args); +pub fn main(init: std.process.Init) !void { + const args = try init.minimal.args.toSlice(init.arena.allocator()); const dynlib_name = args[1]; diff --git a/test/standalone/posix/cwd.zig b/test/standalone/posix/cwd.zig index 3bd1ac066ae48bf8592227ee05fee20dea78cb36..fd5ceae1ec6472fe8aaa41a67c5fb22fae297025 100644 --- a/test/standalone/posix/cwd.zig +++ b/test/standalone/posix/cwd.zig @@ -7,24 +7,16 @@ const assert = std.debug.assert; const path_max = std.fs.max_path_bytes; -pub fn main() !void { +pub fn main(init: std.process.Init) !void { switch (builtin.target.os.tag) { .wasi => return, // WASI doesn't support changing the working directory at all. .windows => return, // POSIX is not implemented by Windows else => {}, } - var debug_allocator: std.heap.DebugAllocator(.{}) = .{}; - defer assert(debug_allocator.deinit() == .ok); - const gpa = debug_allocator.allocator(); - - var threaded: std.Io.Threaded = .init(gpa, .{}); - defer threaded.deinit(); - const io = threaded.io(); - try test_chdir_self(); try test_chdir_absolute(); - try test_chdir_relative(gpa, io); + try test_chdir_relative(init.gpa, init.io); } // get current working directory and expect it to match given path diff --git a/test/standalone/posix/relpaths.zig b/test/standalone/posix/relpaths.zig index b5b2bb5f61876a75213f8b734379977e1259d21a..e31e497875d4d9b556a9680fcdc017b85f7e5b34 100644 --- a/test/standalone/posix/relpaths.zig +++ b/test/standalone/posix/relpaths.zig @@ -6,16 +6,10 @@ const builtin = @import("builtin"); const std = @import("std"); const Io = std.Io; -pub fn main() !void { +pub fn main(init: std.process.Init) !void { if (builtin.target.os.tag == .wasi) return; // Can link, but can't change into tmpDir - var debug_allocator: std.heap.DebugAllocator(.{}) = .init; - const gpa = debug_allocator.allocator(); - defer std.debug.assert(debug_allocator.deinit() == .ok); - - var threaded: std.Io.Threaded = .init(gpa, .{}); - defer threaded.deinit(); - const io = threaded.io(); + const io = init.io; var tmp = tmpDir(io, .{}); defer tmp.cleanup(io); diff --git a/test/standalone/run_cwd/check_file_exists.zig b/test/standalone/run_cwd/check_file_exists.zig index a885c7dafde7c501ba794ecbe4b377aa61eab705..bd4a26b6668cdd328b86ebe43fd56f280e59b0f7 100644 --- a/test/standalone/run_cwd/check_file_exists.zig +++ b/test/standalone/run_cwd/check_file_exists.zig @@ -1,9 +1,6 @@ -pub fn main() !void { - var arena_state: std.heap.ArenaAllocator = .init(std.heap.page_allocator); - defer arena_state.deinit(); - const arena = arena_state.allocator(); - - const args = try std.process.argsAlloc(arena); +pub fn main(init: std.process.Init) !void { + const arena = init.arena.allocator(); + const args = try init.minimal.args.toSlice(arena); if (args.len != 2) return error.BadUsage; const path = args[1]; -- 2.54.0 From bf74827ddb34e36906c5f7615f8b488d90e16be4 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 2 Jan 2026 17:57:54 -0800 Subject: [PATCH 25/60] test-standalone: update more cases to new main API --- lib/compiler/aro/aro/Compilation.zig | 21 +-- lib/compiler/aro/aro/Driver.zig | 40 +++-- lib/compiler/resinator/compile.zig | 5 +- lib/compiler/resinator/main.zig | 11 +- lib/compiler/resinator/preprocess.zig | 4 +- lib/compiler/translate-c/main.zig | 59 +++---- test/standalone/child_process/main.zig | 18 +- test/standalone/cmakedefine/check.zig | 12 +- test/standalone/empty_env/main.zig | 7 +- test/standalone/entry_point/check_differ.zig | 14 +- test/standalone/env_vars/main.zig | 165 ++++++++---------- test/standalone/posix/getenv.zig | 21 +-- test/standalone/run_output_caching/main.zig | 2 +- .../run_output_paths/create_file.zig | 2 +- .../self_exe_symlink/create-symlink.zig | 6 +- test/standalone/self_exe_symlink/main.zig | 11 +- test/standalone/simple/cat/main.zig | 2 +- test/standalone/simple/guess_number/main.zig | 17 +- test/standalone/windows_bat_args/fuzz.zig | 2 +- test/standalone/windows_bat_args/test.zig | 2 +- test/standalone/windows_paths/test.zig | 2 +- test/standalone/windows_spawn/main.zig | 2 +- tools/doctest.zig | 28 +-- tools/dump-cov.zig | 4 +- tools/fetch_them_macos_headers.zig | 4 +- tools/gen_macos_headers_c.zig | 2 +- tools/gen_outline_atomics.zig | 2 +- tools/gen_spirv_spec.zig | 98 +++++------ tools/gen_stubs.zig | 14 +- tools/generate_c_size_and_align_checks.zig | 2 +- tools/generate_linux_syscalls.zig | 2 +- tools/incr-check.zig | 46 ++--- tools/migrate_langref.zig | 15 +- tools/update-linux-headers.zig | 16 +- tools/update_clang_options.zig | 30 ++-- tools/update_cpu_features.zig | 6 +- tools/update_crc_catalog.zig | 16 +- tools/update_freebsd_libc.zig | 13 +- tools/update_glibc.zig | 13 +- tools/update_mingw.zig | 13 +- tools/update_netbsd_libc.zig | 13 +- 41 files changed, 346 insertions(+), 416 deletions(-) diff --git a/lib/compiler/aro/aro/Compilation.zig b/lib/compiler/aro/aro/Compilation.zig index aec780af02abd7f2c9611e7f08b0db14f8f36756..fc38a8b2bf2c970c281fb0fd9329d4101a3895ae 100644 --- a/lib/compiler/aro/aro/Compilation.zig +++ b/lib/compiler/aro/aro/Compilation.zig @@ -74,9 +74,7 @@ pub const Environment = struct { pub const default: @This() = .{ .provided = 0 }; }; - /// Load all of the environment variables using the std.process API. Do not use if using Aro as a shared library on Linux without libc - /// See https://github.com/ziglang/zig/issues/4524 - pub fn loadAll(allocator: std.mem.Allocator) !Environment { + pub fn loadAll(allocator: std.mem.Allocator, environ_map: *const std.process.Environ.Map) !Environment { var env: Environment = .{}; errdefer env.deinit(allocator); @@ -85,11 +83,7 @@ pub const Environment = struct { var env_var_buf: [field.name.len]u8 = undefined; const env_var_name = std.ascii.upperString(&env_var_buf, field.name); - const val: ?[]const u8 = std.process.getEnvVarOwned(allocator, env_var_name) catch |err| switch (err) { - error.OutOfMemory => |e| return e, - error.EnvironmentVariableNotFound => null, - error.InvalidWtf8 => null, - }; + const val: ?[]const u8 = if (environ_map.get(env_var_name)) |v| try allocator.dupe(u8, v) else null; @field(env, field.name) = val; } return env; @@ -193,13 +187,20 @@ pub fn init(gpa: Allocator, arena: Allocator, io: Io, diagnostics: *Diagnostics, /// Initialize Compilation with default environment, /// pragma handlers and emulation mode set to target. -pub fn initDefault(gpa: Allocator, arena: Allocator, io: Io, diagnostics: *Diagnostics, cwd: Io.Dir) !Compilation { +pub fn initDefault( + gpa: Allocator, + arena: Allocator, + io: Io, + diagnostics: *Diagnostics, + cwd: Io.Dir, + env_map: *const std.process.Environ.Map, +) !Compilation { var comp: Compilation = .{ .gpa = gpa, .arena = arena, .io = io, .diagnostics = diagnostics, - .environment = try Environment.loadAll(gpa), + .environment = try Environment.loadAll(gpa, env_map), .cwd = cwd, }; errdefer comp.deinit(); diff --git a/lib/compiler/aro/aro/Driver.zig b/lib/compiler/aro/aro/Driver.zig index 6ffaedc2e715577065dec636fb38db01d0e4546a..400a1ef6f67bf9980a1c2a56750017af10c85f26 100644 --- a/lib/compiler/aro/aro/Driver.zig +++ b/lib/compiler/aro/aro/Driver.zig @@ -1250,21 +1250,25 @@ fn getOutFileName(d: *Driver, source: Source, buf: *[std.fs.max_name_bytes]u8) ! } fn invokeAssembler(d: *Driver, tc: *Toolchain, input_path: []const u8, output_path: []const u8) !void { + const io = d.comp.io; var assembler_path_buf: [std.fs.max_path_bytes]u8 = undefined; const assembler_path = try tc.getAssemblerPath(&assembler_path_buf); const argv = [_][]const u8{ assembler_path, input_path, "-o", output_path }; - var child = std.process.Child.init(&argv, d.comp.gpa); - // TODO handle better - child.stdin_behavior = .inherit; - child.stdout_behavior = .inherit; - child.stderr_behavior = .inherit; - - const term = child.spawnAndWait() catch |er| { + var child = std.process.spawn(io, .{ + .argv = &argv, + // TODO handle better + .stdin = .inherit, + .stdout = .inherit, + .stderr = .inherit, + }) catch |er| { return d.fatal("unable to spawn linker: {s}", .{errorDescription(er)}); }; + const term = child.wait(io) catch |er| { + return d.fatal("unable to wait linker: {s}", .{errorDescription(er)}); + }; switch (term) { - .Exited => |code| if (code != 0) { + .exited => |code| if (code != 0) { const e = d.fatal("assembler exited with an error code", .{}); return e; }, @@ -1490,6 +1494,7 @@ fn dumpLinkerArgs(w: *std.Io.Writer, items: []const []const u8) !void { /// **MAY call `exit` if `fast_exit` is set.** pub fn invokeLinker(d: *Driver, tc: *Toolchain, comptime fast_exit: bool) Compilation.Error!void { const gpa = d.comp.gpa; + const io = d.comp.io; var argv: std.ArrayList([]const u8) = .empty; defer argv.deinit(gpa); @@ -1506,17 +1511,20 @@ pub fn invokeLinker(d: *Driver, tc: *Toolchain, comptime fast_exit: bool) Compil return d.fatal("unable to dump linker args: {s}", .{errorDescription(stdout.err.?)}); }; } - var child = std.process.Child.init(argv.items, d.comp.gpa); - // TODO handle better - child.stdin_behavior = .inherit; - child.stdout_behavior = .inherit; - child.stderr_behavior = .inherit; - - const term = child.spawnAndWait() catch |er| { + var child = std.process.spawn(io, .{ + .argv = argv.items, + // TODO handle better + .stdin = .inherit, + .stdout = .inherit, + .stderr = .inherit, + }) catch |er| { return d.fatal("unable to spawn linker: {s}", .{errorDescription(er)}); }; + const term = child.wait(io) catch |er| { + return d.fatal("unable to wait linker: {s}", .{errorDescription(er)}); + }; switch (term) { - .Exited => |code| if (code != 0) { + .exited => |code| if (code != 0) { const e = d.fatal("linker exited with an error code", .{}); if (fast_exit) d.exitWithCleanup(code); return e; diff --git a/lib/compiler/resinator/compile.zig b/lib/compiler/resinator/compile.zig index 4f75fca18a4287ae96003db378f68f8c83f40c7d..76d6a1463608f3c4f3c7fe80d5bae6d1e2c39f25 100644 --- a/lib/compiler/resinator/compile.zig +++ b/lib/compiler/resinator/compile.zig @@ -80,7 +80,7 @@ pub const Dependencies = struct { } }; -pub fn compile(allocator: Allocator, io: Io, source: []const u8, writer: *std.Io.Writer, options: CompileOptions) !void { +pub fn compile(allocator: Allocator, io: Io, source: []const u8, writer: *std.Io.Writer, options: CompileOptions, env_map: *const std.process.Environ.Map) !void { var lexer = lex.Lexer.init(source, .{ .default_code_page = options.default_code_page, .source_mappings = options.source_mappings, @@ -148,8 +148,7 @@ pub fn compile(allocator: Allocator, io: Io, source: []const u8, writer: *std.Io try search_dirs.append(allocator, .{ .dir = dir, .path = try allocator.dupe(u8, system_include_path) }); } if (!options.ignore_include_env_var) { - const INCLUDE = std.process.getEnvVarOwned(allocator, "INCLUDE") catch ""; - defer allocator.free(INCLUDE); + const INCLUDE = env_map.get("INCLUDE") orelse ""; // The only precedence here is llvm-rc which also uses the platform-specific // delimiter. There's no precedence set by `rc.exe` since it's Windows-only. diff --git a/lib/compiler/resinator/main.zig b/lib/compiler/resinator/main.zig index ec5b6fcbc2e1ac3155548409320adf7f2c014630..58f539274caf3be7b4daab0486c13ef151f0b697 100644 --- a/lib/compiler/resinator/main.zig +++ b/lib/compiler/resinator/main.zig @@ -24,6 +24,9 @@ pub fn main(init: std.process.Init.Minimal) !void { defer std.debug.assert(debug_allocator.deinit() == .ok); const gpa = debug_allocator.allocator(); + var env_map = try init.environ.createMap(gpa); + defer env_map.deinit(); + var threaded: std.Io.Threaded = .init(gpa, .{ .environ = init.environ, .argv0 = .init(init.args), @@ -148,8 +151,8 @@ pub fn main(init: std.process.Init.Minimal) !void { defer argv.deinit(aro_arena); try argv.append(aro_arena, "arocc"); // dummy command name - const resolved_include_paths = try include_paths.get(&error_handler); - try preprocess.appendAroArgs(aro_arena, &argv, options, resolved_include_paths); + const resolved_include_paths = try include_paths.get(&error_handler, &env_map); + try preprocess.appendAroArgs(aro_arena, &argv, options, resolved_include_paths, &env_map); try argv.append(aro_arena, switch (options.input_source) { .stdio => "-", .filename => |filename| filename, @@ -283,7 +286,7 @@ pub fn main(init: std.process.Init.Minimal) !void { .dependencies = maybe_dependencies, .ignore_include_env_var = options.ignore_include_env_var, .extra_include_paths = options.extra_include_paths.items, - .system_include_paths = try include_paths.get(&error_handler), + .system_include_paths = try include_paths.get(&error_handler, &env_map), .default_language_id = options.default_language_id, .default_code_page = default_code_page, .disjoint_code_page = has_disjoint_code_page, @@ -292,7 +295,7 @@ pub fn main(init: std.process.Init.Minimal) !void { .max_string_literal_codepoints = options.max_string_literal_codepoints, .silent_duplicate_control_ids = options.silent_duplicate_control_ids, .warn_instead_of_error_on_invalid_code_page = options.warn_instead_of_error_on_invalid_code_page, - }) catch |err| switch (err) { + }, &env_map) catch |err| switch (err) { error.ParseError, error.CompileError => { try error_handler.emitDiagnostics(gpa, Io.Dir.cwd(), final_input, &diagnostics, mapping_results.mappings); // Delete the output file on error diff --git a/lib/compiler/resinator/preprocess.zig b/lib/compiler/resinator/preprocess.zig index a67721a7e1c62863ef36f0b725333386d4789847..39e01f2f4120986dd7f68a24cb35f737a71074af 100644 --- a/lib/compiler/resinator/preprocess.zig +++ b/lib/compiler/resinator/preprocess.zig @@ -86,7 +86,7 @@ fn hasAnyErrors(comp: *aro.Compilation) bool { /// `arena` is used for temporary -D argument strings and the INCLUDE environment variable. /// The arena should be kept alive at least as long as `argv`. -pub fn appendAroArgs(arena: Allocator, argv: *std.ArrayList([]const u8), options: cli.Options, system_include_paths: []const []const u8) !void { +pub fn appendAroArgs(arena: Allocator, argv: *std.ArrayList([]const u8), options: cli.Options, system_include_paths: []const []const u8, env_map: *const std.process.Environ.Map) !void { try argv.appendSlice(arena, &.{ "-E", "--comments", @@ -109,7 +109,7 @@ pub fn appendAroArgs(arena: Allocator, argv: *std.ArrayList([]const u8), options } if (!options.ignore_include_env_var) { - const INCLUDE = std.process.getEnvVarOwned(arena, "INCLUDE") catch ""; + const INCLUDE = env_map.get("INCLUDE") orelse ""; // The only precedence here is llvm-rc which also uses the platform-specific // delimiter. There's no precedence set by `rc.exe` since it's Windows-only. diff --git a/lib/compiler/translate-c/main.zig b/lib/compiler/translate-c/main.zig index 0c7f8c10a2feb71c609e02ed4a456e80732d87f3..d85c89a62783675bbb3d0ba2901b4b2e2c0945e1 100644 --- a/lib/compiler/translate-c/main.zig +++ b/lib/compiler/translate-c/main.zig @@ -13,6 +13,7 @@ pub fn main(init: std.process.Init) u8 { const gpa = init.gpa; const arena = init.arena.allocator(); const io = init.io; + const env_map = init.env_map; const args = init.minimal.args.toSlice(arena) catch { std.debug.print("ran out of memory allocating arguments\n", .{}); @@ -25,8 +26,8 @@ pub fn main(init: std.process.Init) u8 { zig_integration = true; } - const NO_COLOR = std.zig.EnvVar.NO_COLOR.isSet(init.env_map); - const CLICOLOR_FORCE = std.zig.EnvVar.CLICOLOR_FORCE.isSet(init.env_map); + const NO_COLOR = std.zig.EnvVar.NO_COLOR.isSet(env_map); + const CLICOLOR_FORCE = std.zig.EnvVar.CLICOLOR_FORCE.isSet(env_map); var stderr_buf: [1024]u8 = undefined; var stderr = Io.File.stderr().writer(io, &stderr_buf); @@ -41,7 +42,7 @@ pub fn main(init: std.process.Init) u8 { }; defer diagnostics.deinit(); - var comp = aro.Compilation.initDefault(gpa, arena, io, &diagnostics, Io.Dir.cwd()) catch |err| switch (err) { + var comp = aro.Compilation.initDefault(gpa, arena, io, &diagnostics, .cwd(), env_map) catch |err| switch (err) { error.OutOfMemory => { std.debug.print("ran out of memory initializing C compilation\n", .{}); if (fast_exit) process.exit(1); @@ -114,43 +115,41 @@ pub const usage = \\ ; -fn translate(d: *aro.Driver, tc: *aro.Toolchain, args: [][:0]u8, zig_integration: bool) !void { +fn translate(d: *aro.Driver, tc: *aro.Toolchain, args: []const [:0]const u8, zig_integration: bool) !void { const gpa = d.comp.gpa; const io = d.comp.io; - const aro_args = args: { - var i: usize = 0; - for (args) |arg| { - args[i] = arg; - if (mem.eql(u8, arg, "--help")) { - var stdout_buf: [512]u8 = undefined; - var stdout = Io.File.stdout().writer(io, &stdout_buf); - try stdout.interface.print(usage, .{args[0]}); - try stdout.interface.flush(); - return; - } else if (mem.eql(u8, arg, "--version")) { - var stdout_buf: [512]u8 = undefined; - var stdout = Io.File.stdout().writer(io, &stdout_buf); - // TODO add version - try stdout.interface.writeAll("0.0.0-dev\n"); - try stdout.interface.flush(); - return; - } else if (mem.eql(u8, arg, "--zig-integration")) { - if (i != 1 or !zig_integration) - return d.fatal("--zig-integration must be the first argument", .{}); - } else { - i += 1; - } + var aro_args: std.ArrayList([:0]const u8) = .empty; + defer aro_args.deinit(gpa); + + for (args, 0..) |arg, i| { + if (mem.eql(u8, arg, "--help")) { + var stdout_buf: [512]u8 = undefined; + var stdout = Io.File.stdout().writer(io, &stdout_buf); + try stdout.interface.print(usage, .{args[0]}); + try stdout.interface.flush(); + return; + } else if (mem.eql(u8, arg, "--version")) { + var stdout_buf: [512]u8 = undefined; + var stdout = Io.File.stdout().writer(io, &stdout_buf); + // TODO add version + try stdout.interface.writeAll("0.0.0-dev\n"); + try stdout.interface.flush(); + return; + } else if (mem.eql(u8, arg, "--zig-integration")) { + if (i != 1 or !zig_integration) + return d.fatal("--zig-integration must be the first argument", .{}); + } else { + try aro_args.append(gpa, arg); } - break :args args[0..i]; - }; + } const user_macros = macros: { var macro_buf: std.ArrayList(u8) = .empty; defer macro_buf.deinit(gpa); var discard_buf: [256]u8 = undefined; var discarding: std.Io.Writer.Discarding = .init(&discard_buf); - assert(!try d.parseArgs(&discarding.writer, ¯o_buf, aro_args)); + assert(!try d.parseArgs(&discarding.writer, ¯o_buf, aro_args.items)); if (macro_buf.items.len > std.math.maxInt(u32)) { return d.fatal("user provided macro source exceeded max size", .{}); } diff --git a/test/standalone/child_process/main.zig b/test/standalone/child_process/main.zig index c2ae038f5ad5558ed40b241f4a2cc96dc054693e..ef933c0abdea75cdd2df46943aa58a43cc9b91bc 100644 --- a/test/standalone/child_process/main.zig +++ b/test/standalone/child_process/main.zig @@ -9,7 +9,7 @@ pub fn main(init: std.process.Init.Minimal) !void { }; const gpa = gpa_state.allocator(); - var it = try init.iterateAllocator(gpa); + var it = try init.args.iterateAllocator(gpa); defer it.deinit(); _ = it.next() orelse unreachable; // skip binary name const child_path, const needs_free = child_path: { @@ -28,11 +28,13 @@ pub fn main(init: std.process.Init.Minimal) !void { defer threaded.deinit(); const io = threaded.io(); - var child = std.process.Child.init(&.{ child_path, "hello arg" }, gpa); - child.stdin_behavior = .pipe; - child.stdout_behavior = .pipe; - child.stderr_behavior = .inherit; - try child.spawn(io); + var child = try std.process.spawn(.{ + .argv = &.{ child_path, "hello arg" }, + .stdin = .pipe, + .stdout = .pipe, + .stderr = .inherit, + }); + const child_stdin = child.stdin.?; try child_stdin.writeStreamingAll(io, "hello from stdin"); // verified in child child_stdin.close(io); @@ -47,7 +49,7 @@ pub fn main(init: std.process.Init.Minimal) !void { } switch (try child.wait(io)) { - .Exited => |code| { + .exited => |code| { const child_ok_code = 42; // set by child if no test errors if (code != child_ok_code) { testError(io, "child exit code: {d}; want {d}", .{ code, child_ok_code }); @@ -60,7 +62,7 @@ pub fn main(init: std.process.Init.Minimal) !void { // Check that FileNotFound is consistent across platforms when trying to spawn an executable that doesn't exist const missing_child_path = try std.mem.concat(gpa, u8, &.{ child_path, "_intentionally_missing" }); defer gpa.free(missing_child_path); - try std.testing.expectError(error.FileNotFound, std.process.Child.run(gpa, io, .{ .argv = &.{missing_child_path} })); + try std.testing.expectError(error.FileNotFound, std.process.run(gpa, io, .{ .argv = &.{missing_child_path} })); } var parent_test_error = false; diff --git a/test/standalone/cmakedefine/check.zig b/test/standalone/cmakedefine/check.zig index c2f89ad1127ccba3f663886e84d085cab2f7cb26..8db602f39d6db5f20bb016ade3853b1bf78b7ec3 100644 --- a/test/standalone/cmakedefine/check.zig +++ b/test/standalone/cmakedefine/check.zig @@ -1,16 +1,12 @@ -pub fn main() !void { - var arena_state: std.heap.ArenaAllocator = .init(std.heap.page_allocator); - defer arena_state.deinit(); - const arena = arena_state.allocator(); - - const args = try std.process.argsAlloc(arena); +pub fn main(init: std.process.Init) !void { + const arena = init.arena.allocator(); + const io = init.io; + const args = try init.minimal.args.toSlice(arena); if (args.len != 3) return error.BadUsage; const actual_path = args[1]; const expected_path = args[2]; - const io = std.Io.Threaded.global_single_threaded.ioBasic(); - const actual = try std.Io.Dir.cwd().readFileAlloc(io, actual_path, arena, .limited(1024 * 1024)); const expected = try std.Io.Dir.cwd().readFileAlloc(io, expected_path, arena, .limited(1024 * 1024)); diff --git a/test/standalone/empty_env/main.zig b/test/standalone/empty_env/main.zig index 1dc435d9fa90d3e9b046d79224617d887a2ac31d..7a47dd6d3e804390308e4f1941d5ec26fdbe8537 100644 --- a/test/standalone/empty_env/main.zig +++ b/test/standalone/empty_env/main.zig @@ -1,8 +1,5 @@ const std = @import("std"); -pub fn main() !void { - var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init; - defer _ = gpa.deinit(); - const env_map = std.process.getEnvMap(gpa.allocator()) catch @panic("unable to get env map"); - try std.testing.expect(env_map.count() == 0); +pub fn main(init: std.process.Init) !void { + try std.testing.expect(init.env_map.count() == 0); } diff --git a/test/standalone/entry_point/check_differ.zig b/test/standalone/entry_point/check_differ.zig index 29b333632fcd0f7d823f4b1caf501a2b7e7ca06d..be761d46ed0370f505bb9e86c1ef31324b2234e1 100644 --- a/test/standalone/entry_point/check_differ.zig +++ b/test/standalone/entry_point/check_differ.zig @@ -1,13 +1,12 @@ -pub fn main() !void { - var arena_state: std.heap.ArenaAllocator = .init(std.heap.page_allocator); - defer arena_state.deinit(); - const arena = arena_state.allocator(); +const std = @import("std"); + +pub fn main(init: std.process.Init) !void { + const arena = init.arena.allocator(); + const io = init.io; + const args = try init.minimal.args.toSlice(arena); - const args = try std.process.argsAlloc(arena); if (args.len != 3) return error.BadUsage; // usage: 'check_differ ' - const io = std.Io.Threaded.global_single_threaded.ioBasic(); - const contents_1 = try std.Io.Dir.cwd().readFileAlloc(io, args[1], arena, .limited(1024 * 1024 * 64)); // 64 MiB ought to be plenty const contents_2 = try std.Io.Dir.cwd().readFileAlloc(io, args[2], arena, .limited(1024 * 1024 * 64)); // 64 MiB ought to be plenty @@ -16,4 +15,3 @@ pub fn main() !void { } // success, files differ } -const std = @import("std"); diff --git a/test/standalone/env_vars/main.zig b/test/standalone/env_vars/main.zig index 9069f93f42fb48d4fad5f229b385252fe121f563..112c2a831296b13e3315c37c0f33755338fc21d0 100644 --- a/test/standalone/env_vars/main.zig +++ b/test/standalone/env_vars/main.zig @@ -7,145 +7,126 @@ pub fn main(init: std.process.Init) !void { const allocator = init.gpa; const arena = init.arena.allocator(); + const environ = init.minimal.environ; - // hasNonEmptyEnvVar + // containsUnempty { - try std.testing.expect(try std.process.hasNonEmptyEnvVar(allocator, "FOO")); - try std.testing.expect(!(try std.process.hasNonEmptyEnvVar(allocator, "FOO="))); - try std.testing.expect(!(try std.process.hasNonEmptyEnvVar(allocator, "FO"))); - try std.testing.expect(!(try std.process.hasNonEmptyEnvVar(allocator, "FOOO"))); + try std.testing.expect(try environ.containsUnempty(allocator, "FOO")); + try std.testing.expect(!(try environ.containsUnempty(allocator, "FOO="))); + try std.testing.expect(!(try environ.containsUnempty(allocator, "FO"))); + try std.testing.expect(!(try environ.containsUnempty(allocator, "FOOO"))); if (builtin.os.tag == .windows) { - try std.testing.expect(try std.process.hasNonEmptyEnvVar(allocator, "foo")); + try std.testing.expect(try environ.containsUnempty(allocator, "foo")); } - try std.testing.expect(try std.process.hasNonEmptyEnvVar(allocator, "EQUALS")); - try std.testing.expect(!(try std.process.hasNonEmptyEnvVar(allocator, "EQUALS=ABC"))); - try std.testing.expect(try std.process.hasNonEmptyEnvVar(allocator, "КИРиллИЦА")); + try std.testing.expect(try environ.containsUnempty(allocator, "EQUALS")); + try std.testing.expect(!(try environ.containsUnempty(allocator, "EQUALS=ABC"))); + try std.testing.expect(try environ.containsUnempty(allocator, "КИРиллИЦА")); if (builtin.os.tag == .windows) { - try std.testing.expect(try std.process.hasNonEmptyEnvVar(allocator, "кирИЛЛица")); + try std.testing.expect(try environ.containsUnempty(allocator, "кирИЛЛица")); } - try std.testing.expect(!(try std.process.hasNonEmptyEnvVar(allocator, "NO_VALUE"))); - try std.testing.expect(!(try std.process.hasNonEmptyEnvVar(allocator, "NOT_SET"))); + try std.testing.expect(!(try environ.containsUnempty(allocator, "NO_VALUE"))); + try std.testing.expect(!(try environ.containsUnempty(allocator, "NOT_SET"))); if (builtin.os.tag == .windows) { - try std.testing.expect(try std.process.hasNonEmptyEnvVar(allocator, "=HIDDEN")); - try std.testing.expect(try std.process.hasNonEmptyEnvVar(allocator, "INVALID_UTF16_\xed\xa0\x80")); + try std.testing.expect(try environ.containsUnempty(allocator, "=HIDDEN")); + try std.testing.expect(try environ.containsUnempty(allocator, "INVALID_UTF16_\xed\xa0\x80")); } } - // hasNonEmptyEnvVarContstant + // containsUnemptyConstant { - try std.testing.expect(std.process.hasNonEmptyEnvVarConstant("FOO")); - try std.testing.expect(!std.process.hasNonEmptyEnvVarConstant("FOO=")); - try std.testing.expect(!std.process.hasNonEmptyEnvVarConstant("FO")); - try std.testing.expect(!std.process.hasNonEmptyEnvVarConstant("FOOO")); + try std.testing.expect(environ.containsUnemptyConstant("FOO")); + try std.testing.expect(!environ.containsUnemptyConstant("FOO=")); + try std.testing.expect(!environ.containsUnemptyConstant("FO")); + try std.testing.expect(!environ.containsUnemptyConstant("FOOO")); if (builtin.os.tag == .windows) { - try std.testing.expect(std.process.hasNonEmptyEnvVarConstant("foo")); + try std.testing.expect(environ.containsUnemptyConstant("foo")); } - try std.testing.expect(std.process.hasNonEmptyEnvVarConstant("EQUALS")); - try std.testing.expect(!std.process.hasNonEmptyEnvVarConstant("EQUALS=ABC")); - try std.testing.expect(std.process.hasNonEmptyEnvVarConstant("КИРиллИЦА")); + try std.testing.expect(environ.containsUnemptyConstant("EQUALS")); + try std.testing.expect(!environ.containsUnemptyConstant("EQUALS=ABC")); + try std.testing.expect(environ.containsUnemptyConstant("КИРиллИЦА")); if (builtin.os.tag == .windows) { - try std.testing.expect(std.process.hasNonEmptyEnvVarConstant("кирИЛЛица")); + try std.testing.expect(environ.containsUnemptyConstant("кирИЛЛица")); } - try std.testing.expect(!(std.process.hasNonEmptyEnvVarConstant("NO_VALUE"))); - try std.testing.expect(!(std.process.hasNonEmptyEnvVarConstant("NOT_SET"))); + try std.testing.expect(!(environ.containsUnemptyConstant("NO_VALUE"))); + try std.testing.expect(!(environ.containsUnemptyConstant("NOT_SET"))); if (builtin.os.tag == .windows) { - try std.testing.expect(std.process.hasNonEmptyEnvVarConstant("=HIDDEN")); - try std.testing.expect(std.process.hasNonEmptyEnvVarConstant("INVALID_UTF16_\xed\xa0\x80")); + try std.testing.expect(environ.containsUnemptyConstant("=HIDDEN")); + try std.testing.expect(environ.containsUnemptyConstant("INVALID_UTF16_\xed\xa0\x80")); } } - // hasEnvVar + // contains { - try std.testing.expect(try std.process.hasEnvVar(allocator, "FOO")); - try std.testing.expect(!(try std.process.hasEnvVar(allocator, "FOO="))); - try std.testing.expect(!(try std.process.hasEnvVar(allocator, "FO"))); - try std.testing.expect(!(try std.process.hasEnvVar(allocator, "FOOO"))); + try std.testing.expect(try environ.contains(allocator, "FOO")); + try std.testing.expect(!(try environ.contains(allocator, "FOO="))); + try std.testing.expect(!(try environ.contains(allocator, "FO"))); + try std.testing.expect(!(try environ.contains(allocator, "FOOO"))); if (builtin.os.tag == .windows) { - try std.testing.expect(try std.process.hasEnvVar(allocator, "foo")); + try std.testing.expect(try environ.contains(allocator, "foo")); } - try std.testing.expect(try std.process.hasEnvVar(allocator, "EQUALS")); - try std.testing.expect(!(try std.process.hasEnvVar(allocator, "EQUALS=ABC"))); - try std.testing.expect(try std.process.hasEnvVar(allocator, "КИРиллИЦА")); + try std.testing.expect(try environ.contains(allocator, "EQUALS")); + try std.testing.expect(!(try environ.contains(allocator, "EQUALS=ABC"))); + try std.testing.expect(try environ.contains(allocator, "КИРиллИЦА")); if (builtin.os.tag == .windows) { - try std.testing.expect(try std.process.hasEnvVar(allocator, "кирИЛЛица")); + try std.testing.expect(try environ.contains(allocator, "кирИЛЛица")); } - try std.testing.expect(try std.process.hasEnvVar(allocator, "NO_VALUE")); - try std.testing.expect(!(try std.process.hasEnvVar(allocator, "NOT_SET"))); + try std.testing.expect(try environ.contains(allocator, "NO_VALUE")); + try std.testing.expect(!(try environ.contains(allocator, "NOT_SET"))); if (builtin.os.tag == .windows) { - try std.testing.expect(try std.process.hasEnvVar(allocator, "=HIDDEN")); - try std.testing.expect(try std.process.hasEnvVar(allocator, "INVALID_UTF16_\xed\xa0\x80")); + try std.testing.expect(try environ.contains(allocator, "=HIDDEN")); + try std.testing.expect(try environ.contains(allocator, "INVALID_UTF16_\xed\xa0\x80")); } } - // hasEnvVarConstant + // containsConstant { - try std.testing.expect(std.process.hasEnvVarConstant("FOO")); - try std.testing.expect(!std.process.hasEnvVarConstant("FOO=")); - try std.testing.expect(!std.process.hasEnvVarConstant("FO")); - try std.testing.expect(!std.process.hasEnvVarConstant("FOOO")); + try std.testing.expect(environ.containsConstant("FOO")); + try std.testing.expect(!environ.containsConstant("FOO=")); + try std.testing.expect(!environ.containsConstant("FO")); + try std.testing.expect(!environ.containsConstant("FOOO")); if (builtin.os.tag == .windows) { - try std.testing.expect(std.process.hasEnvVarConstant("foo")); + try std.testing.expect(environ.containsConstant("foo")); } - try std.testing.expect(std.process.hasEnvVarConstant("EQUALS")); - try std.testing.expect(!std.process.hasEnvVarConstant("EQUALS=ABC")); - try std.testing.expect(std.process.hasEnvVarConstant("КИРиллИЦА")); + try std.testing.expect(environ.containsConstant("EQUALS")); + try std.testing.expect(!environ.containsConstant("EQUALS=ABC")); + try std.testing.expect(environ.containsConstant("КИРиллИЦА")); if (builtin.os.tag == .windows) { - try std.testing.expect(std.process.hasEnvVarConstant("кирИЛЛица")); + try std.testing.expect(environ.containsConstant("кирИЛЛица")); } - try std.testing.expect(std.process.hasEnvVarConstant("NO_VALUE")); - try std.testing.expect(!(std.process.hasEnvVarConstant("NOT_SET"))); + try std.testing.expect(environ.containsConstant("NO_VALUE")); + try std.testing.expect(!(environ.containsConstant("NOT_SET"))); if (builtin.os.tag == .windows) { - try std.testing.expect(std.process.hasEnvVarConstant("=HIDDEN")); - try std.testing.expect(std.process.hasEnvVarConstant("INVALID_UTF16_\xed\xa0\x80")); + try std.testing.expect(environ.containsConstant("=HIDDEN")); + try std.testing.expect(environ.containsConstant("INVALID_UTF16_\xed\xa0\x80")); } } - // getEnvVarOwned + // getAlloc { - try std.testing.expectEqualSlices(u8, "123", try std.process.getEnvVarOwned(arena, "FOO")); - try std.testing.expectError(error.EnvironmentVariableNotFound, std.process.getEnvVarOwned(arena, "FOO=")); - try std.testing.expectError(error.EnvironmentVariableNotFound, std.process.getEnvVarOwned(arena, "FO")); - try std.testing.expectError(error.EnvironmentVariableNotFound, std.process.getEnvVarOwned(arena, "FOOO")); + try std.testing.expectEqualSlices(u8, "123", try environ.getAlloc(arena, "FOO")); + try std.testing.expectError(error.EnvironmentVariableNotFound, environ.getAlloc(arena, "FOO=")); + try std.testing.expectError(error.EnvironmentVariableNotFound, environ.getAlloc(arena, "FO")); + try std.testing.expectError(error.EnvironmentVariableNotFound, environ.getAlloc(arena, "FOOO")); if (builtin.os.tag == .windows) { - try std.testing.expectEqualSlices(u8, "123", try std.process.getEnvVarOwned(arena, "foo")); + try std.testing.expectEqualSlices(u8, "123", try environ.getAlloc(arena, "foo")); } - try std.testing.expectEqualSlices(u8, "ABC=123", try std.process.getEnvVarOwned(arena, "EQUALS")); - try std.testing.expectError(error.EnvironmentVariableNotFound, std.process.getEnvVarOwned(arena, "EQUALS=ABC")); - try std.testing.expectEqualSlices(u8, "non-ascii አማርኛ \u{10FFFF}", try std.process.getEnvVarOwned(arena, "КИРиллИЦА")); + try std.testing.expectEqualSlices(u8, "ABC=123", try environ.getAlloc(arena, "EQUALS")); + try std.testing.expectError(error.EnvironmentVariableNotFound, environ.getAlloc(arena, "EQUALS=ABC")); + try std.testing.expectEqualSlices(u8, "non-ascii አማርኛ \u{10FFFF}", try environ.getAlloc(arena, "КИРиллИЦА")); if (builtin.os.tag == .windows) { - try std.testing.expectEqualSlices(u8, "non-ascii አማርኛ \u{10FFFF}", try std.process.getEnvVarOwned(arena, "кирИЛЛица")); + try std.testing.expectEqualSlices(u8, "non-ascii አማርኛ \u{10FFFF}", try environ.getAlloc(arena, "кирИЛЛица")); } - try std.testing.expectEqualSlices(u8, "", try std.process.getEnvVarOwned(arena, "NO_VALUE")); - try std.testing.expectError(error.EnvironmentVariableNotFound, std.process.getEnvVarOwned(arena, "NOT_SET")); + try std.testing.expectEqualSlices(u8, "", try environ.getAlloc(arena, "NO_VALUE")); + try std.testing.expectError(error.EnvironmentVariableNotFound, environ.getAlloc(arena, "NOT_SET")); if (builtin.os.tag == .windows) { - try std.testing.expectEqualSlices(u8, "hi", try std.process.getEnvVarOwned(arena, "=HIDDEN")); - try std.testing.expectEqualSlices(u8, "\xed\xa0\x80", try std.process.getEnvVarOwned(arena, "INVALID_UTF16_\xed\xa0\x80")); + try std.testing.expectEqualSlices(u8, "hi", try environ.getAlloc(arena, "=HIDDEN")); + try std.testing.expectEqualSlices(u8, "\xed\xa0\x80", try environ.getAlloc(arena, "INVALID_UTF16_\xed\xa0\x80")); } } - // parseEnvVarInt + // Environ.Map { - try std.testing.expectEqual(123, try std.process.parseEnvVarInt("FOO", u32, 10)); - try std.testing.expectError(error.EnvironmentVariableNotFound, std.process.parseEnvVarInt("FO", u32, 10)); - try std.testing.expectError(error.EnvironmentVariableNotFound, std.process.parseEnvVarInt("FOOO", u32, 10)); - try std.testing.expectEqual(0x123, try std.process.parseEnvVarInt("FOO", u32, 16)); - if (builtin.os.tag == .windows) { - try std.testing.expectEqual(123, try std.process.parseEnvVarInt("foo", u32, 10)); - } - try std.testing.expectError(error.InvalidCharacter, std.process.parseEnvVarInt("EQUALS", u32, 10)); - try std.testing.expectError(error.EnvironmentVariableNotFound, std.process.parseEnvVarInt("EQUALS=ABC", u32, 10)); - try std.testing.expectError(error.InvalidCharacter, std.process.parseEnvVarInt("КИРиллИЦА", u32, 10)); - try std.testing.expectError(error.InvalidCharacter, std.process.parseEnvVarInt("NO_VALUE", u32, 10)); - try std.testing.expectError(error.EnvironmentVariableNotFound, std.process.parseEnvVarInt("NOT_SET", u32, 10)); - if (builtin.os.tag == .windows) { - try std.testing.expectError(error.InvalidCharacter, std.process.parseEnvVarInt("=HIDDEN", u32, 10)); - try std.testing.expectError(error.InvalidCharacter, std.process.parseEnvVarInt("INVALID_UTF16_\xed\xa0\x80", u32, 10)); - } - } - - // EnvMap - { - var env_map = try std.process.getEnvMap(allocator); + var env_map = try environ.createMap(allocator); defer env_map.deinit(); try std.testing.expectEqualSlices(u8, "123", env_map.get("FOO").?); diff --git a/test/standalone/posix/getenv.zig b/test/standalone/posix/getenv.zig index 8b28d09ae76d0d91cf60a41a6f0a4d6a94083378..ea4f9c4877cb5f3fd2fe8715300720e70e3743b3 100644 --- a/test/standalone/posix/getenv.zig +++ b/test/standalone/posix/getenv.zig @@ -1,9 +1,9 @@ -// test getting environment variables +//! test getting environment variables const std = @import("std"); const builtin = @import("builtin"); -pub fn main() !void { +pub fn main(init: std.process.Init.Minimal) !void { if (builtin.target.os.tag == .windows) { return; // Windows env strings are WTF-16, so not supported by Zig's std.posix.getenv() } @@ -12,21 +12,22 @@ pub fn main() !void { return; // std.posix.getenv is not supported on WASI due to the need of allocation } + const environ = init.environ; + // Test some unset env vars: - - try std.testing.expectEqual(std.posix.getenv(""), null); - try std.testing.expectEqual(std.posix.getenv("BOGUSDOESNOTEXISTENVVAR"), null); - try std.testing.expectEqual(std.posix.getenvZ("BOGUSDOESNOTEXISTENVVAR"), null); + try std.testing.expectEqual(environ.getPosix(""), null); + try std.testing.expectEqual(environ.getPosix("BOGUSDOESNOTEXISTENVVAR"), null); + try std.testing.expectEqual(environ.getPosix("BOGUSDOESNOTEXISTENVVAR"), null); if (builtin.link_libc) { // Test if USER matches what C library sees const expected = std.mem.span(std.c.getenv("USER") orelse ""); - const actual = std.posix.getenv("USER") orelse ""; + const actual = environ.getPosix("USER") orelse ""; try std.testing.expectEqualStrings(expected, actual); } // env vars set by our build.zig run step: - try std.testing.expectEqualStrings("", std.posix.getenv("ZIG_TEST_POSIX_EMPTY") orelse "invalid"); - try std.testing.expectEqualStrings("test=variable", std.posix.getenv("ZIG_TEST_POSIX_1EQ") orelse "invalid"); - try std.testing.expectEqualStrings("=test=variable=", std.posix.getenv("ZIG_TEST_POSIX_3EQ") orelse "invalid"); + try std.testing.expectEqualStrings("", environ.getPosix("ZIG_TEST_POSIX_EMPTY") orelse "invalid"); + try std.testing.expectEqualStrings("test=variable", environ.getPosix("ZIG_TEST_POSIX_1EQ") orelse "invalid"); + try std.testing.expectEqualStrings("=test=variable=", environ.getPosix("ZIG_TEST_POSIX_3EQ") orelse "invalid"); } diff --git a/test/standalone/run_output_caching/main.zig b/test/standalone/run_output_caching/main.zig index d7838cab9930f9d6fd7739ec7a554247c6c6ac7d..8a9855f5ecef4709e02b20a6b302370562895224 100644 --- a/test/standalone/run_output_caching/main.zig +++ b/test/standalone/run_output_caching/main.zig @@ -2,7 +2,7 @@ const std = @import("std"); pub fn main(init: std.process.Init) !void { const io = init.io; - var args = try init.minimal.argsAllocator(init.arena.allocator()); + var args = try init.minimal.args.iterateAllocator(init.arena.allocator()); _ = args.skip(); const filename = args.next().?; const file = try std.Io.Dir.cwd().createFile(io, filename, .{}); diff --git a/test/standalone/run_output_paths/create_file.zig b/test/standalone/run_output_paths/create_file.zig index 5281e6675a9269123fc6049e4ac3f023486c4e84..8490c1c5e9e9241a88035f2c2d8502bf7a370a3c 100644 --- a/test/standalone/run_output_paths/create_file.zig +++ b/test/standalone/run_output_paths/create_file.zig @@ -2,7 +2,7 @@ const std = @import("std"); pub fn main(init: std.process.Init) !void { const io = init.io; - var args = try init.args.iterateAllocator(init.arena.allocator()); + var args = try init.minimal.args.iterateAllocator(init.arena.allocator()); _ = args.skip(); const dir_name = args.next().?; const dir = try std.Io.Dir.cwd().openDir(io, if (std.mem.startsWith(u8, dir_name, "--dir=")) diff --git a/test/standalone/self_exe_symlink/create-symlink.zig b/test/standalone/self_exe_symlink/create-symlink.zig index cf6a1c81dddcdaa856624d85374d22f0859795c6..3de4f8a69d9f7a6127cd15770736a7626c70ace1 100644 --- a/test/standalone/self_exe_symlink/create-symlink.zig +++ b/test/standalone/self_exe_symlink/create-symlink.zig @@ -3,14 +3,16 @@ const std = @import("std"); pub fn main(init: std.process.Init) !void { const io = init.io; const gpa = init.gpa; - var it = try init.args.iterateAllocator(gpa); + var it = try init.minimal.args.iterateAllocator(gpa); defer it.deinit(); _ = it.next() orelse unreachable; // skip binary name const exe_path = it.next() orelse unreachable; const symlink_path = it.next() orelse unreachable; + const cwd = std.process.getCwdAlloc(init.arena.allocator()); + // If `exe_path` is relative to our cwd, we need to convert it to be relative to the dirname of `symlink_path`. - const exe_rel_path = try std.fs.path.relative(gpa, std.fs.path.dirname(symlink_path) orelse ".", exe_path); + const exe_rel_path = try std.fs.path.relative(gpa, cwd, init.env_map, std.fs.path.dirname(symlink_path) orelse ".", exe_path); defer gpa.free(exe_rel_path); try std.Io.Dir.cwd().symLink(io, exe_rel_path, symlink_path, .{}); diff --git a/test/standalone/self_exe_symlink/main.zig b/test/standalone/self_exe_symlink/main.zig index fa2c3380b580548d01bf00ba9274a0e7db46cdd3..0b1704b18a0463d3e012fc212be931be058a8cf3 100644 --- a/test/standalone/self_exe_symlink/main.zig +++ b/test/standalone/self_exe_symlink/main.zig @@ -1,13 +1,8 @@ const std = @import("std"); -pub fn main() !void { - var debug_allocator: std.heap.DebugAllocator(.{}) = .init; - defer if (debug_allocator.deinit() == .leak) @panic("found memory leaks"); - const gpa = debug_allocator.allocator(); - - var threaded: std.Io.Threaded = .init(gpa, .{}); - defer threaded.deinit(); - const io = threaded.io(); +pub fn main(init: std.process.Init) !void { + const gpa = init.gpa; + const io = init.io; const self_path = try std.process.executablePathAlloc(io, gpa); defer gpa.free(self_path); diff --git a/test/standalone/simple/cat/main.zig b/test/standalone/simple/cat/main.zig index adab3aead843e135d06120cccec04b6f5e2b0bf7..bc894d3881fd59658dc17ca65afc3dd934226560 100644 --- a/test/standalone/simple/cat/main.zig +++ b/test/standalone/simple/cat/main.zig @@ -7,7 +7,7 @@ const fatal = std.process.fatal; pub fn main(init: std.process.Init) !void { const arena = init.arena.allocator(); const io = init.io; - const args = try init.args.toSlice(arena); + const args = try init.minimal.args.toSlice(arena); const exe = args[0]; var catted_anything = false; diff --git a/test/standalone/simple/guess_number/main.zig b/test/standalone/simple/guess_number/main.zig index b98d109f211f2d8f26761e6a0726cab4efdfb469..e7b30867ec6ec5675822915750cd2c236653a26e 100644 --- a/test/standalone/simple/guess_number/main.zig +++ b/test/standalone/simple/guess_number/main.zig @@ -1,22 +1,11 @@ -const builtin = @import("builtin"); const std = @import("std"); -// See https://github.com/ziglang/zig/issues/24510 -// for the plan to simplify this code. -pub fn main() !void { - var debug_allocator: std.heap.DebugAllocator(.{}) = .init; - defer _ = debug_allocator.deinit(); - const gpa = debug_allocator.allocator(); - - var threaded: std.Io.Threaded = .init(gpa, .{}); - defer threaded.deinit(); - const io = threaded.io(); - - var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{}); +pub fn main(init: std.process.Init) !void { + var stdout_writer = std.Io.File.stdout().writerStreaming(init.io, &.{}); const out = &stdout_writer.interface; var line_buffer: [20]u8 = undefined; - var stdin_reader: std.Io.File.Reader = .init(.stdin(), io, &line_buffer); + var stdin_reader: std.Io.File.Reader = .init(.stdin(), init.io, &line_buffer); const in = &stdin_reader.interface; try out.writeAll("Welcome to the Guess Number Game in Zig.\n"); diff --git a/test/standalone/windows_bat_args/fuzz.zig b/test/standalone/windows_bat_args/fuzz.zig index 123c0be31454ccedb32d6ff2816745382013831c..0f29f4039856ed4a73eb606b8fa85f747909c086 100644 --- a/test/standalone/windows_bat_args/fuzz.zig +++ b/test/standalone/windows_bat_args/fuzz.zig @@ -93,7 +93,7 @@ fn testExecBat(gpa: Allocator, io: Io, bat: []const u8, args: []const []const u8 const can_have_trailing_empty_args = std.mem.eql(u8, bat, "args3.bat"); - const result = try std.process.Child.run(gpa, io, .{ + const result = try std.process.run(gpa, io, .{ .env_map = env, .argv = argv, }); diff --git a/test/standalone/windows_bat_args/test.zig b/test/standalone/windows_bat_args/test.zig index 38ecb5e2f897a001c49b6c3eb6bc3cc9aa0fb64d..c1841925994fb9c1dddd5f1801adcaeed4c5fd85 100644 --- a/test/standalone/windows_bat_args/test.zig +++ b/test/standalone/windows_bat_args/test.zig @@ -140,7 +140,7 @@ fn testExecBat(gpa: Allocator, io: Io, bat: []const u8, args: []const []const u8 const can_have_trailing_empty_args = std.mem.eql(u8, bat, "args3.bat"); - const result = try std.process.Child.run(gpa, io, .{ + const result = try std.process.run(gpa, io, .{ .env_map = env, .argv = argv, }); diff --git a/test/standalone/windows_paths/test.zig b/test/standalone/windows_paths/test.zig index 1fbdca211526fcc61639b8c518d8221f3febf3ab..b874dea3e9e345e4c4a60b5ac1f437b7adb2964f 100644 --- a/test/standalone/windows_paths/test.zig +++ b/test/standalone/windows_paths/test.zig @@ -98,7 +98,7 @@ fn checkRelative( cwd: ?[]const u8, env_map: ?*const std.process.Environ.Map, ) !void { - const result = try std.process.Child.run(allocator, io, .{ + const result = try std.process.run(allocator, io, .{ .argv = argv, .cwd = cwd, .env_map = env_map, diff --git a/test/standalone/windows_spawn/main.zig b/test/standalone/windows_spawn/main.zig index 32db164a5a53a6317e3856a39bf170376093d235..b1fba1bed65c135eeb1ad88972b6db36a30b7476 100644 --- a/test/standalone/windows_spawn/main.zig +++ b/test/standalone/windows_spawn/main.zig @@ -207,7 +207,7 @@ fn testExec(gpa: Allocator, io: Io, command: []const u8, expected_stdout: []cons } fn testExecWithCwd(gpa: Allocator, io: Io, command: []const u8, cwd: ?[]const u8, expected_stdout: []const u8) !void { - const result = try std.process.Child.run(gpa, io, .{ + const result = try std.process.run(gpa, io, .{ .argv = &[_][]const u8{command}, .cwd = cwd, }); diff --git a/tools/doctest.zig b/tools/doctest.zig index 44d5954f7e845b88168b2fa07ba01bb926421c11..377de25e93f88c6db5bfdd37413614a36d1b436e 100644 --- a/tools/doctest.zig +++ b/tools/doctest.zig @@ -193,14 +193,14 @@ fn printOutput( try shell_out.print("\n", .{}); if (expected_outcome == .build_fail) { - const result = try process.Child.run(arena, io, .{ + const result = try process.run(arena, io, .{ .argv = build_args.items, .cwd = tmp_dir_path, .env_map = &env_map, .max_output_bytes = max_doc_file_size, }); switch (result.term) { - .Exited => |exit_code| { + .exited => |exit_code| { if (exit_code == 0) { print("{s}\nThe following command incorrectly succeeded:\n", .{result.stderr}); dumpArgs(build_args.items); @@ -249,21 +249,21 @@ fn printOutput( var exited_with_signal = false; const result = if (expected_outcome == .fail) blk: { - const result = try process.Child.run(arena, io, .{ + const result = try process.run(arena, io, .{ .argv = run_args, .env_map = &env_map, .cwd = tmp_dir_path, .max_output_bytes = max_doc_file_size, }); switch (result.term) { - .Exited => |exit_code| { + .exited => |exit_code| { if (exit_code == 0) { print("{s}\nThe following command incorrectly succeeded:\n", .{result.stderr}); dumpArgs(run_args); fatal("example incorrectly compiled", .{}); } }, - .Signal => exited_with_signal = true, + .signal => exited_with_signal = true, else => {}, } break :blk result; @@ -368,14 +368,14 @@ fn printOutput( try test_args.append("-lc"); try shell_out.print("-lc ", .{}); } - const result = try process.Child.run(arena, io, .{ + const result = try process.run(arena, io, .{ .argv = test_args.items, .env_map = &env_map, .cwd = tmp_dir_path, .max_output_bytes = max_doc_file_size, }); switch (result.term) { - .Exited => |exit_code| { + .exited => |exit_code| { if (exit_code == 0) { print("{s}\nThe following command incorrectly succeeded:\n", .{result.stderr}); dumpArgs(test_args.items); @@ -424,14 +424,14 @@ fn printOutput( }, } - const result = try process.Child.run(arena, io, .{ + const result = try process.run(arena, io, .{ .argv = test_args.items, .env_map = &env_map, .cwd = tmp_dir_path, .max_output_bytes = max_doc_file_size, }); switch (result.term) { - .Exited => |exit_code| { + .exited => |exit_code| { if (exit_code == 0) { print("{s}\nThe following command incorrectly succeeded:\n", .{result.stderr}); dumpArgs(test_args.items); @@ -500,14 +500,14 @@ fn printOutput( } if (maybe_error_match) |error_match| { - const result = try process.Child.run(arena, io, .{ + const result = try process.run(arena, io, .{ .argv = build_args.items, .env_map = &env_map, .cwd = tmp_dir_path, .max_output_bytes = max_doc_file_size, }); switch (result.term) { - .Exited => |exit_code| { + .exited => |exit_code| { if (exit_code == 0) { print("{s}\nThe following command incorrectly succeeded:\n", .{result.stderr}); dumpArgs(build_args.items); @@ -1123,15 +1123,15 @@ fn run( env_map: *process.Environ.Map, cwd: []const u8, args: []const []const u8, -) !process.Child.RunResult { - const result = try process.Child.run(allocator, io, .{ +) !process.RunResult { + const result = try process.run(allocator, io, .{ .argv = args, .env_map = env_map, .cwd = cwd, .max_output_bytes = max_doc_file_size, }); switch (result.term) { - .Exited => |exit_code| { + .exited => |exit_code| { if (exit_code != 0) { std.debug.print("{s}\nThe following command exited with code {}:\n", .{ result.stderr, exit_code }); dumpArgs(args); diff --git a/tools/dump-cov.zig b/tools/dump-cov.zig index b264f8d0322bdd6ee7e8c5222b3af900ca59a0a8..4a54d9f5199299a75dd8fcae46a7d3506ee53d50 100644 --- a/tools/dump-cov.zig +++ b/tools/dump-cov.zig @@ -10,9 +10,9 @@ const SeenPcsHeader = std.Build.abi.fuzz.SeenPcsHeader; pub fn main(init: std.process.Init) !void { const gpa = init.gpa; - const arena = init.arena; + const arena = init.arena.allocator(); const io = init.io; - const args = try init.args.toSlice(arena); + const args = try init.minimal.args.toSlice(arena); const target_query_str = switch (args.len) { 3 => "native", diff --git a/tools/fetch_them_macos_headers.zig b/tools/fetch_them_macos_headers.zig index d52ac9173a42b39a72fad971739be61093a33984..c0d41b85ae991f29918b378e98c85185369fdad5 100644 --- a/tools/fetch_them_macos_headers.zig +++ b/tools/fetch_them_macos_headers.zig @@ -68,7 +68,7 @@ const usage = pub fn main(init: std.process.Init) !void { const allocator = init.arena; - const args = try init.args.toSlice(allocator); + const args = try init.minimal.args.toSlice(allocator); var argv = std.array_list.Managed([]const u8).init(allocator); var sysroot: ?[]const u8 = null; @@ -161,7 +161,7 @@ fn fetchTarget( }); try cc_argv.appendSlice(args); - const res = try std.process.Child.run(arena, io, .{ .argv = cc_argv.items }); + const res = try std.process.run(arena, io, .{ .argv = cc_argv.items }); if (res.stderr.len != 0) { std.log.err("{s}", .{res.stderr}); diff --git a/tools/gen_macos_headers_c.zig b/tools/gen_macos_headers_c.zig index 960b480b15d4896d079721de3b79f7cd1592239d..393e17f321bbcb5a050b838970b8b25ff83b32ff 100644 --- a/tools/gen_macos_headers_c.zig +++ b/tools/gen_macos_headers_c.zig @@ -16,8 +16,8 @@ const usage = pub fn main(init: std.process.Init) !void { const arena = init.arena; const io = init.io; + const args = try init.minimal.args.toSlice(arena); - const args = try init.args.toSlice(arena); if (args.len == 1) fatal("no command or option specified", .{}); var positionals = std.array_list.Managed([]const u8).init(arena); diff --git a/tools/gen_outline_atomics.zig b/tools/gen_outline_atomics.zig index 3a33c333d5f1c162df9dfb2e6136e1e5a15d6acd..bd36484e1e9b697deba71a6d32169c0bfde880e2 100644 --- a/tools/gen_outline_atomics.zig +++ b/tools/gen_outline_atomics.zig @@ -12,7 +12,7 @@ const AtomicOp = enum { }; pub fn main(init: std.process.Init) !void { - const arena = init.arena; + const arena = init.arena.allocator(); const io = init.io; //const args = try std.process.argsAlloc(arena); diff --git a/tools/gen_spirv_spec.zig b/tools/gen_spirv_spec.zig index e96c98b6c01c776476ca37fe624c7bbb029eb3b6..f53b10cbb1bfa1d61957bf37001c3a933875ba2f 100644 --- a/tools/gen_spirv_spec.zig +++ b/tools/gen_spirv_spec.zig @@ -1,6 +1,7 @@ const std = @import("std"); const Io = std.Io; const Allocator = std.mem.Allocator; +const assert = std.debug.assert; const g = @import("spirv/grammar.zig"); const CoreRegistry = g.CoreRegistry; @@ -54,28 +55,22 @@ const set_names = std.StaticStringMap(struct { []const u8, []const u8 }).initCom .{ "zig", .{ "zig", "Zig" } }, }); -var arena = std.heap.ArenaAllocator.init(std.heap.smp_allocator); -const allocator = arena.allocator(); - -pub fn main() !void { - defer arena.deinit(); - - const args = try std.process.argsAlloc(allocator); +pub fn main(init: std.process.Init) !void { + const arena = init.arena.allocator(); + const args = try init.minimal.args.toSlice(arena); if (args.len != 3) { usageAndExit(args[0], 1); } - var threaded: std.Io.Threaded = .init(allocator, .{}); - defer threaded.deinit(); - const io = threaded.io(); + const io = init.io; - const json_path = try Io.Dir.path.join(allocator, &.{ args[1], "include/spirv/unified1/" }); + const json_path = try Io.Dir.path.join(arena, &.{ args[1], "include/spirv/unified1/" }); const dir = try Io.Dir.cwd().openDir(io, json_path, .{ .iterate = true }); - const core_spec = try readRegistry(io, CoreRegistry, dir, "spirv.core.grammar.json"); + const core_spec = try readRegistry(io, arena, CoreRegistry, dir, "spirv.core.grammar.json"); std.mem.sortUnstable(Instruction, core_spec.instructions, CmpInst{}, CmpInst.lt); - var exts = std.array_list.Managed(Extension).init(allocator); + var exts = std.array_list.Managed(Extension).init(arena); var it = dir.iterate(); while (try it.next(io)) |entry| { @@ -83,48 +78,48 @@ pub fn main() !void { continue; } - try readExtRegistry(io, &exts, dir, entry.name); + try readExtRegistry(io, arena, &exts, dir, entry.name); } - try readExtRegistry(io, &exts, Io.Dir.cwd(), args[2]); + try readExtRegistry(io, arena, &exts, Io.Dir.cwd(), args[2]); - var allocating: std.Io.Writer.Allocating = .init(allocator); + var allocating: std.Io.Writer.Allocating = .init(arena); defer allocating.deinit(); - try render(&allocating.writer, core_spec, exts.items); + try render(arena, &allocating.writer, core_spec, exts.items); try allocating.writer.writeByte(0); const output = allocating.written()[0 .. allocating.written().len - 1 :0]; - var tree = try std.zig.Ast.parse(allocator, output, .zig); + var tree = try std.zig.Ast.parse(arena, output, .zig); if (tree.errors.len != 0) { - try std.zig.printAstErrorsToStderr(allocator, io, tree, "", .auto); + try std.zig.printAstErrorsToStderr(arena, io, tree, "", .auto); return; } - var zir = try std.zig.AstGen.generate(allocator, tree); + var zir = try std.zig.AstGen.generate(arena, tree); if (zir.hasCompileErrors()) { var wip_errors: std.zig.ErrorBundle.Wip = undefined; - try wip_errors.init(allocator); + try wip_errors.init(arena); defer wip_errors.deinit(); try wip_errors.addZirErrorMessages(zir, tree, output, ""); var error_bundle = try wip_errors.toOwnedBundle(""); - defer error_bundle.deinit(allocator); + defer error_bundle.deinit(arena); try error_bundle.renderToStderr(io, .{}, .auto); } - const formatted_output = try tree.renderAlloc(allocator); + const formatted_output = try tree.renderAlloc(arena); try Io.File.stdout().writeStreamingAll(io, formatted_output); } -fn readExtRegistry(io: Io, exts: *std.array_list.Managed(Extension), dir: Io.Dir, sub_path: []const u8) !void { +fn readExtRegistry(io: Io, arena: Allocator, exts: *std.array_list.Managed(Extension), dir: Io.Dir, sub_path: []const u8) !void { const filename = Io.Dir.path.basename(sub_path); if (!std.mem.startsWith(u8, filename, "extinst.")) { return; } - std.debug.assert(std.mem.endsWith(u8, filename, ".grammar.json")); + assert(std.mem.endsWith(u8, filename, ".grammar.json")); const name = filename["extinst.".len .. filename.len - ".grammar.json".len]; - const spec = try readRegistry(io, ExtensionRegistry, dir, sub_path); + const spec = try readRegistry(io, arena, ExtensionRegistry, dir, sub_path); const set_name = set_names.get(name) orelse { std.log.info("ignored instruction set '{s}'", .{name}); @@ -140,16 +135,16 @@ fn readExtRegistry(io: Io, exts: *std.array_list.Managed(Extension), dir: Io.Dir }); } -fn readRegistry(io: Io, comptime RegistryType: type, dir: Io.Dir, path: []const u8) !RegistryType { - const spec = try dir.readFileAlloc(io, path, allocator, .unlimited); +fn readRegistry(io: Io, arena: Allocator, comptime RegistryType: type, dir: Io.Dir, path: []const u8) !RegistryType { + const spec = try dir.readFileAlloc(io, path, arena, .unlimited); // Required for json parsing. // TODO: ALI @setEvalBranchQuota(10000); - var scanner = std.json.Scanner.initCompleteInput(allocator, spec); + var scanner = std.json.Scanner.initCompleteInput(arena, spec); var diagnostics = std.json.Diagnostics{}; scanner.enableDiagnostics(&diagnostics); - const parsed = std.json.parseFromTokenSource(RegistryType, allocator, &scanner, .{}) catch |err| { + const parsed = std.json.parseFromTokenSource(RegistryType, arena, &scanner, .{}) catch |err| { std.debug.print("{s}:{}:{}:\n", .{ path, diagnostics.getLine(), diagnostics.getColumn() }); return err; }; @@ -158,8 +153,8 @@ fn readRegistry(io: Io, comptime RegistryType: type, dir: Io.Dir, path: []const /// Returns a set with types that require an extra struct for the `Instruction` interface /// to the spir-v spec, or whether the original type can be used. -fn extendedStructs(kinds: []const OperandKind) !ExtendedStructSet { - var map = ExtendedStructSet.init(allocator); +fn extendedStructs(arena: Allocator, kinds: []const OperandKind) !ExtendedStructSet { + var map = ExtendedStructSet.init(arena); try map.ensureTotalCapacity(@as(u32, @intCast(kinds.len))); for (kinds) |kind| { @@ -194,6 +189,7 @@ fn tagPriorityScore(tag: []const u8) usize { } fn render( + arena: Allocator, writer: *std.Io.Writer, registry: CoreRegistry, extensions: []const Extension, @@ -299,7 +295,7 @@ fn render( ); // Merge the operand kinds from all extensions together. - var all_operand_kinds = OperandKindMap.init(allocator); + var all_operand_kinds = OperandKindMap.init(arena); for (registry.operand_kinds) |kind| { try all_operand_kinds.putNoClobber(.{ "core", kind.kind }, kind); } @@ -312,22 +308,22 @@ fn render( try all_operand_kinds.ensureUnusedCapacity(ext.spec.operand_kinds.len); for (ext.spec.operand_kinds) |kind| { var new_kind = kind; - new_kind.kind = try std.mem.join(allocator, ".", &.{ ext.name, kind.kind }); + new_kind.kind = try std.mem.join(arena, ".", &.{ ext.name, kind.kind }); try all_operand_kinds.putNoClobber(.{ ext.name, kind.kind }, new_kind); } } - const extended_structs = try extendedStructs(all_operand_kinds.values()); + const extended_structs = try extendedStructs(arena, all_operand_kinds.values()); // Note: extensions don't seem to have class. - try renderClass(writer, registry.instructions); + try renderClass(arena, writer, registry.instructions); try renderOperandKind(writer, all_operand_kinds.values()); - try renderOpcodes(writer, "Opcode", true, registry.instructions, extended_structs); + try renderOpcodes(arena, writer, "Opcode", true, registry.instructions, extended_structs); for (extensions) |ext| { - try renderOpcodes(writer, ext.opcode_name, false, ext.spec.instructions, extended_structs); + try renderOpcodes(arena, writer, ext.opcode_name, false, ext.spec.instructions, extended_structs); } - try renderOperandKinds(writer, all_operand_kinds.values(), extended_structs); + try renderOperandKinds(arena, writer, all_operand_kinds.values(), extended_structs); try renderInstructionSet(writer, registry, extensions, all_operand_kinds); } @@ -414,8 +410,8 @@ fn renderInstructionsCase( ); } -fn renderClass(writer: *std.Io.Writer, instructions: []const Instruction) !void { - var class_map = std.StringArrayHashMap(void).init(allocator); +fn renderClass(arena: Allocator, writer: *std.Io.Writer, instructions: []const Instruction) !void { + var class_map = std.StringArrayHashMap(void).init(arena); for (instructions) |inst| { if (std.mem.eql(u8, inst.class.?, "@exclude")) continue; @@ -535,16 +531,17 @@ fn renderEnumerant(writer: *std.Io.Writer, enumerant: Enumerant) !void { } fn renderOpcodes( + arena: Allocator, writer: *std.Io.Writer, opcode_type_name: []const u8, want_operands: bool, instructions: []const Instruction, extended_structs: ExtendedStructSet, ) !void { - var inst_map = std.AutoArrayHashMap(u32, usize).init(allocator); + var inst_map = std.AutoArrayHashMap(u32, usize).init(arena); try inst_map.ensureTotalCapacity(instructions.len); - var aliases = std.array_list.Managed(struct { inst: usize, alias: usize }).init(allocator); + var aliases = std.array_list.Managed(struct { inst: usize, alias: usize }).init(arena); try aliases.ensureTotalCapacity(instructions.len); for (instructions, 0..) |inst, i| { @@ -634,30 +631,32 @@ fn renderOpcodes( } fn renderOperandKinds( + arena: Allocator, writer: *std.Io.Writer, kinds: []const OperandKind, extended_structs: ExtendedStructSet, ) !void { for (kinds) |kind| { switch (kind.category) { - .ValueEnum => try renderValueEnum(writer, kind, extended_structs), - .BitEnum => try renderBitEnum(writer, kind, extended_structs), + .ValueEnum => try renderValueEnum(arena, writer, kind, extended_structs), + .BitEnum => try renderBitEnum(arena, writer, kind, extended_structs), else => {}, } } } fn renderValueEnum( + arena: Allocator, writer: *std.Io.Writer, enumeration: OperandKind, extended_structs: ExtendedStructSet, ) !void { const enumerants = enumeration.enumerants orelse return error.InvalidRegistry; - var enum_map = std.AutoArrayHashMap(u32, usize).init(allocator); + var enum_map = std.AutoArrayHashMap(u32, usize).init(arena); try enum_map.ensureTotalCapacity(enumerants.len); - var aliases = std.array_list.Managed(struct { enumerant: usize, alias: usize }).init(allocator); + var aliases = std.array_list.Managed(struct { enumerant: usize, alias: usize }).init(arena); try aliases.ensureTotalCapacity(enumerants.len); for (enumerants, 0..) |enumerant, i| { @@ -726,6 +725,7 @@ fn renderValueEnum( } fn renderBitEnum( + arena: Allocator, writer: *std.Io.Writer, enumeration: OperandKind, extended_structs: ExtendedStructSet, @@ -735,7 +735,7 @@ fn renderBitEnum( var flags_by_bitpos = [_]?usize{null} ** 32; const enumerants = enumeration.enumerants orelse return error.InvalidRegistry; - var aliases = std.array_list.Managed(struct { flag: usize, alias: u5 }).init(allocator); + var aliases = std.array_list.Managed(struct { flag: usize, alias: u5 }).init(arena); try aliases.ensureTotalCapacity(enumerants.len); for (enumerants, 0..) |enumerant, i| { @@ -749,7 +749,7 @@ fn renderBitEnum( continue; } - std.debug.assert(@popCount(value) == 1); + assert(@popCount(value) == 1); const bitpos = std.math.log2_int(u32, value); if (flags_by_bitpos[bitpos]) |*existing| { diff --git a/tools/gen_stubs.zig b/tools/gen_stubs.zig index 51e83e1a08c08afe91287c27099a398d079540e8..bad27fd2015cd57ae34f00b8448127b0a35630bd 100644 --- a/tools/gen_stubs.zig +++ b/tools/gen_stubs.zig @@ -281,16 +281,10 @@ const Parse = struct { arch: Arch, }; -pub fn main() !void { - var arena_instance = std.heap.ArenaAllocator.init(std.heap.page_allocator); - defer arena_instance.deinit(); - const arena = arena_instance.allocator(); - - var threaded: std.Io.Threaded = .init(arena, .{}); - defer threaded.deinit(); - const io = threaded.io(); - - const args = try std.process.argsAlloc(arena); +pub fn main(init: std.process.Init) !void { + const arena = init.arena.allocator(); + const io = init.io; + const args = try init.minimal.args.toSlice(arena); const build_all_path = args[1]; var build_all_dir = try Io.Dir.cwd().openDir(io, build_all_path, .{}); diff --git a/tools/generate_c_size_and_align_checks.zig b/tools/generate_c_size_and_align_checks.zig index 48c35f07d94f1db54ed61f5783f60caab5c93436..6041a550da6f3ac04cf366cf9d1913c3cbbed5da 100644 --- a/tools/generate_c_size_and_align_checks.zig +++ b/tools/generate_c_size_and_align_checks.zig @@ -29,7 +29,7 @@ fn cName(ty: std.Target.CType) []const u8 { var general_purpose_allocator: std.heap.GeneralPurposeAllocator(.{}) = .init; pub fn main(init: std.process.Init) !void { - const args = try init.args.toSlice(init.arena); + const args = try init.minimal.args.toSlice(init.arena.allocator()); const io = init.io; if (args.len != 2) { diff --git a/tools/generate_linux_syscalls.zig b/tools/generate_linux_syscalls.zig index c9740a7f70e71d308416f0fd71ded045e79b0279..e124a2c84d374648d696a766f4f570d5c492d09f 100644 --- a/tools/generate_linux_syscalls.zig +++ b/tools/generate_linux_syscalls.zig @@ -174,7 +174,7 @@ pub fn main(init: std.process.Init) !void { const gpa = init.gpa; const io = init.io; - const args = try std.process.argsAlloc(gpa); + const args = try init.minimal.args.toSlice(init.arena.allocator()); if (args.len < 2 or mem.eql(u8, args[1], "--help")) { const stderr = std.debug.lockStderr(&.{}); const w = &stderr.file_writer.interface; diff --git a/tools/incr-check.zig b/tools/incr-check.zig index d9664be7108a6f650d9de3dffcf84ff7021a52b1..16f37b1bd02a69bb8a13e75c492d737bfea8f411 100644 --- a/tools/incr-check.zig +++ b/tools/incr-check.zig @@ -171,14 +171,6 @@ pub fn main(init: std.process.Init) !void { const zig_prog_node = target_prog_node.start("zig build-exe", 0); defer zig_prog_node.end(); - var child = std.process.Child.init(child_args.items, arena); - child.stdin_behavior = .Pipe; - child.stdout_behavior = .Pipe; - child.stderr_behavior = .Pipe; - child.progress_node = zig_prog_node; - child.cwd_dir = tmp_dir; - child.cwd = tmp_dir_path; - var cc_child_args: std.ArrayList([]const u8) = .empty; if (target.backend == .cbe) { const resolved_cc_zig_exe = if (opt_cc_zig) |cc_zig_exe| @@ -201,6 +193,17 @@ pub fn main(init: std.process.Init) !void { try cc_child_args.append(arena, "-o"); } + var child = std.process.spawn(io, .{ + .argv = child_args.items, + .stdin = .pipe, + .stdout = .pipe, + .stderr = .pipe, + .progress_node = zig_prog_node, + .cwd_dir = tmp_dir, + .cwd = tmp_dir_path, + }); + defer child.kill(io); + var eval: Eval = .{ .arena = arena, .io = io, @@ -219,11 +222,6 @@ pub fn main(init: std.process.Init) !void { .enable_darling = enable_darling, }; - try child.spawn(io); - errdefer { - _ = child.kill(io) catch {}; - } - var poller = Io.poll(arena, Eval.StreamEnum, .{ .stdout = child.stdout.?, .stderr = child.stderr.?, @@ -528,7 +526,7 @@ const Eval = struct { const run_prog_node = prog_node.start("run generated executable", 0); defer run_prog_node.end(); - const result = std.process.Child.run(eval.arena, io, .{ + const result = std.process.run(eval.arena, io, .{ .argv = argv, .cwd_dir = eval.tmp_dir, .cwd = eval.tmp_dir_path, @@ -556,7 +554,7 @@ const Eval = struct { } switch (result.term) { - .Exited => |code| switch (update.outcome) { + .exited => |code| switch (update.outcome) { .unknown, .compile_errors => unreachable, .stdout => |expected_stdout| { if (code != 0) { @@ -564,9 +562,12 @@ const Eval = struct { } try std.testing.expectEqualStrings(expected_stdout, result.stdout); }, - .exit_code => |expected_code| try std.testing.expectEqual(expected_code, result.term.Exited), + .exit_code => |expected_code| try std.testing.expectEqual(expected_code, code), }, - .Signal, .Stopped, .Unknown => { + .signal => |sig| { + eval.fatal("generated executable '{s}' terminated with signal {t}", .{ binary_path, sig }); + }, + .stopped, .unknown => { eval.fatal("generated executable '{s}' terminated unexpectedly", .{binary_path}); }, } @@ -614,7 +615,7 @@ const Eval = struct { try eval.cc_child_args.appendSlice(eval.arena, &.{ out_path, c_path }); defer eval.cc_child_args.items.len -= 2; - const result = std.process.Child.run(eval.arena, eval.io, .{ + const result = std.process.run(eval.arena, eval.io, .{ .argv = eval.cc_child_args.items, .cwd_dir = eval.tmp_dir, .cwd = eval.tmp_dir_path, @@ -623,13 +624,13 @@ const Eval = struct { eval.fatal("failed to spawn zig cc for '{s}': {t}", .{ c_path, err }); }; switch (result.term) { - .Exited => |code| if (code != 0) { + .exited => |code| if (code != 0) { if (result.stderr.len != 0) { std.log.err("zig cc stderr:\n{s}", .{result.stderr}); } eval.fatal("zig cc for '{s}' failed with code {d}", .{ c_path, code }); }, - .Signal, .Stopped, .Unknown => { + .signal, .stopped, .unknown => { if (result.stderr.len != 0) { std.log.err("zig cc stderr:\n{s}", .{result.stderr}); } @@ -909,8 +910,9 @@ fn waitChild(child: *std.process.Child, eval: *Eval) void { requestExit(child, eval); const term = child.wait(io) catch |err| eval.fatal("child process failed: {t}", .{err}); switch (term) { - .Exited => |code| if (code != 0) eval.fatal("compiler failed with code {d}", .{code}), - .Signal, .Stopped, .Unknown => eval.fatal("compiler terminated unexpectedly", .{}), + .exited => |code| if (code != 0) eval.fatal("compiler failed with code {d}", .{code}), + .signal => |sig| eval.fatal("compiler terminated with signal {t}", .{sig}), + .stopped, .unknown => eval.fatal("compiler terminated unexpectedly", .{}), } } diff --git a/tools/migrate_langref.zig b/tools/migrate_langref.zig index 24ddd5941d3956bd42b59b05c8a7505d290a18c6..581d1294ff57cc8f3f9bdb7bb65560b467d40db4 100644 --- a/tools/migrate_langref.zig +++ b/tools/migrate_langref.zig @@ -11,21 +11,14 @@ const fatal = std.process.fatal; const max_doc_file_size = 10 * 1024 * 1024; -pub fn main() !void { - var arena_instance = std.heap.ArenaAllocator.init(std.heap.page_allocator); - defer arena_instance.deinit(); - const arena = arena_instance.allocator(); +pub fn main(init: std.process.Init) !void { + const arena = init.arena.allocator(); + const io = init.io; + const args = try init.minimal.args.toSlice(arena); - const gpa = arena; - - const args = try std.process.argsAlloc(arena); const input_file = args[1]; const output_file = args[2]; - var threaded: std.Io.Threaded = .init(gpa, .{}); - defer threaded.deinit(); - const io = threaded.io(); - var in_file = try Dir.cwd().openFile(io, input_file, .{ .mode = .read_only }); defer in_file.close(io); diff --git a/tools/update-linux-headers.zig b/tools/update-linux-headers.zig index 5091b18dbe74e88eeec1516beb8e67091672b413..26cf1fd1439c15dd37830e6e3351d7654ced573c 100644 --- a/tools/update-linux-headers.zig +++ b/tools/update-linux-headers.zig @@ -141,15 +141,13 @@ const HashToContents = std.StringHashMap(Contents); const TargetToHash = std.ArrayHashMap(DestTarget, []const u8, DestTarget.HashContext, true); const PathTable = std.StringHashMap(*TargetToHash); -pub fn main() !void { - var arena_state = std.heap.ArenaAllocator.init(std.heap.page_allocator); - const arena = arena_state.allocator(); +pub fn main(init: std.process.Init) !void { + const arena = init.arena.allocator(); + const io = init.io; + const args = try init.minimal.args.toSlice(arena); + const env_map = init.env_map; + const cwd = try std.process.getCwdAlloc(arena); - var threaded: Io.Threaded = .init(arena, .{}); - defer threaded.deinit(); - const io = threaded.io(); - - const args = try std.process.argsAlloc(arena); var search_paths = std.array_list.Managed([]const u8).init(arena); var opt_out_dir: ?[]const u8 = null; @@ -211,7 +209,7 @@ pub fn main() !void { switch (entry.kind) { .directory => try dir_stack.append(full_path), .file => { - const rel_path = try Dir.path.relative(arena, target_include_dir, full_path); + const rel_path = try Dir.path.relative(arena, cwd, env_map, target_include_dir, full_path); const max_size = 2 * 1024 * 1024 * 1024; const raw_bytes = try Dir.cwd().readFileAlloc(io, full_path, arena, .limited(max_size)); const trimmed = std.mem.trim(u8, raw_bytes, " \r\n\t"); diff --git a/tools/update_clang_options.zig b/tools/update_clang_options.zig index 3c7762bab6cda7a8d4b4ec9aa60139c414642393..b52267a3fbd2c7efe37b58efe19615c5c8a8c556 100644 --- a/tools/update_clang_options.zig +++ b/tools/update_clang_options.zig @@ -627,16 +627,10 @@ const cpu_targets = struct { pub const xtensa = std.Target.xtensa; }; -pub fn main() anyerror!void { - var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); - defer arena.deinit(); - - const allocator = arena.allocator(); - const args = try std.process.argsAlloc(allocator); - - var threaded: std.Io.Threaded = .init(allocator, .{}); - defer threaded.deinit(); - const io = threaded.io(); +pub fn main(init: std.process.Init) !void { + const arena = init.arena.allocator(); + const args = try init.minimal.args.toSlice(arena); + const io = init.io; var stdout_buffer: [4000]u8 = undefined; var stdout_writer = Io.File.stdout().writerStreaming(io, &stdout_buffer); @@ -658,7 +652,7 @@ pub fn main() anyerror!void { const llvm_src_root = args[2]; if (std.mem.startsWith(u8, llvm_src_root, "-")) printUsageAndExit(args[0]); - var llvm_to_zig_cpu_features = std.StringHashMap([]const u8).init(allocator); + var llvm_to_zig_cpu_features = std.StringHashMap([]const u8).init(arena); inline for (@typeInfo(cpu_targets).@"struct".decls) |decl| { const Feature = @field(cpu_targets, decl.name).Feature; @@ -675,12 +669,12 @@ pub fn main() anyerror!void { const child_args = [_][]const u8{ llvm_tblgen_exe, "--dump-json", - try std.fmt.allocPrint(allocator, "{s}/clang/include/clang/Driver/Options.td", .{llvm_src_root}), - try std.fmt.allocPrint(allocator, "-I={s}/llvm/include", .{llvm_src_root}), - try std.fmt.allocPrint(allocator, "-I={s}/clang/include/clang/Driver", .{llvm_src_root}), + try std.fmt.allocPrint(arena, "{s}/clang/include/clang/Driver/Options.td", .{llvm_src_root}), + try std.fmt.allocPrint(arena, "-I={s}/llvm/include", .{llvm_src_root}), + try std.fmt.allocPrint(arena, "-I={s}/clang/include/clang/Driver", .{llvm_src_root}), }; - const child_result = try std.process.Child.run(allocator, io, .{ + const child_result = try std.process.run(arena, io, .{ .argv = &child_args, .max_output_bytes = 100 * 1024 * 1024, }); @@ -688,7 +682,7 @@ pub fn main() anyerror!void { std.debug.print("{s}\n", .{child_result.stderr}); const json_text = switch (child_result.term) { - .Exited => |code| if (code == 0) child_result.stdout else { + .exited => |code| if (code == 0) child_result.stdout else { std.debug.print("llvm-tblgen exited with code {d}\n", .{code}); std.process.exit(1); }, @@ -698,11 +692,11 @@ pub fn main() anyerror!void { }, }; - const parsed = try json.parseFromSlice(json.Value, allocator, json_text, .{}); + const parsed = try json.parseFromSlice(json.Value, arena, json_text, .{}); defer parsed.deinit(); const root_map = &parsed.value.object; - var all_objects = std.array_list.Managed(*json.ObjectMap).init(allocator); + var all_objects = std.array_list.Managed(*json.ObjectMap).init(arena); { var it = root_map.iterator(); it_map: while (it.next()) |kv| { diff --git a/tools/update_cpu_features.zig b/tools/update_cpu_features.zig index 6def3db6baf18782e28e34fa781e0efde7030623..3041ee6acc5129a3bfbe9b8d1295a94f69a529e7 100644 --- a/tools/update_cpu_features.zig +++ b/tools/update_cpu_features.zig @@ -1884,7 +1884,7 @@ const targets = [_]ArchTarget{ }; pub fn main(init: std.process.Init) !void { - const arena = init.arena_allocator.allocator(); + const arena = init.arena.allocator(); const io = init.io; var args = try init.minimal.args.iterateAllocator(arena); @@ -1985,7 +1985,7 @@ fn processOneTarget(io: Io, job: Job) void { }), }; - const child_result = try std.process.Child.run(arena, io, .{ + const child_result = try std.process.run(arena, io, .{ .argv = &child_args, .max_output_bytes = 500 * 1024 * 1024, }); @@ -1995,7 +1995,7 @@ fn processOneTarget(io: Io, job: Job) void { } const json_text = switch (child_result.term) { - .Exited => |code| if (code == 0) child_result.stdout else { + .exited => |code| if (code == 0) child_result.stdout else { std.debug.print("llvm-tblgen exited with code {d}\n", .{code}); std.process.exit(1); }, diff --git a/tools/update_crc_catalog.zig b/tools/update_crc_catalog.zig index 29856aacf8d1ae2b039b20aca40b9eea698e2d42..57eedf375b29c1195dcfbf4dcea1f42f1235a192 100644 --- a/tools/update_crc_catalog.zig +++ b/tools/update_crc_catalog.zig @@ -6,16 +6,14 @@ const ascii = std.ascii; const catalog_txt = @embedFile("crc/catalog.txt"); -pub fn main() anyerror!void { - var arena_state = std.heap.ArenaAllocator.init(std.heap.page_allocator); - defer arena_state.deinit(); - const arena = arena_state.allocator(); +pub fn main(init: std.process.Init) !void { + const arena = init.arena.allocator(); + const io = init.io; + const args = try init.minimal.args.toSlice(arena); + return @"i like cheese"(arena, io, args); +} - var threaded: Io.Threaded = .init(arena, .{}); - defer threaded.deinit(); - const io = threaded.io(); - - const args = try std.process.argsAlloc(arena); +fn @"i like cheese"(arena: std.mem.Allocator, io: Io, args: []const []const u8) !void { if (args.len <= 1) printUsageAndExit(args[0]); const zig_src_root = args[1]; diff --git a/tools/update_freebsd_libc.zig b/tools/update_freebsd_libc.zig index d50364351f599e7501e0b910b6f532abb34fa351..c2b251da0fdae79803e10f8c3c41e37142ea1500 100644 --- a/tools/update_freebsd_libc.zig +++ b/tools/update_freebsd_libc.zig @@ -12,16 +12,11 @@ const exempt_files = [_][]const u8{ "abilists", }; -pub fn main() !void { - var arena_instance = std.heap.ArenaAllocator.init(std.heap.page_allocator); - defer arena_instance.deinit(); - const arena = arena_instance.allocator(); +pub fn main(init: std.process.Init) !void { + const arena = init.arena.allocator(); + const io = init.io; + const args = try init.minimal.args.toSlice(arena); - var threaded: Io.Threaded = .init(arena, .{}); - defer threaded.deinit(); - const io = threaded.io(); - - const args = try std.process.argsAlloc(arena); const freebsd_src_path = args[1]; const zig_src_path = args[2]; diff --git a/tools/update_glibc.zig b/tools/update_glibc.zig index 296d677d45e0a60bcb485acfa6ba2a52f1575b31..29df298bf6c49d5e5f140069d5b607550ecca979 100644 --- a/tools/update_glibc.zig +++ b/tools/update_glibc.zig @@ -38,16 +38,11 @@ const exempt_extensions = [_][]const u8{ "-2.33.c", }; -pub fn main() !void { - var arena_instance = std.heap.ArenaAllocator.init(std.heap.page_allocator); - defer arena_instance.deinit(); - const arena = arena_instance.allocator(); +pub fn main(init: std.process.Init) !void { + const arena = init.arena.allocator(); + const io = init.io; + const args = try init.minimal.args.toSlice(arena); - var threaded: Io.Threaded = .init(arena, .{}); - defer threaded.deinit(); - const io = threaded.io(); - - const args = try std.process.argsAlloc(arena); const glibc_src_path = args[1]; const zig_src_path = args[2]; diff --git a/tools/update_mingw.zig b/tools/update_mingw.zig index 678c3dbdca45564d32cfe76c2a4c6153533878cb..ad3237d1ff086e164744845f0741f4eff295390d 100644 --- a/tools/update_mingw.zig +++ b/tools/update_mingw.zig @@ -2,16 +2,11 @@ const std = @import("std"); const Io = std.Io; const Dir = std.Io.Dir; -pub fn main() !void { - var arena_instance = std.heap.ArenaAllocator.init(std.heap.page_allocator); - defer arena_instance.deinit(); - const arena = arena_instance.allocator(); +pub fn main(init: std.process.Init) !void { + const arena = init.arena.allocator(); + const io = init.io; + const args = try init.minimal.args.toSlice(arena); - var threaded: Io.Threaded = .init(arena, .{}); - defer threaded.deinit(); - const io = threaded.io(); - - const args = try std.process.argsAlloc(arena); const zig_src_lib_path = args[1]; const mingw_src_path = args[2]; diff --git a/tools/update_netbsd_libc.zig b/tools/update_netbsd_libc.zig index a5eeca35c7f2499affd28221931f0a27e137a03b..7a466c3246573cbcae9104000b9734d42fe89d7a 100644 --- a/tools/update_netbsd_libc.zig +++ b/tools/update_netbsd_libc.zig @@ -12,16 +12,11 @@ const exempt_files = [_][]const u8{ "abilists", }; -pub fn main() !void { - var arena_instance = std.heap.ArenaAllocator.init(std.heap.page_allocator); - defer arena_instance.deinit(); - const arena = arena_instance.allocator(); +pub fn main(init: std.process.Init) !void { + const arena = init.arena.allocator(); + const io = init.io; + const args = try init.minimal.args.toSlice(arena); - var threaded: Io.Threaded = .init(arena, .{}); - defer threaded.deinit(); - const io = threaded.io(); - - const args = try std.process.argsAlloc(arena); const netbsd_src_path = args[1]; const zig_src_path = args[2]; -- 2.54.0 From ca5c5ade5f6ba73430ae9dd107c774d31382ad02 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 2 Jan 2026 18:08:41 -0800 Subject: [PATCH 26/60] std.Options: work around not lazy enough compiler --- lib/std/debug/ElfFile.zig | 2 +- lib/std/std.zig | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/lib/std/debug/ElfFile.zig b/lib/std/debug/ElfFile.zig index 53db1c755a56255ccf9968c1fd7503ee39236a3a..007e815e374d61d860544e531d0a9e663157447d 100644 --- a/lib/std/debug/ElfFile.zig +++ b/lib/std/debug/ElfFile.zig @@ -67,7 +67,7 @@ pub const DebugInfoSearchPaths = struct { }; pub fn native(exe_path: []const u8) DebugInfoSearchPaths { - if (std.options.elf_debug_info_search_paths) |f| return f(exe_path); + if (std.Options.elf_debug_info_search_paths) |f| return f(exe_path); if (std.Options.debug_threaded_io) |t| return .{ .debuginfod_client = p: { if (t.environString("DEBUGINFOD_CACHE_PATH")) |p| { diff --git a/lib/std/std.zig b/lib/std/std.zig index 627e7de6ddace2b60d84f9ce46c52ccd765151c3..c5d71dcb602a06e4f4aeef18dc604e01eb056ba5 100644 --- a/lib/std/std.zig +++ b/lib/std/std.zig @@ -173,15 +173,21 @@ pub const Options = struct { /// stack traces will just print an error to the relevant `Io.Writer` and return. allow_stack_tracing: bool = !@import("builtin").strip_debug_info, - elf_debug_info_search_paths: ?fn (exe_path: []const u8) switch (@import("builtin").object_format) { + /// TODO This is a separate decl instead of a field as a workaround around + /// compilation errors due to zig not being lazy enough. + pub const elf_debug_info_search_paths: ?fn (exe_path: []const u8) switch (@import("builtin").object_format) { .elf => debug.ElfFile.DebugInfoSearchPaths, else => void, - } = null, + } = if (@hasDecl(root, "std_options_elf_debug_info_search_paths")) + root.std_options_elf_debug_info_search_paths + else + null; pub const debug_threaded_io: ?*Io.Threaded = if (@hasDecl(root, "std_options_debug_threaded_io")) root.std_options_debug_threaded_io else Io.Threaded.global_single_threaded; + /// The `Io` instance that `std.debug` uses for `std.debug.print`, /// capturing stack traces, loading debug info, finding the executable's /// own path, and environment variables that affect terminal mode -- 2.54.0 From af164b3f3cdfddc2e5449b05b9e1a30535b9ac94 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 2 Jan 2026 18:42:31 -0800 Subject: [PATCH 27/60] std.Build.Step.Run: no need to sort the environment since we now use ArrayHashMap, the order is deterministic, and in fact, observable by applications. --- lib/std/Build/Step/Run.zig | 28 +++------------------------- 1 file changed, 3 insertions(+), 25 deletions(-) diff --git a/lib/std/Build/Step/Run.zig b/lib/std/Build/Step/Run.zig index 32b04b4a988b923957f31f33a8e27f257c94ad52..838b1ec347351456f08f265b54ac74e30dee6935 100644 --- a/lib/std/Build/Step/Run.zig +++ b/lib/std/Build/Step/Run.zig @@ -795,31 +795,9 @@ fn make(step: *Step, options: Step.MakeOptions) !void { defer man.deinit(); if (run.env_map) |env_map| { - const KV = struct { []const u8, []const u8 }; - var kv_pairs = try std.array_list.Managed(KV).initCapacity(arena, env_map.count()); - var iter = env_map.iterator(); - while (iter.next()) |entry| { - kv_pairs.appendAssumeCapacity(.{ entry.key_ptr.*, entry.value_ptr.* }); - } - - std.mem.sortUnstable(KV, kv_pairs.items, {}, struct { - fn lessThan(_: void, kv1: KV, kv2: KV) bool { - const k1 = kv1[0]; - const k2 = kv2[0]; - - if (k1.len != k2.len) return k1.len < k2.len; - - for (k1, k2) |c1, c2| { - if (c1 == c2) continue; - return c1 < c2; - } - unreachable; // two keys cannot be equal - } - }.lessThan); - - for (kv_pairs.items) |kv| { - man.hash.addBytes(kv[0]); - man.hash.addBytes(kv[1]); + for (env_map.keys(), env_map.values()) |key, value| { + man.hash.addBytes(key); + man.hash.addBytes(value); } } -- 2.54.0 From e149c0e2aa9e35ce90760ebef158bb05f3a74d0d Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 2 Jan 2026 18:46:13 -0800 Subject: [PATCH 28/60] std.Build.Step.Run: fix wrong environment passed --- lib/std/Build/Step/Run.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/std/Build/Step/Run.zig b/lib/std/Build/Step/Run.zig index 838b1ec347351456f08f265b54ac74e30dee6935..a1acb2c37f615304b597c5ebe7f79cb640301724 100644 --- a/lib/std/Build/Step/Run.zig +++ b/lib/std/Build/Step/Run.zig @@ -1555,7 +1555,7 @@ fn spawnChildAndCollect( var spawn_options: process.SpawnOptions = .{ .argv = argv, .cwd = child_cwd, - .env_map = &graph.env_map, + .env_map = env_map, .request_resource_usage_statistics = true, .stdin = if (run.stdin != .none) s: { assert(run.stdio != .inherit); -- 2.54.0 From 4afed3e9ef83724bdfddd1d06b7727acda55e642 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 2 Jan 2026 18:46:25 -0800 Subject: [PATCH 29/60] test-standalone: update the rest of the cases to new API --- lib/compiler/objcopy.zig | 18 ++----- test/standalone/child_process/main.zig | 10 +++- test/standalone/empty_env/main.zig | 2 +- test/standalone/env_vars/main.zig | 10 ++-- .../self_exe_symlink/create-symlink.zig | 2 +- tools/fetch_them_macos_headers.zig | 25 ++++------ tools/gen_macos_headers_c.zig | 2 +- tools/process_headers.zig | 48 +++++++++---------- 8 files changed, 52 insertions(+), 65 deletions(-) diff --git a/lib/compiler/objcopy.zig b/lib/compiler/objcopy.zig index 77b60c9b372efc1616413ddb713741cbb59c0e65..57d019bc95b8887c9bce708d6be08fb3abc9205a 100644 --- a/lib/compiler/objcopy.zig +++ b/lib/compiler/objcopy.zig @@ -17,20 +17,10 @@ var stdout_buffer: [1024]u8 = undefined; var input_buffer: [1024]u8 = undefined; var output_buffer: [1024]u8 = undefined; -pub fn main() !void { - var arena_instance = std.heap.ArenaAllocator.init(std.heap.page_allocator); - defer arena_instance.deinit(); - const arena = arena_instance.allocator(); - - var general_purpose_allocator: std.heap.GeneralPurposeAllocator(.{}) = .init; - const gpa = general_purpose_allocator.allocator(); - - var threaded: std.Io.Threaded = .init(gpa, .{}); - defer threaded.deinit(); - const io = threaded.io(); - - const args = try std.process.argsAlloc(arena); - return cmdObjCopy(arena, io, args[1..]); +pub fn main(init: std.process.Init) !void { + const arena = init.arena.allocator(); + const args = try init.minimal.args.toSlice(arena); + return cmdObjCopy(arena, init.io, args[1..]); } fn cmdObjCopy(arena: Allocator, io: Io, args: []const []const u8) !void { diff --git a/test/standalone/child_process/main.zig b/test/standalone/child_process/main.zig index ef933c0abdea75cdd2df46943aa58a43cc9b91bc..bfd84a5361c885391b8d40b1caabc929d6cd8926 100644 --- a/test/standalone/child_process/main.zig +++ b/test/standalone/child_process/main.zig @@ -9,6 +9,12 @@ pub fn main(init: std.process.Init.Minimal) !void { }; const gpa = gpa_state.allocator(); + const process_cwd_path = try std.process.getCwdAlloc(gpa); + defer gpa.free(process_cwd_path); + + var env_map = try init.environ.createMap(gpa); + defer env_map.deinit(); + var it = try init.args.iterateAllocator(gpa); defer it.deinit(); _ = it.next() orelse unreachable; // skip binary name @@ -17,7 +23,7 @@ pub fn main(init: std.process.Init.Minimal) !void { const cwd_path = it.next() orelse break :child_path .{ child_path, false }; // If there is a third argument, it is the current CWD somewhere within the cache directory. // In that case, modify the child path in order to test spawning a path with a leading `..` component. - break :child_path .{ try std.fs.path.relative(gpa, cwd_path, child_path), true }; + break :child_path .{ try std.fs.path.relative(gpa, process_cwd_path, &env_map, cwd_path, child_path), true }; }; defer if (needs_free) gpa.free(child_path); @@ -28,7 +34,7 @@ pub fn main(init: std.process.Init.Minimal) !void { defer threaded.deinit(); const io = threaded.io(); - var child = try std.process.spawn(.{ + var child = try std.process.spawn(io, .{ .argv = &.{ child_path, "hello arg" }, .stdin = .pipe, .stdout = .pipe, diff --git a/test/standalone/empty_env/main.zig b/test/standalone/empty_env/main.zig index 7a47dd6d3e804390308e4f1941d5ec26fdbe8537..80331d5e5ff615007a0cae122f3e08735d5907dd 100644 --- a/test/standalone/empty_env/main.zig +++ b/test/standalone/empty_env/main.zig @@ -1,5 +1,5 @@ const std = @import("std"); pub fn main(init: std.process.Init) !void { - try std.testing.expect(init.env_map.count() == 0); + try std.testing.expectEqual(0, init.env_map.count()); } diff --git a/test/standalone/env_vars/main.zig b/test/standalone/env_vars/main.zig index 112c2a831296b13e3315c37c0f33755338fc21d0..04778baae115a604c9f876a86df353535d52668c 100644 --- a/test/standalone/env_vars/main.zig +++ b/test/standalone/env_vars/main.zig @@ -104,20 +104,20 @@ pub fn main(init: std.process.Init) !void { // getAlloc { try std.testing.expectEqualSlices(u8, "123", try environ.getAlloc(arena, "FOO")); - try std.testing.expectError(error.EnvironmentVariableNotFound, environ.getAlloc(arena, "FOO=")); - try std.testing.expectError(error.EnvironmentVariableNotFound, environ.getAlloc(arena, "FO")); - try std.testing.expectError(error.EnvironmentVariableNotFound, environ.getAlloc(arena, "FOOO")); + try std.testing.expectError(error.EnvironmentVariableMissing, environ.getAlloc(arena, "FOO=")); + try std.testing.expectError(error.EnvironmentVariableMissing, environ.getAlloc(arena, "FO")); + try std.testing.expectError(error.EnvironmentVariableMissing, environ.getAlloc(arena, "FOOO")); if (builtin.os.tag == .windows) { try std.testing.expectEqualSlices(u8, "123", try environ.getAlloc(arena, "foo")); } try std.testing.expectEqualSlices(u8, "ABC=123", try environ.getAlloc(arena, "EQUALS")); - try std.testing.expectError(error.EnvironmentVariableNotFound, environ.getAlloc(arena, "EQUALS=ABC")); + try std.testing.expectError(error.EnvironmentVariableMissing, environ.getAlloc(arena, "EQUALS=ABC")); try std.testing.expectEqualSlices(u8, "non-ascii አማርኛ \u{10FFFF}", try environ.getAlloc(arena, "КИРиллИЦА")); if (builtin.os.tag == .windows) { try std.testing.expectEqualSlices(u8, "non-ascii አማርኛ \u{10FFFF}", try environ.getAlloc(arena, "кирИЛЛица")); } try std.testing.expectEqualSlices(u8, "", try environ.getAlloc(arena, "NO_VALUE")); - try std.testing.expectError(error.EnvironmentVariableNotFound, environ.getAlloc(arena, "NOT_SET")); + try std.testing.expectError(error.EnvironmentVariableMissing, environ.getAlloc(arena, "NOT_SET")); if (builtin.os.tag == .windows) { try std.testing.expectEqualSlices(u8, "hi", try environ.getAlloc(arena, "=HIDDEN")); try std.testing.expectEqualSlices(u8, "\xed\xa0\x80", try environ.getAlloc(arena, "INVALID_UTF16_\xed\xa0\x80")); diff --git a/test/standalone/self_exe_symlink/create-symlink.zig b/test/standalone/self_exe_symlink/create-symlink.zig index 3de4f8a69d9f7a6127cd15770736a7626c70ace1..32918fe5df86ad933f4ce32aad392b08b44433e9 100644 --- a/test/standalone/self_exe_symlink/create-symlink.zig +++ b/test/standalone/self_exe_symlink/create-symlink.zig @@ -9,7 +9,7 @@ pub fn main(init: std.process.Init) !void { const exe_path = it.next() orelse unreachable; const symlink_path = it.next() orelse unreachable; - const cwd = std.process.getCwdAlloc(init.arena.allocator()); + const cwd = try std.process.getCwdAlloc(init.arena.allocator()); // If `exe_path` is relative to our cwd, we need to convert it to be relative to the dirname of `symlink_path`. const exe_rel_path = try std.fs.path.relative(gpa, cwd, init.env_map, std.fs.path.dirname(symlink_path) orelse ".", exe_path); diff --git a/tools/fetch_them_macos_headers.zig b/tools/fetch_them_macos_headers.zig index c0d41b85ae991f29918b378e98c85185369fdad5..cac4675bd42aaf5f6af8cb35d3cb621b0e352103 100644 --- a/tools/fetch_them_macos_headers.zig +++ b/tools/fetch_them_macos_headers.zig @@ -6,13 +6,9 @@ const process = std.process; const assert = std.debug.assert; const fatal = std.process.fatal; const info = std.log.info; - -const Allocator = mem.Allocator; +const Allocator = std.mem.Allocator; const OsTag = std.Target.Os.Tag; -var general_purpose_allocator = std.heap.GeneralPurposeAllocator(.{}){}; -const gpa = general_purpose_allocator.allocator(); - const Arch = enum { aarch64, x86_64, @@ -67,10 +63,11 @@ const usage = ; pub fn main(init: std.process.Init) !void { - const allocator = init.arena; - const args = try init.minimal.args.toSlice(allocator); + const io = init.io; + const arena = init.arena.allocator(); + const args = try init.minimal.args.toSlice(arena); - var argv = std.array_list.Managed([]const u8).init(allocator); + var argv = std.array_list.Managed([]const u8).init(arena); var sysroot: ?[]const u8 = null; var args_iter = ArgsIterator{ .args = args[1..] }; @@ -82,23 +79,19 @@ pub fn main(init: std.process.Init) !void { } else try argv.append(arg); } - var threaded: Io.Threaded = .init(gpa, .{}); - defer threaded.deinit(); - const io = threaded.io(); - const sysroot_path = sysroot orelse blk: { const target = try std.zig.system.resolveTargetQuery(io, .{}); - break :blk std.zig.system.darwin.getSdk(allocator, io, &target) orelse + break :blk std.zig.system.darwin.getSdk(arena, io, &target) orelse fatal("no SDK found; you can provide one explicitly with '--sysroot' flag", .{}); }; var sdk_dir = try Dir.cwd().openDir(io, sysroot_path, .{}); defer sdk_dir.close(io); - const sdk_info = try sdk_dir.readFileAlloc(io, "SDKSettings.json", allocator, .limited(std.math.maxInt(u32))); + const sdk_info = try sdk_dir.readFileAlloc(io, "SDKSettings.json", arena, .limited(std.math.maxInt(u32))); const parsed_json = try std.json.parseFromSlice(struct { DefaultProperties: struct { MACOSX_DEPLOYMENT_TARGET: []const u8 }, - }, allocator, sdk_info, .{ .ignore_unknown_fields = true }); + }, arena, sdk_info, .{ .ignore_unknown_fields = true }); const version = Version.parse(parsed_json.value.DefaultProperties.MACOSX_DEPLOYMENT_TARGET) orelse fatal("don't know how to parse SDK version: {s}", .{ @@ -114,7 +107,7 @@ pub fn main(init: std.process.Init) !void { .arch = arch, .os_ver = os_ver, }; - try fetchTarget(allocator, io, argv.items, sysroot_path, target, version, tmp_dir); + try fetchTarget(arena, io, argv.items, sysroot_path, target, version, tmp_dir); } } diff --git a/tools/gen_macos_headers_c.zig b/tools/gen_macos_headers_c.zig index 393e17f321bbcb5a050b838970b8b25ff83b32ff..3e83761923e55de8fc9f54eb019d7b7f91007d5c 100644 --- a/tools/gen_macos_headers_c.zig +++ b/tools/gen_macos_headers_c.zig @@ -14,7 +14,7 @@ const usage = ; pub fn main(init: std.process.Init) !void { - const arena = init.arena; + const arena = init.arena.allocator(); const io = init.io; const args = try init.minimal.args.toSlice(arena); diff --git a/tools/process_headers.zig b/tools/process_headers.zig index cbd94c6292fffecd177572ee6962328bb169b7a2..a3e6bbc439abe3e42d102e1dbc41e6b6126643df 100644 --- a/tools/process_headers.zig +++ b/tools/process_headers.zig @@ -127,16 +127,14 @@ const LibCVendor = enum { netbsd, }; -pub fn main() !void { - var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); - const allocator = arena.allocator(); +pub fn main(init: std.process.Init) !void { + const arena = init.arena.allocator(); + const io = init.io; + const args = try init.minimal.args.toSlice(arena); + const cwd_path = try std.process.getCwdAlloc(arena); + const env_map = init.env_map; - var threaded: Io.Threaded = .init(allocator, .{}); - defer threaded.deinit(); - const io = threaded.io(); - - const args = try std.process.argsAlloc(allocator); - var search_paths = std.array_list.Managed([]const u8).init(allocator); + var search_paths = std.array_list.Managed([]const u8).init(arena); var opt_out_dir: ?[]const u8 = null; var opt_abi: ?[]const u8 = null; @@ -172,7 +170,7 @@ pub fn main() !void { usageAndExit(args[0]); }; - const generic_name = try std.fmt.allocPrint(allocator, "generic-{s}", .{abi_name}); + const generic_name = try std.fmt.allocPrint(arena, "generic-{s}", .{abi_name}); const libc_targets = switch (vendor) { .glibc => &glibc_targets, .musl => &musl_targets, @@ -180,8 +178,8 @@ pub fn main() !void { .netbsd => &netbsd_targets, }; - var path_table = PathTable.init(allocator); - var hash_to_contents = HashToContents.init(allocator); + var path_table = PathTable.init(arena); + var hash_to_contents = HashToContents.init(arena); var max_bytes_saved: usize = 0; var total_bytes: usize = 0; @@ -189,7 +187,7 @@ pub fn main() !void { for (libc_targets) |libc_target| { const libc_dir = switch (vendor) { - .glibc => try std.zig.target.glibcRuntimeTriple(allocator, libc_target.arch, .linux, libc_target.abi), + .glibc => try std.zig.target.glibcRuntimeTriple(arena, libc_target.arch, .linux, libc_target.abi), .musl => std.zig.target.muslArchName(libc_target.arch, libc_target.abi), .freebsd => switch (libc_target.arch) { .arm => "armv7", @@ -221,7 +219,7 @@ pub fn main() !void { }, }; - const dest_target = if (libc_target.dest) |dest| dest else try std.fmt.allocPrint(allocator, "{s}-{s}-{s}", .{ + const dest_target = if (libc_target.dest) |dest| dest else try std.fmt.allocPrint(arena, "{s}-{s}-{s}", .{ @tagName(libc_target.arch), switch (vendor) { .musl, .glibc => "linux", @@ -239,8 +237,8 @@ pub fn main() !void { => &[_][]const u8{ search_path, libc_dir, "usr", "include" }, .musl => &[_][]const u8{ search_path, libc_dir, "usr", "local", "musl", "include" }, }; - const target_include_dir = try Dir.path.join(allocator, sub_path); - var dir_stack = std.array_list.Managed([]const u8).init(allocator); + const target_include_dir = try Dir.path.join(arena, sub_path); + var dir_stack = std.array_list.Managed([]const u8).init(arena); try dir_stack.append(target_include_dir); while (dir_stack.pop()) |full_dir_name| { @@ -254,16 +252,16 @@ pub fn main() !void { var dir_it = dir.iterate(); while (try dir_it.next(io)) |entry| { - const full_path = try Dir.path.join(allocator, &[_][]const u8{ full_dir_name, entry.name }); + const full_path = try Dir.path.join(arena, &[_][]const u8{ full_dir_name, entry.name }); switch (entry.kind) { .directory => try dir_stack.append(full_path), .file, .sym_link => { - const rel_path = try Dir.path.relative(allocator, target_include_dir, full_path); + const rel_path = try Dir.path.relative(arena, cwd_path, env_map, target_include_dir, full_path); const max_size = 2 * 1024 * 1024 * 1024; - const raw_bytes = try Dir.cwd().readFileAlloc(io, full_path, allocator, .limited(max_size)); + const raw_bytes = try Dir.cwd().readFileAlloc(io, full_path, arena, .limited(max_size)); const trimmed = std.mem.trim(u8, raw_bytes, " \r\n\t"); total_bytes += raw_bytes.len; - const hash = try allocator.alloc(u8, 32); + const hash = try arena.alloc(u8, 32); hasher = Blake3.init(.{}); hasher.update(rel_path); hasher.update(trimmed); @@ -285,8 +283,8 @@ pub fn main() !void { } const path_gop = try path_table.getOrPut(rel_path); const target_to_hash = if (path_gop.found_existing) path_gop.value_ptr.* else blk: { - const ptr = try allocator.create(TargetToHash); - ptr.* = TargetToHash.init(allocator); + const ptr = try arena.create(TargetToHash); + ptr.* = TargetToHash.init(arena); path_gop.value_ptr.* = ptr; break :blk ptr; }; @@ -327,7 +325,7 @@ pub fn main() !void { // gets their header in a separate arch directory. var path_it = path_table.iterator(); while (path_it.next()) |path_kv| { - var contents_list = std.array_list.Managed(*Contents).init(allocator); + var contents_list = std.array_list.Managed(*Contents).init(arena); { var hash_it = path_kv.value_ptr.*.iterator(); while (hash_it.next()) |hash_kv| { @@ -339,7 +337,7 @@ pub fn main() !void { const best_contents = contents_list.pop().?; if (best_contents.hit_count > 1) { // worth it to make it generic - const full_path = try Dir.path.join(allocator, &[_][]const u8{ out_dir, generic_name, path_kv.key_ptr.* }); + const full_path = try Dir.path.join(arena, &[_][]const u8{ out_dir, generic_name, path_kv.key_ptr.* }); try Dir.cwd().createDirPath(io, Dir.path.dirname(full_path).?); try Dir.cwd().writeFile(io, .{ .sub_path = full_path, .data = best_contents.bytes }); best_contents.is_generic = true; @@ -360,7 +358,7 @@ pub fn main() !void { if (contents.is_generic) continue; const dest_target = hash_kv.key_ptr.*; - const full_path = try Dir.path.join(allocator, &[_][]const u8{ out_dir, dest_target, path_kv.key_ptr.* }); + const full_path = try Dir.path.join(arena, &[_][]const u8{ out_dir, dest_target, path_kv.key_ptr.* }); try Dir.cwd().createDirPath(io, Dir.path.dirname(full_path).?); try Dir.cwd().writeFile(io, .{ .sub_path = full_path, .data = contents.bytes }); } -- 2.54.0 From 77087f6f316e16cf374339750f7a5446c18d000d Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 2 Jan 2026 18:57:43 -0800 Subject: [PATCH 30/60] langref: update to new main API --- doc/langref.html.in | 3 +-- doc/langref/hello.zig | 14 ++---------- doc/langref/wasi_args.zig | 12 ++++------ doc/langref/wasi_preopens.zig | 14 +++--------- tools/doctest.zig | 42 +++++++++++++++++++++-------------- 5 files changed, 35 insertions(+), 50 deletions(-) diff --git a/doc/langref.html.in b/doc/langref.html.in index c0272124caeb9529e670e06c3464fd05ef369ada..c3aa3c9b1b443c588647ecbe4e8d9a1d6724812a 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -7027,8 +7027,7 @@ WebAssembly.instantiate(typedArray, { The result is 3{#end_shell_samp#} {#header_close#} {#header_open|WASI#} -

Zig's support for WebAssembly System Interface (WASI) is under active development. - Example of using the standard library and reading command line arguments:

+

Zig standard library has first-class support for WebAssembly System Interface.

{#code|wasi_args.zig#} {#shell_samp#}$ wasmtime wasi_args.wasm 123 hello diff --git a/doc/langref/hello.zig b/doc/langref/hello.zig index 3fc2fb98d58b1e65317102941f5a8a06f740e1a7..0986aaae7d4a4c9d39e2544bf2063dbc9cb8e42d 100644 --- a/doc/langref/hello.zig +++ b/doc/langref/hello.zig @@ -1,17 +1,7 @@ const std = @import("std"); -// See https://github.com/ziglang/zig/issues/24510 -// for the plan to simplify this code. -pub fn main() !void { - var debug_allocator: std.heap.DebugAllocator(.{}) = .init; - defer _ = debug_allocator.deinit(); - const gpa = debug_allocator.allocator(); - - var threaded: std.Io.Threaded = .init(gpa, .{}); - defer threaded.deinit(); - const io = threaded.io(); - - try std.Io.File.stdout().writeStreamingAll(io, "Hello, World!\n"); +pub fn main(init: std.process.Init) !void { + try std.Io.File.stdout().writeStreamingAll(init.io, "Hello, World!\n"); } // exe=succeed diff --git a/doc/langref/wasi_args.zig b/doc/langref/wasi_args.zig index 6801e67f0c7ad4fe57af4fe9bca6870524f2b81c..e1c8e532d3e8d6542c08c0b076f84cbed991a8b6 100644 --- a/doc/langref/wasi_args.zig +++ b/doc/langref/wasi_args.zig @@ -1,13 +1,9 @@ const std = @import("std"); -pub fn main() !void { - var general_purpose_allocator: std.heap.GeneralPurposeAllocator(.{}) = .init; - const gpa = general_purpose_allocator.allocator(); - const args = try std.process.argsAlloc(gpa); - defer std.process.argsFree(gpa, args); - - for (args, 0..) |arg, i| { - std.debug.print("{}: {s}\n", .{ i, arg }); +pub fn main(init: std.process.Init) !void { + const args = try init.minimal.args.toSlice(init.arena.allocator()); + for (0.., args) |i, arg| { + std.debug.print("{d}: {s}\n", .{ i, arg }); } } diff --git a/doc/langref/wasi_preopens.zig b/doc/langref/wasi_preopens.zig index 5a167bc8dbd96967193f256da71d89e5d5aee37d..99ab36f31483d318a3be9c254a4927427d2e4ca4 100644 --- a/doc/langref/wasi_preopens.zig +++ b/doc/langref/wasi_preopens.zig @@ -1,18 +1,10 @@ const std = @import("std"); -const fs = std.fs; -pub fn main() !void { - var general_purpose_allocator: std.heap.GeneralPurposeAllocator(.{}) = .init; - const gpa = general_purpose_allocator.allocator(); - - var arena_instance = std.heap.ArenaAllocator.init(gpa); - defer arena_instance.deinit(); - const arena = arena_instance.allocator(); - - const preopens = try fs.wasi.preopensAlloc(arena); +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("{}: {s}\n", .{ i, preopen }); + std.debug.print("{d}: {s}\n", .{ i, preopen }); } } diff --git a/tools/doctest.zig b/tools/doctest.zig index 377de25e93f88c6db5bfdd37413614a36d1b436e..7ec04ba8c217b84c7872abc0e2fcc13e65e6c127 100644 --- a/tools/doctest.zig +++ b/tools/doctest.zig @@ -32,6 +32,10 @@ const usage = pub fn main(init: std.process.Init) !void { const arena = init.arena.allocator(); const io = init.io; + const env_map = init.env_map; + const cwd_path = try std.process.getCwdAlloc(arena); + + try env_map.put("CLICOLOR_FORCE", "1"); var args_it = try init.minimal.args.iterateAllocator(arena); if (!args_it.skip()) fatal("missing argv[0]", .{}); @@ -97,12 +101,13 @@ pub fn main(init: std.process.Init) !void { out, code, tmp_dir_path, - try Dir.path.relative(arena, tmp_dir_path, zig_path), - try Dir.path.relative(arena, tmp_dir_path, input_path), + try Dir.path.relative(arena, cwd_path, env_map, tmp_dir_path, zig_path), + try Dir.path.relative(arena, cwd_path, env_map, tmp_dir_path, input_path), if (opt_zig_lib_dir) |zig_lib_dir| - try Dir.path.relative(arena, tmp_dir_path, zig_lib_dir) + try Dir.path.relative(arena, cwd_path, env_map, tmp_dir_path, zig_lib_dir) else null, + env_map, ); try out_file_writer.end(); @@ -121,10 +126,8 @@ fn printOutput( input_path: []const u8, /// Relative to `tmp_dir_path`. opt_zig_lib_dir: ?[]const u8, + env_map: *const process.Environ.Map, ) !void { - var env_map = try process.getEnvMap(arena); - try env_map.put("CLICOLOR_FORCE", "1"); - const host = try std.zig.system.resolveTargetQuery(io, .{}); const obj_ext = builtin.object_format.fileExt(builtin.cpu.arch); const print = std.debug.print; @@ -196,7 +199,7 @@ fn printOutput( const result = try process.run(arena, io, .{ .argv = build_args.items, .cwd = tmp_dir_path, - .env_map = &env_map, + .env_map = env_map, .max_output_bytes = max_doc_file_size, }); switch (result.term) { @@ -218,7 +221,7 @@ fn printOutput( try shell_out.writeAll(colored_stderr); break :code_block; } - const exec_result = run(arena, io, &env_map, tmp_dir_path, build_args.items) catch + const exec_result = run(arena, io, env_map, tmp_dir_path, build_args.items) catch fatal("example failed to compile", .{}); if (code.verbose_cimport) { @@ -251,7 +254,7 @@ fn printOutput( const result = if (expected_outcome == .fail) blk: { const result = try process.run(arena, io, .{ .argv = run_args, - .env_map = &env_map, + .env_map = env_map, .cwd = tmp_dir_path, .max_output_bytes = max_doc_file_size, }); @@ -268,7 +271,7 @@ fn printOutput( } break :blk result; } else blk: { - break :blk run(arena, io, &env_map, tmp_dir_path, run_args) catch + break :blk run(arena, io, env_map, tmp_dir_path, run_args) catch fatal("example crashed", .{}); }; @@ -337,7 +340,7 @@ fn printOutput( } } - const result = run(arena, io, &env_map, tmp_dir_path, test_args.items) catch + const result = run(arena, io, env_map, tmp_dir_path, test_args.items) catch fatal("test failed", .{}); const escaped_stderr = try escapeHtml(arena, result.stderr); const escaped_stdout = try escapeHtml(arena, result.stdout); @@ -370,7 +373,7 @@ fn printOutput( } const result = try process.run(arena, io, .{ .argv = test_args.items, - .env_map = &env_map, + .env_map = env_map, .cwd = tmp_dir_path, .max_output_bytes = max_doc_file_size, }); @@ -426,7 +429,7 @@ fn printOutput( const result = try process.run(arena, io, .{ .argv = test_args.items, - .env_map = &env_map, + .env_map = env_map, .cwd = tmp_dir_path, .max_output_bytes = max_doc_file_size, }); @@ -502,7 +505,7 @@ fn printOutput( if (maybe_error_match) |error_match| { const result = try process.run(arena, io, .{ .argv = build_args.items, - .env_map = &env_map, + .env_map = env_map, .cwd = tmp_dir_path, .max_output_bytes = max_doc_file_size, }); @@ -528,7 +531,7 @@ fn printOutput( const colored_stderr = try termColor(arena, escaped_stderr); try shell_out.print("\n{s} ", .{colored_stderr}); } else { - _ = run(arena, io, &env_map, tmp_dir_path, build_args.items) catch fatal("example failed to compile", .{}); + _ = run(arena, io, env_map, tmp_dir_path, build_args.items) catch fatal("example failed to compile", .{}); } try shell_out.writeAll("\n"); }, @@ -587,7 +590,7 @@ fn printOutput( try test_args.append(option); try shell_out.print("{s} ", .{option}); } - const result = run(arena, io, &env_map, tmp_dir_path, test_args.items) catch fatal("test failed", .{}); + const result = run(arena, io, env_map, tmp_dir_path, test_args.items) catch fatal("test failed", .{}); const escaped_stderr = try escapeHtml(arena, result.stderr); const escaped_stdout = try escapeHtml(arena, result.stdout); try shell_out.print("\n{s}{s}\n", .{ escaped_stderr, escaped_stdout }); @@ -1120,7 +1123,7 @@ fn in(slice: []const u8, number: u8) bool { fn run( allocator: Allocator, io: Io, - env_map: *process.Environ.Map, + env_map: *const process.Environ.Map, cwd: []const u8, args: []const []const u8, ) !process.RunResult { @@ -1138,6 +1141,11 @@ fn run( return error.ChildExitError; } }, + .signal => |sig| { + std.debug.print("{s}\nThe following command terminated with signal {t}:\n", .{ result.stderr, sig }); + dumpArgs(args); + return error.ChildCrashed; + }, else => { std.debug.print("{s}\nThe following command crashed:\n", .{result.stderr}); dumpArgs(args); -- 2.54.0 From d97e4ca0d1de34c8987e675b4d61312e42a1d984 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 2 Jan 2026 19:08:15 -0800 Subject: [PATCH 31/60] documentation should be descriptive not prescriptive --- lib/std/os/linux/syscalls.zig | 3 +-- tools/generate_linux_syscalls.zig | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/std/os/linux/syscalls.zig b/lib/std/os/linux/syscalls.zig index 0eec4dafee8fa9f0dda9ebb06033481c6a5f7cff..c111f518b6d617d4874f847bc883715b4a7a8344 100644 --- a/lib/std/os/linux/syscalls.zig +++ b/lib/std/os/linux/syscalls.zig @@ -1,5 +1,4 @@ -// This file is automatically generated, DO NOT edit it manually. -// See tools/generate_linux_syscalls.zig for more info. +// This file is automatically generated by tools/generate_linux_syscalls.zig // This list current as of kernel: 6.18.2 pub const X86 = enum(usize) { diff --git a/tools/generate_linux_syscalls.zig b/tools/generate_linux_syscalls.zig index e124a2c84d374648d696a766f4f570d5c492d09f..70d410f806b35999af5950f67cf8faecfb951fa7 100644 --- a/tools/generate_linux_syscalls.zig +++ b/tools/generate_linux_syscalls.zig @@ -212,8 +212,7 @@ pub fn main(init: std.process.Init) !void { }; try Io.Writer.print(stdout, - \\// This file is automatically generated, DO NOT edit it manually. - \\// See tools/generate_linux_syscalls.zig for more info. + \\// This file is automatically generated by tools/generate_linux_syscalls.zig \\// This list current as of kernel: {f} \\ \\ -- 2.54.0 From c6b75b61b7dc914c46de86aa764958f6954d28a4 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 2 Jan 2026 20:19:32 -0800 Subject: [PATCH 32/60] std: fix child processes on riscv32-linux --- lib/std/Io/Threaded.zig | 92 +++++++++++++++++++++++++++++++++-------- lib/std/os/linux.zig | 21 +++++++++- lib/std/posix.zig | 41 ------------------ 3 files changed, 94 insertions(+), 60 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index e9190f590b0ba1d514b1a9fa9c768a0efb0dffba..a515766e6ddf93846acc538aebe25709c3624494 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -1638,8 +1638,14 @@ const have_fchmod = switch (native_os) { else => true, }; +const have_waitid = switch (native_os) { + .linux => @hasField(std.os.linux.SYS, "waitid"), + else => false, +}; + const have_wait4 = switch (native_os) { - .dragonfly, .freebsd, .netbsd, .openbsd, .illumos, .linux, .serenity, .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => true, + .linux => @hasField(std.os.linux.SYS, "wait4"), + .dragonfly, .freebsd, .netbsd, .openbsd, .illumos, .serenity, .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => true, else => false, }; @@ -13019,7 +13025,7 @@ fn childWait(userdata: ?*anyopaque, child: *std.process.Child) process.Child.Wai const t: *Threaded = @ptrCast(@alignCast(userdata)); switch (native_os) { .windows => return childWaitWindows(t, child), - else => return childWaitPosix(t, child), + else => return childWaitPosix(Thread.getCurrent(t), child), } } @@ -13029,7 +13035,7 @@ fn childKill(userdata: ?*anyopaque, child: *std.process.Child) void { if (is_windows) { childKillWindows(t, child, 1) catch childCleanupWindows(child); } else { - childKillPosix(t, child) catch childCleanupPosix(child); + childKillPosix(Thread.getCurrent(t), child) catch childCleanupPosix(child); } } @@ -13109,20 +13115,71 @@ fn childCleanupWindows(child: *process.Child) void { } } -fn childWaitPosix(t: *Threaded, child: *process.Child) process.Child.WaitError!process.Child.Term { - _ = t; // TODO cancelation +fn childWaitPosix(current_thread: *Thread, child: *process.Child) process.Child.WaitError!process.Child.Term { + defer childCleanupPosix(child); + const pid = child.id.?; - const res: posix.WaitPidResult = res: { - if (child.request_resource_usage_statistics and have_wait4) { - var ru: posix.rusage = undefined; - const res = posix.wait4(pid, 0, &ru); - child.resource_usage_statistics.rusage = ru; - break :res res; - } - break :res posix.waitpid(pid, 0); + + var ru: posix.rusage = undefined; + const ru_ptr = if (child.request_resource_usage_statistics) &ru else null; + + if (have_wait4) { + var status: if (builtin.link_libc) c_int else u32 = undefined; + try current_thread.beginSyscall(); + while (true) switch (posix.errno(posix.system.wait4(pid, &status, 0, ru_ptr))) { + .SUCCESS => { + current_thread.endSyscall(); + if (ru_ptr) |p| child.resource_usage_statistics.rusage = p.*; + return statusToTerm(@bitCast(status)); + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + .CHILD => |err| return current_thread.endSyscallErrnoBug(err), // Double-free. + else => |err| return current_thread.endSyscallUnexpectedErrno(err), + }; + } + + if (have_waitid) { + const linux = std.os.linux; // Bypass libc which has the wrong signature. + var info: linux.siginfo_t = undefined; + try current_thread.beginSyscall(); + while (true) switch (linux.errno(linux.waitid(.PID, pid, &info, linux.W.EXITED, ru_ptr))) { + .SUCCESS => { + current_thread.endSyscall(); + if (ru_ptr) |p| child.resource_usage_statistics.rusage = p.*; + const status: u32 = @bitCast(info.fields.common.second.sigchld.status); + const code: linux.CLD = @enumFromInt(info.code); + return switch (code) { + .EXITED => .{ .exited = @truncate(status) }, + .KILLED, .DUMPED => .{ .signal = @enumFromInt(status) }, + .TRAPPED, .STOPPED => .{ .stopped = status }, + _, .CONTINUED => .{ .unknown = status }, + }; + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + .CHILD => |err| return current_thread.endSyscallErrnoBug(err), // Double-free. + else => |err| return current_thread.endSyscallUnexpectedErrno(err), + }; + } + + var status: if (builtin.link_libc) c_int else u32 = undefined; + while (true) switch (posix.errno(posix.system.waitpid(pid, &status, 0))) { + .SUCCESS => { + current_thread.endSyscall(); + return statusToTerm(@bitCast(status)); + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + .CHILD => |err| return current_thread.endSyscallErrnoBug(err), // Double-free. + else => |err| return current_thread.endSyscallUnexpectedErrno(err), }; - childCleanupPosix(child); - return statusToTerm(res.status); } fn statusToTerm(status: u32) process.Child.Term { @@ -13136,7 +13193,8 @@ fn statusToTerm(status: u32) process.Child.Term { .{ .unknown = status }; } -fn childKillPosix(t: *Threaded, child: *process.Child) !void { +fn childKillPosix(current_thread: *Thread, child: *process.Child) !void { + // Intentionally uncancelable. while (true) switch (posix.errno(posix.system.kill(child.id.?, .TERM))) { .SUCCESS => break, .INTR => continue, @@ -13145,7 +13203,7 @@ fn childKillPosix(t: *Threaded, child: *process.Child) !void { .SRCH => |err| return errnoBug(err), else => |err| return posix.unexpectedErrno(err), }; - _ = try childWaitPosix(t, child); + _ = try childWaitPosix(current_thread, child); } fn childCleanupPosix(child: *process.Child) void { diff --git a/lib/std/os/linux.zig b/lib/std/os/linux.zig index 68baa9c626e638fbe4c214a1bfaf3b700e2a259e..a1626113ace96ba6df88f0f861e731371d041822 100644 --- a/lib/std/os/linux.zig +++ b/lib/std/os/linux.zig @@ -1598,8 +1598,15 @@ pub fn wait4(pid: pid_t, status: *u32, flags: u32, usage: ?*rusage) usize { ); } -pub fn waitid(id_type: P, id: i32, infop: *siginfo_t, flags: u32) usize { - return syscall5(.waitid, @intFromEnum(id_type), @as(usize, @bitCast(@as(isize, id))), @intFromPtr(infop), flags, 0); +pub fn waitid(id_type: P, id: i32, infop: *siginfo_t, flags: u32, usage: ?*rusage) usize { + return syscall5( + .waitid, + @intFromEnum(id_type), + @as(usize, @bitCast(@as(isize, id))), + @intFromPtr(infop), + flags, + @intFromPtr(usage), + ); } pub const F = struct { @@ -6205,6 +6212,16 @@ const siginfo_fields_union = extern union { }, }; +pub const CLD = enum(i32) { + EXITED = 1, + KILLED = 2, + DUMPED = 3, + TRAPPED = 4, + STOPPED = 5, + CONTINUED = 6, + _, +}; + pub const siginfo_t = if (is_mips) extern struct { signo: SIG, diff --git a/lib/std/posix.zig b/lib/std/posix.zig index 72ddd276763ae3d81ccf0d10ce0542465d831a49..c703897d98d24a57222713892eca66defc2a47a7 100644 --- a/lib/std/posix.zig +++ b/lib/std/posix.zig @@ -1690,47 +1690,6 @@ pub fn getsockoptError(sockfd: fd_t) ConnectError!void { } } -pub const WaitPidResult = struct { - pid: pid_t, - status: u32, -}; - -/// Use this version of the `waitpid` wrapper if you spawned your child process using explicit -/// `fork` and `execve` method. -pub fn waitpid(pid: pid_t, flags: u32) WaitPidResult { - var status: if (builtin.link_libc) c_int else u32 = undefined; - while (true) { - const rc = system.waitpid(pid, &status, @intCast(flags)); - switch (errno(rc)) { - .SUCCESS => return .{ - .pid = @intCast(rc), - .status = @bitCast(status), - }, - .INTR => continue, - .CHILD => unreachable, // The process specified does not exist. It would be a race condition to handle this error. - .INVAL => unreachable, // Invalid flags. - else => unreachable, - } - } -} - -pub fn wait4(pid: pid_t, flags: u32, ru: ?*rusage) WaitPidResult { - var status: if (builtin.link_libc) c_int else u32 = undefined; - while (true) { - const rc = system.wait4(pid, &status, @intCast(flags), ru); - switch (errno(rc)) { - .SUCCESS => return .{ - .pid = @intCast(rc), - .status = @bitCast(status), - }, - .INTR => continue, - .CHILD => unreachable, // The process specified does not exist. It would be a race condition to handle this error. - .INVAL => unreachable, // Invalid flags. - else => unreachable, - } - } -} - pub const FStatError = std.Io.File.StatError; /// Return information about a file descriptor. -- 2.54.0 From 17c7a339d87b8029436c676582e9c54720e6c180 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 2 Jan 2026 20:35:13 -0800 Subject: [PATCH 33/60] incr-check: update to new APIs --- tools/incr-check.zig | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/tools/incr-check.zig b/tools/incr-check.zig index 16f37b1bd02a69bb8a13e75c492d737bfea8f411..1404d44f71f8b35ee2d63149808bd5280e8c3c61 100644 --- a/tools/incr-check.zig +++ b/tools/incr-check.zig @@ -29,8 +29,10 @@ fn logImpl( pub fn main(init: std.process.Init) !void { const fatal = std.process.fatal; - const arena = init.arena; + const arena = init.arena.allocator(); const io = init.io; + const env_map = init.env_map; + const cwd_path = try std.process.getCwdAlloc(arena); var opt_zig_exe: ?[]const u8 = null; var opt_input_file_name: ?[]const u8 = null; @@ -44,7 +46,7 @@ pub fn main(init: std.process.Init) !void { var debug_log_args: std.ArrayList([]const u8) = .empty; - var arg_it = try init.minimal.argsIterator(arena); + var arg_it = try init.minimal.args.iterateAllocator(arena); _ = arg_it.skip(); while (arg_it.next()) |arg| { if (arg.len > 0 and arg[0] == '-') { @@ -111,9 +113,9 @@ pub fn main(init: std.process.Init) !void { } // Convert paths to be relative to the cwd of the subprocess. - const resolved_zig_exe = try Dir.path.relative(arena, tmp_dir_path, zig_exe); + const resolved_zig_exe = try Dir.path.relative(arena, cwd_path, env_map, tmp_dir_path, zig_exe); const opt_resolved_lib_dir = if (opt_lib_dir) |lib_dir| - try Dir.path.relative(arena, tmp_dir_path, lib_dir) + try Dir.path.relative(arena, cwd_path, env_map, tmp_dir_path, lib_dir) else null; @@ -174,7 +176,7 @@ pub fn main(init: std.process.Init) !void { var cc_child_args: std.ArrayList([]const u8) = .empty; if (target.backend == .cbe) { const resolved_cc_zig_exe = if (opt_cc_zig) |cc_zig_exe| - try Dir.path.relative(arena, tmp_dir_path, cc_zig_exe) + try Dir.path.relative(arena, cwd_path, env_map, tmp_dir_path, cc_zig_exe) else resolved_zig_exe; @@ -193,7 +195,7 @@ pub fn main(init: std.process.Init) !void { try cc_child_args.append(arena, "-o"); } - var child = std.process.spawn(io, .{ + var child = try std.process.spawn(io, .{ .argv = child_args.items, .stdin = .pipe, .stdout = .pipe, @@ -644,7 +646,7 @@ const Eval = struct { eval.tmp_dir.close(io); if (!eval.preserve_tmp_on_fatal) { // Kill the child since it holds an open handle to its CWD which is the tmp dir path - _ = eval.child.kill(io) catch {}; + eval.child.kill(io); Dir.cwd().deleteTree(io, eval.tmp_dir_path) catch |err| { std.log.warn("failed to delete tree '{s}': {t}", .{ eval.tmp_dir_path, err }); }; -- 2.54.0 From f25de4c7a238d46a20c6a22e3b951ee09ecfb962 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 2 Jan 2026 20:46:24 -0800 Subject: [PATCH 34/60] fix native path lookup on macOS --- lib/std/zig.zig | 3 +++ lib/std/zig/system/NativePaths.zig | 4 +--- test/standalone/posix/getenv.zig | 9 ++------- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/lib/std/zig.zig b/lib/std/zig.zig index 0a24a242d721a8810f3944d61aff65be069f33f5..4f0f47b11f3dfe5b39bcbbef50b562f7a3f87189 100644 --- a/lib/std/zig.zig +++ b/lib/std/zig.zig @@ -763,6 +763,9 @@ pub const EnvVar = enum { // Windows SDK integration PROGRAMDATA, + // Homebrew integration + HOMEBREW_PREFIX, + pub fn isSet(ev: EnvVar, map: *const std.process.Environ.Map) bool { return map.contains(@tagName(ev)); } diff --git a/lib/std/zig/system/NativePaths.zig b/lib/std/zig/system/NativePaths.zig index 02f59d4b03dcb8846446874879868c854b03c807..fd717f38b30212936f9da9ccdbc86260012dfb5f 100644 --- a/lib/std/zig/system/NativePaths.zig +++ b/lib/std/zig/system/NativePaths.zig @@ -121,7 +121,7 @@ pub fn detect( } // Check for homebrew paths - if (std.posix.getenv("HOMEBREW_PREFIX")) |prefix| { + if (std.zig.EnvVar.HOMEBREW_PREFIX.get(env_map)) |prefix| { try self.addLibDir(try std.fs.path.join(arena, &.{ prefix, "/lib" })); try self.addIncludeDir(try std.fs.path.join(arena, &.{ prefix, "/include" })); } @@ -177,8 +177,6 @@ pub fn detect( // Distros like guix don't use FHS, so they rely on environment // variables to search for headers and libraries. - // We use os.getenv here since this part won't be executed on - // windows, to get rid of unnecessary error handling. if (std.zig.EnvVar.C_INCLUDE_PATH.get(env_map)) |c_include_path| { var it = mem.tokenizeScalar(u8, c_include_path, ':'); while (it.next()) |dir| { diff --git a/test/standalone/posix/getenv.zig b/test/standalone/posix/getenv.zig index ea4f9c4877cb5f3fd2fe8715300720e70e3743b3..62c348e085b93dfee378b84953096879cbc575cc 100644 --- a/test/standalone/posix/getenv.zig +++ b/test/standalone/posix/getenv.zig @@ -4,13 +4,8 @@ const std = @import("std"); const builtin = @import("builtin"); pub fn main(init: std.process.Init.Minimal) !void { - if (builtin.target.os.tag == .windows) { - return; // Windows env strings are WTF-16, so not supported by Zig's std.posix.getenv() - } - - if (builtin.target.os.tag == .wasi and !builtin.link_libc) { - return; // std.posix.getenv is not supported on WASI due to the need of allocation - } + if (builtin.target.os.tag == .windows) return; + if (builtin.target.os.tag == .wasi and !builtin.link_libc) return; const environ = init.environ; -- 2.54.0 From 1070c2a71a89175461273eba9f49bb85bdc83ecd Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 2 Jan 2026 21:57:47 -0800 Subject: [PATCH 35/60] rename env_map to environ_map For naming consistency with `std.process.Environ.Map`. --- lib/compiler/aro/aro/Compilation.zig | 4 +- lib/compiler/build_runner.zig | 10 +- lib/compiler/libc.zig | 6 +- lib/compiler/resinator/compile.zig | 4 +- lib/compiler/resinator/main.zig | 20 +-- lib/compiler/resinator/preprocess.zig | 4 +- lib/compiler/translate-c/main.zig | 8 +- lib/std/Build.zig | 13 +- lib/std/Build/Step.zig | 6 +- lib/std/Build/Step/Compile.zig | 4 +- lib/std/Build/Step/Options.zig | 2 +- lib/std/Build/Step/Run.zig | 68 ++++---- lib/std/Build/WebServer.zig | 2 +- lib/std/Io/Threaded.zig | 6 +- lib/std/fs/path.zig | 20 +-- lib/std/http/Client.zig | 10 +- lib/std/process.zig | 10 +- lib/std/start.zig | 6 +- lib/std/zig/LibCDirs.zig | 6 +- lib/std/zig/LibCInstallation.zig | 40 ++--- lib/std/zig/WindowsSdk.zig | 28 +-- lib/std/zig/system/NativePaths.zig | 16 +- src/Compilation.zig | 8 +- src/introspect.zig | 10 +- src/main.zig | 160 +++++++++--------- src/print_env.zig | 10 +- test/standalone/child_process/main.zig | 6 +- test/standalone/empty_env/main.zig | 2 +- test/standalone/env_vars/main.zig | 28 +-- .../self_exe_symlink/create-symlink.zig | 2 +- test/standalone/windows_bat_args/fuzz.zig | 2 +- test/standalone/windows_bat_args/test.zig | 2 +- test/standalone/windows_paths/test.zig | 4 +- tools/doctest.zig | 38 ++--- tools/incr-check.zig | 8 +- tools/process_headers.zig | 4 +- tools/update-linux-headers.zig | 4 +- 37 files changed, 290 insertions(+), 291 deletions(-) diff --git a/lib/compiler/aro/aro/Compilation.zig b/lib/compiler/aro/aro/Compilation.zig index fc38a8b2bf2c970c281fb0fd9329d4101a3895ae..6f3a1a44da119e3e1b27750a551efb852df21d33 100644 --- a/lib/compiler/aro/aro/Compilation.zig +++ b/lib/compiler/aro/aro/Compilation.zig @@ -193,14 +193,14 @@ pub fn initDefault( io: Io, diagnostics: *Diagnostics, cwd: Io.Dir, - env_map: *const std.process.Environ.Map, + environ_map: *const std.process.Environ.Map, ) !Compilation { var comp: Compilation = .{ .gpa = gpa, .arena = arena, .io = io, .diagnostics = diagnostics, - .environment = try Environment.loadAll(gpa, env_map), + .environment = try Environment.loadAll(gpa, environ_map), .cwd = cwd, }; errdefer comp.deinit(); diff --git a/lib/compiler/build_runner.zig b/lib/compiler/build_runner.zig index 6b225b205d2e5b7d2d2d0dac43b9c54084378fc3..0d18d835a4295550f304f1dd1c854e20e6538f89 100644 --- a/lib/compiler/build_runner.zig +++ b/lib/compiler/build_runner.zig @@ -87,7 +87,7 @@ pub fn main(init: process.Init.Minimal) !void { .cwd = try process.getCwdAlloc(single_threaded_arena.allocator()), }, .zig_exe = zig_exe, - .env_map = try init.environ.createMap(arena), + .environ_map = try init.environ.createMap(arena), .global_cache_root = global_cache_directory, .zig_lib_directory = zig_lib_directory, .host = .{ @@ -130,13 +130,13 @@ pub fn main(init: process.Init.Minimal) !void { var debounce_interval_ms: u16 = 50; var webui_listen: ?Io.net.IpAddress = null; - if (std.zig.EnvVar.ZIG_BUILD_ERROR_STYLE.get(&graph.env_map)) |str| { + if (std.zig.EnvVar.ZIG_BUILD_ERROR_STYLE.get(&graph.environ_map)) |str| { if (std.meta.stringToEnum(ErrorStyle, str)) |style| { error_style = style; } } - if (std.zig.EnvVar.ZIG_BUILD_MULTILINE_ERRORS.get(&graph.env_map)) |str| { + if (std.zig.EnvVar.ZIG_BUILD_MULTILINE_ERRORS.get(&graph.environ_map)) |str| { if (std.meta.stringToEnum(MultilineErrors, str)) |style| { multiline_errors = style; } @@ -433,8 +433,8 @@ pub fn main(init: process.Init.Minimal) !void { } } - const NO_COLOR = std.zig.EnvVar.NO_COLOR.isSet(&graph.env_map); - const CLICOLOR_FORCE = std.zig.EnvVar.CLICOLOR_FORCE.isSet(&graph.env_map); + const NO_COLOR = std.zig.EnvVar.NO_COLOR.isSet(&graph.environ_map); + const CLICOLOR_FORCE = std.zig.EnvVar.CLICOLOR_FORCE.isSet(&graph.environ_map); graph.stderr_mode = switch (color) { .auto => try .detect(io, .stderr(), NO_COLOR, CLICOLOR_FORCE), diff --git a/lib/compiler/libc.zig b/lib/compiler/libc.zig index a27e96dab6fd94864ba229e8ff4ff6903b665f25..8ca53fefc905c01527c7157bee4c0d10575e582e 100644 --- a/lib/compiler/libc.zig +++ b/lib/compiler/libc.zig @@ -29,7 +29,7 @@ pub fn main(init: std.process.Init) !void { const gpa = init.gpa; const io = init.io; const args = try init.minimal.args.toSlice(arena); - const env_map = init.env_map; + const environ_map = init.environ_map; const zig_lib_directory = args[1]; @@ -92,7 +92,7 @@ pub fn main(init: std.process.Init) !void { is_native_abi, true, libc_installation, - env_map, + environ_map, ) catch |err| { const zig_target = try target.zigTriple(arena); fatal("unable to detect libc for target {s}: {t}", .{ zig_target, err }); @@ -123,7 +123,7 @@ pub fn main(init: std.process.Init) !void { var libc = LibCInstallation.findNative(gpa, io, .{ .verbose = true, .target = &target, - .env_map = env_map, + .environ_map = environ_map, }) catch |err| { fatal("unable to detect native libc: {t}", .{err}); }; diff --git a/lib/compiler/resinator/compile.zig b/lib/compiler/resinator/compile.zig index 76d6a1463608f3c4f3c7fe80d5bae6d1e2c39f25..a315ea488e6be0a7673f0c48b430b75271939304 100644 --- a/lib/compiler/resinator/compile.zig +++ b/lib/compiler/resinator/compile.zig @@ -80,7 +80,7 @@ pub const Dependencies = struct { } }; -pub fn compile(allocator: Allocator, io: Io, source: []const u8, writer: *std.Io.Writer, options: CompileOptions, env_map: *const std.process.Environ.Map) !void { +pub fn compile(allocator: Allocator, io: Io, source: []const u8, writer: *std.Io.Writer, options: CompileOptions, environ_map: *const std.process.Environ.Map) !void { var lexer = lex.Lexer.init(source, .{ .default_code_page = options.default_code_page, .source_mappings = options.source_mappings, @@ -148,7 +148,7 @@ pub fn compile(allocator: Allocator, io: Io, source: []const u8, writer: *std.Io try search_dirs.append(allocator, .{ .dir = dir, .path = try allocator.dupe(u8, system_include_path) }); } if (!options.ignore_include_env_var) { - const INCLUDE = env_map.get("INCLUDE") orelse ""; + const INCLUDE = environ_map.get("INCLUDE") orelse ""; // The only precedence here is llvm-rc which also uses the platform-specific // delimiter. There's no precedence set by `rc.exe` since it's Windows-only. diff --git a/lib/compiler/resinator/main.zig b/lib/compiler/resinator/main.zig index 58f539274caf3be7b4daab0486c13ef151f0b697..9e654b4947bfb87b11d8586c8d1f3a3d9dc11885 100644 --- a/lib/compiler/resinator/main.zig +++ b/lib/compiler/resinator/main.zig @@ -24,8 +24,8 @@ pub fn main(init: std.process.Init.Minimal) !void { defer std.debug.assert(debug_allocator.deinit() == .ok); const gpa = debug_allocator.allocator(); - var env_map = try init.environ.createMap(gpa); - defer env_map.deinit(); + var environ_map = try init.environ.createMap(gpa); + defer environ_map.deinit(); var threaded: std.Io.Threaded = .init(gpa, .{ .environ = init.environ, @@ -151,8 +151,8 @@ pub fn main(init: std.process.Init.Minimal) !void { defer argv.deinit(aro_arena); try argv.append(aro_arena, "arocc"); // dummy command name - const resolved_include_paths = try include_paths.get(&error_handler, &env_map); - try preprocess.appendAroArgs(aro_arena, &argv, options, resolved_include_paths, &env_map); + const resolved_include_paths = try include_paths.get(&error_handler, &environ_map); + try preprocess.appendAroArgs(aro_arena, &argv, options, resolved_include_paths, &environ_map); try argv.append(aro_arena, switch (options.input_source) { .stdio => "-", .filename => |filename| filename, @@ -286,7 +286,7 @@ pub fn main(init: std.process.Init.Minimal) !void { .dependencies = maybe_dependencies, .ignore_include_env_var = options.ignore_include_env_var, .extra_include_paths = options.extra_include_paths.items, - .system_include_paths = try include_paths.get(&error_handler, &env_map), + .system_include_paths = try include_paths.get(&error_handler, &environ_map), .default_language_id = options.default_language_id, .default_code_page = default_code_page, .disjoint_code_page = has_disjoint_code_page, @@ -295,7 +295,7 @@ pub fn main(init: std.process.Init.Minimal) !void { .max_string_literal_codepoints = options.max_string_literal_codepoints, .silent_duplicate_control_ids = options.silent_duplicate_control_ids, .warn_instead_of_error_on_invalid_code_page = options.warn_instead_of_error_on_invalid_code_page, - }, &env_map) catch |err| switch (err) { + }, &environ_map) catch |err| switch (err) { error.ParseError, error.CompileError => { try error_handler.emitDiagnostics(gpa, Io.Dir.cwd(), final_input, &diagnostics, mapping_results.mappings); // Delete the output file on error @@ -545,7 +545,7 @@ const LazyIncludePaths = struct { pub fn get( self: *LazyIncludePaths, error_handler: *ErrorHandler, - env_map: *const std.process.Environ.Map, + environ_map: *const std.process.Environ.Map, ) ![]const []const u8 { const io = self.io; @@ -558,7 +558,7 @@ const LazyIncludePaths = struct { self.auto_includes_option, self.zig_lib_dir, self.target_machine_type, - env_map, + environ_map, ) catch |err| switch (err) { error.OutOfMemory => |e| return e, else => |e| { @@ -586,7 +586,7 @@ fn getIncludePaths( auto_includes_option: cli.Options.AutoIncludes, zig_lib_dir: []const u8, target_machine_type: std.coff.IMAGE.FILE.MACHINE, - env_map: *const std.process.Environ.Map, + environ_map: *const std.process.Environ.Map, ) ![]const []const u8 { if (auto_includes_option == .none) return &[_][]const u8{}; @@ -667,7 +667,7 @@ fn getIncludePaths( is_native_abi, true, null, - env_map, + environ_map, ) catch |err| switch (err) { error.OutOfMemory => |e| return e, else => return error.MingwIncludesNotFound, diff --git a/lib/compiler/resinator/preprocess.zig b/lib/compiler/resinator/preprocess.zig index 39e01f2f4120986dd7f68a24cb35f737a71074af..1d8c038b60eaf6d7aa5e4e31b465e2a7e479816f 100644 --- a/lib/compiler/resinator/preprocess.zig +++ b/lib/compiler/resinator/preprocess.zig @@ -86,7 +86,7 @@ fn hasAnyErrors(comp: *aro.Compilation) bool { /// `arena` is used for temporary -D argument strings and the INCLUDE environment variable. /// The arena should be kept alive at least as long as `argv`. -pub fn appendAroArgs(arena: Allocator, argv: *std.ArrayList([]const u8), options: cli.Options, system_include_paths: []const []const u8, env_map: *const std.process.Environ.Map) !void { +pub fn appendAroArgs(arena: Allocator, argv: *std.ArrayList([]const u8), options: cli.Options, system_include_paths: []const []const u8, environ_map: *const std.process.Environ.Map) !void { try argv.appendSlice(arena, &.{ "-E", "--comments", @@ -109,7 +109,7 @@ pub fn appendAroArgs(arena: Allocator, argv: *std.ArrayList([]const u8), options } if (!options.ignore_include_env_var) { - const INCLUDE = env_map.get("INCLUDE") orelse ""; + const INCLUDE = environ_map.get("INCLUDE") orelse ""; // The only precedence here is llvm-rc which also uses the platform-specific // delimiter. There's no precedence set by `rc.exe` since it's Windows-only. diff --git a/lib/compiler/translate-c/main.zig b/lib/compiler/translate-c/main.zig index d85c89a62783675bbb3d0ba2901b4b2e2c0945e1..ce8f4f55c3b6d4060df197f382e8cfa746fbd3d2 100644 --- a/lib/compiler/translate-c/main.zig +++ b/lib/compiler/translate-c/main.zig @@ -13,7 +13,7 @@ pub fn main(init: std.process.Init) u8 { const gpa = init.gpa; const arena = init.arena.allocator(); const io = init.io; - const env_map = init.env_map; + const environ_map = init.environ_map; const args = init.minimal.args.toSlice(arena) catch { std.debug.print("ran out of memory allocating arguments\n", .{}); @@ -26,8 +26,8 @@ pub fn main(init: std.process.Init) u8 { zig_integration = true; } - const NO_COLOR = std.zig.EnvVar.NO_COLOR.isSet(env_map); - const CLICOLOR_FORCE = std.zig.EnvVar.CLICOLOR_FORCE.isSet(env_map); + const NO_COLOR = std.zig.EnvVar.NO_COLOR.isSet(environ_map); + const CLICOLOR_FORCE = std.zig.EnvVar.CLICOLOR_FORCE.isSet(environ_map); var stderr_buf: [1024]u8 = undefined; var stderr = Io.File.stderr().writer(io, &stderr_buf); @@ -42,7 +42,7 @@ pub fn main(init: std.process.Init) u8 { }; defer diagnostics.deinit(); - var comp = aro.Compilation.initDefault(gpa, arena, io, &diagnostics, .cwd(), env_map) catch |err| switch (err) { + var comp = aro.Compilation.initDefault(gpa, arena, io, &diagnostics, .cwd(), environ_map) catch |err| switch (err) { error.OutOfMemory => { std.debug.print("ran out of memory initializing C compilation\n", .{}); if (fast_exit) process.exit(1); diff --git a/lib/std/Build.zig b/lib/std/Build.zig index db88a114619f9c234b930d3d778c7524207df32d..1724025beaa37480303d93dfb568da768799fa56 100644 --- a/lib/std/Build.zig +++ b/lib/std/Build.zig @@ -12,7 +12,6 @@ const StringHashMap = std.StringHashMap; const Allocator = std.mem.Allocator; const Target = std.Target; const process = std.process; -const EnvMap = std.process.Environ.Map; const File = std.Io.File; const Sha256 = std.crypto.hash.sha2.Sha256; const ArrayList = std.ArrayList; @@ -118,7 +117,7 @@ pub const Graph = struct { debug_compiler_runtime_libs: bool = false, cache: Cache, zig_exe: [:0]const u8, - env_map: EnvMap, + environ_map: process.Environ.Map, global_cache_root: Cache.Directory, zig_lib_directory: Cache.Directory, needed_lazy_dependencies: std.StringArrayHashMapUnmanaged(void) = .empty, @@ -289,7 +288,7 @@ pub fn create( .lib_dir = undefined, .exe_dir = undefined, .h_dir = undefined, - .dest_dir = graph.env_map.get("DESTDIR"), + .dest_dir = graph.environ_map.get("DESTDIR"), .install_tls = .{ .step = .init(.{ .id = TopLevelStep.base_id, @@ -1772,7 +1771,7 @@ fn tryFindProgram(b: *Build, full_path: []const u8) ?[]const u8 { } if (builtin.os.tag == .windows) { - if (b.graph.env_map.get("PATHEXT")) |PATHEXT| { + if (b.graph.environ_map.get("PATHEXT")) |PATHEXT| { var it = mem.tokenizeScalar(u8, PATHEXT, fs.path.delimiter); while (it.next()) |ext| { @@ -1803,7 +1802,7 @@ pub fn findProgram(b: *Build, names: []const []const u8, paths: []const []const return tryFindProgram(b, b.pathJoin(&.{ search_prefix, "bin", name })) orelse continue; } } - if (b.graph.env_map.get("PATH")) |PATH| { + if (b.graph.environ_map.get("PATH")) |PATH| { for (names) |name| { if (fs.path.isAbsolute(name)) { return name; @@ -1840,11 +1839,11 @@ pub fn runAllowFail( const io = graph.io; const max_output_size = 400 * 1024; - try Step.handleVerbose2(b, null, &graph.env_map, argv); + try Step.handleVerbose2(b, null, &graph.environ_map, argv); var child = try std.process.spawn(io, .{ .argv = argv, - .env_map = &graph.env_map, + .environ_map = &graph.environ_map, .stdin = .ignore, .stdout = .pipe, .stderr = stderr_behavior, diff --git a/lib/std/Build/Step.zig b/lib/std/Build/Step.zig index 745894551dd7f9e4239daba15b216e7a36688f24..28141cebe536778ee7503521952404e7d25a0e58 100644 --- a/lib/std/Build/Step.zig +++ b/lib/std/Build/Step.zig @@ -362,7 +362,7 @@ pub fn captureChildProcess( const result = std.process.run(arena, io, .{ .argv = argv, - .env_map = &graph.env_map, + .environ_map = &graph.environ_map, .progress_node = progress_node, }) catch |err| return s.fail("failed to run {s}: {t}", .{ argv[0], err }); @@ -453,7 +453,7 @@ pub fn evalZigProcess( zp.child = std.process.spawn(io, .{ .argv = argv, - .env_map = &b.graph.env_map, + .environ_map = &b.graph.environ_map, .stdin = .pipe, .stdout = .pipe, .stderr = .pipe, @@ -702,7 +702,7 @@ pub fn handleVerbose2( // stderr before spawning them. const text = try allocPrintCmd(b.allocator, opt_cwd, if (opt_env) |env| .{ .child = env, - .parent = &graph.env_map, + .parent = &graph.environ_map, } else null, argv); std.debug.print("{s}\n", .{text}); } diff --git a/lib/std/Build/Step/Compile.zig b/lib/std/Build/Step/Compile.zig index 915f7343865d950c3b7cc020cc131ac570f95236..2403436c73818693a142a42b82bf67acaa5c69a3 100644 --- a/lib/std/Build/Step/Compile.zig +++ b/lib/std/Build/Step/Compile.zig @@ -741,7 +741,7 @@ fn runPkgConfig(compile: *Compile, lib_name: []const u8) !PkgConfigResult { }; var code: u8 = undefined; - const pkg_config_exe = b.graph.env_map.get("PKG_CONFIG") orelse "pkg-config"; + const pkg_config_exe = b.graph.environ_map.get("PKG_CONFIG") orelse "pkg-config"; const stdout = if (b.runAllowFail(&[_][]const u8{ pkg_config_exe, pkg_name, @@ -1846,7 +1846,7 @@ pub fn doAtomicSymLinks( } fn execPkgConfigList(b: *std.Build, out_code: *u8) (PkgConfigError || RunError)![]const PkgConfigPkg { - const pkg_config_exe = b.graph.env_map.get("PKG_CONFIG") orelse "pkg-config"; + const pkg_config_exe = b.graph.environ_map.get("PKG_CONFIG") orelse "pkg-config"; const stdout = try b.runAllowFail(&[_][]const u8{ pkg_config_exe, "--list-all" }, out_code, .ignore); var list = std.array_list.Managed(PkgConfigPkg).init(b.allocator); errdefer list.deinit(); diff --git a/lib/std/Build/Step/Options.zig b/lib/std/Build/Step/Options.zig index 614ff515d45e9fb1f8f4f89e105a45d31c11a165..9bb91606208fed0af89168ad1d452cb9c84ffe71 100644 --- a/lib/std/Build/Step/Options.zig +++ b/lib/std/Build/Step/Options.zig @@ -550,7 +550,7 @@ test Options { .cwd = cwd, }, .zig_exe = "test", - .env_map = std.process.Environ.Map.init(arena.allocator()), + .environ_map = std.process.Environ.Map.init(arena.allocator()), .global_cache_root = .{ .path = "test", .handle = Io.Dir.cwd() }, .host = .{ .query = .{}, diff --git a/lib/std/Build/Step/Run.zig b/lib/std/Build/Step/Run.zig index a1acb2c37f615304b597c5ebe7f79cb640301724..7ae9849f645f11a5f9b37396ecb2bef9ce415d18 100644 --- a/lib/std/Build/Step/Run.zig +++ b/lib/std/Build/Step/Run.zig @@ -23,7 +23,7 @@ argv: std.ArrayList(Arg), cwd: ?Build.LazyPath, /// Override this field to modify the environment, or use setEnvironmentVariable -env_map: ?*EnvMap, +environ_map: ?*EnvMap, /// Controls the `NO_COLOR` and `CLICOLOR_FORCE` environment variables. color: Color = .auto, @@ -215,7 +215,7 @@ pub fn create(owner: *std.Build, name: []const u8) *Run { }), .argv = .{}, .cwd = null, - .env_map = null, + .environ_map = null, .disable_zig_progress = false, .stdio = .infer_from_args, .stdin = .none, @@ -540,12 +540,12 @@ pub fn clearEnvironment(run: *Run) void { const b = run.step.owner; const new_env_map = b.allocator.create(EnvMap) catch @panic("OOM"); new_env_map.* = .init(b.allocator); - run.env_map = new_env_map; + run.environ_map = new_env_map; } pub fn addPathDir(run: *Run, search_path: []const u8) void { const b = run.step.owner; - const env_map = getEnvMapInternal(run); + const environ_map = getEnvMapInternal(run); const use_wine = b.enable_wine and b.graph.host.result.os.tag != .windows and use_wine: switch (run.argv.items[0]) { .artifact => |p| p.artifact.rootModuleTarget().os.tag == .windows, @@ -562,7 +562,7 @@ pub fn addPathDir(run: *Run, search_path: []const u8) void { .output_file, .output_directory => false, }; const key = if (use_wine) "WINEPATH" else "PATH"; - const prev_path = env_map.get(key); + const prev_path = environ_map.get(key); if (prev_path) |pp| { const new_path = b.fmt("{s}{c}{s}", .{ @@ -570,9 +570,9 @@ pub fn addPathDir(run: *Run, search_path: []const u8) void { if (use_wine) Dir.path.delimiter_windows else Dir.path.delimiter, search_path, }); - env_map.put(key, new_path) catch @panic("OOM"); + environ_map.put(key, new_path) catch @panic("OOM"); } else { - env_map.put(key, b.dupePath(search_path)) catch @panic("OOM"); + environ_map.put(key, b.dupePath(search_path)) catch @panic("OOM"); } } @@ -583,18 +583,18 @@ pub fn getEnvMap(run: *Run) *EnvMap { fn getEnvMapInternal(run: *Run) *EnvMap { const graph = run.step.owner.graph; const arena = graph.arena; - return run.env_map orelse { + return run.environ_map orelse { const cloned_map = arena.create(EnvMap) catch @panic("OOM"); - cloned_map.* = graph.env_map.clone(arena) catch @panic("OOM"); - run.env_map = cloned_map; + cloned_map.* = graph.environ_map.clone(arena) catch @panic("OOM"); + run.environ_map = cloned_map; return cloned_map; }; } pub fn setEnvironmentVariable(run: *Run, key: []const u8, value: []const u8) void { - const env_map = run.getEnvMap(); + const environ_map = run.getEnvMap(); // This data structure already dupes keys and values. - env_map.put(key, value) catch @panic("OOM"); + environ_map.put(key, value) catch @panic("OOM"); } pub fn removeEnvironmentVariable(run: *Run, key: []const u8) void { @@ -762,7 +762,7 @@ fn convertPathArg(run: *Run, path: Build.Cache.Path) []const u8 { const child_lazy_cwd = run.cwd orelse break :rel path_str; const child_cwd = child_lazy_cwd.getPath3(b, &run.step).toString(arena) catch @panic("OOM"); // Convert it from relative to *our* cwd, to relative to the *child's* cwd. - break :rel Dir.path.relative(arena, graph.cache.cwd, &graph.env_map, child_cwd, path_str) catch @panic("OOM"); + break :rel Dir.path.relative(arena, graph.cache.cwd, &graph.environ_map, child_cwd, path_str) catch @panic("OOM"); }; // Not every path can be made relative, e.g. if the path and the child cwd are on different // disk designators on Windows. In that case, `relative` will return an absolute path which we can @@ -794,8 +794,8 @@ fn make(step: *Step, options: Step.MakeOptions) !void { var man = b.graph.cache.obtain(); defer man.deinit(); - if (run.env_map) |env_map| { - for (env_map.keys(), env_map.values()) |key, value| { + if (run.environ_map) |environ_map| { + for (environ_map.keys(), environ_map.values()) |key, value| { man.hash.addBytes(key); man.hash.addBytes(value); } @@ -1222,7 +1222,7 @@ fn runCommand( const cwd: ?[]const u8 = if (run.cwd) |lazy_cwd| lazy_cwd.getPath2(b, step) else null; try step.handleChildProcUnsupported(); - try Step.handleVerbose2(step.owner, cwd, run.env_map, argv); + try Step.handleVerbose2(step.owner, cwd, run.environ_map, argv); const allow_skip = switch (run.stdio) { .check, .zig_test => run.skip_foreign_checks, @@ -1232,13 +1232,13 @@ fn runCommand( var interp_argv = std.array_list.Managed([]const u8).init(b.allocator); defer interp_argv.deinit(); - var env_map: EnvMap = env: { - const orig = run.env_map orelse &b.graph.env_map; + var environ_map: EnvMap = env: { + const orig = run.environ_map orelse &b.graph.environ_map; break :env try orig.clone(gpa); }; - defer env_map.deinit(); + defer environ_map.deinit(); - const opt_generic_result = spawnChildAndCollect(run, argv, &env_map, has_side_effects, options, fuzz_context) catch |err| term: { + const opt_generic_result = spawnChildAndCollect(run, argv, &environ_map, has_side_effects, options, fuzz_context) catch |err| term: { // InvalidExe: cpu arch mismatch // FileNotFound: can happen with a wrong dynamic linker path if (err == error.InvalidExe or err == error.FileNotFound) interpret: { @@ -1274,8 +1274,8 @@ fn runCommand( // Wine's excessive stderr logging is only situationally helpful. Disable it by default, but // allow the user to override it (e.g. with `WINEDEBUG=err+all`) if desired. - if (env_map.get("WINEDEBUG") == null) { - try env_map.put("WINEDEBUG", "-all"); + if (environ_map.get("WINEDEBUG") == null) { + try environ_map.put("WINEDEBUG", "-all"); } } else { return failForeign(run, "-fwine", argv[0], exe); @@ -1372,9 +1372,9 @@ fn runCommand( gpa.free(step.result_failed_command.?); step.result_failed_command = null; - try Step.handleVerbose2(step.owner, cwd, run.env_map, interp_argv.items); + try Step.handleVerbose2(step.owner, cwd, run.environ_map, interp_argv.items); - break :term spawnChildAndCollect(run, interp_argv.items, &env_map, has_side_effects, options, fuzz_context) catch |e| { + break :term spawnChildAndCollect(run, interp_argv.items, &environ_map, has_side_effects, options, fuzz_context) catch |e| { if (!run.failing_to_execute_foreign_is_an_error) return error.MakeSkipped; if (e == error.MakeFailed) return error.MakeFailed; // error already reported return step.fail("unable to spawn interpreter {s}: {s}", .{ @@ -1529,7 +1529,7 @@ const EvalGenericResult = struct { fn spawnChildAndCollect( run: *Run, argv: []const []const u8, - env_map: *EnvMap, + environ_map: *EnvMap, has_side_effects: bool, options: Step.MakeOptions, fuzz_context: ?FuzzContext, @@ -1548,14 +1548,14 @@ fn spawnChildAndCollect( // If an error occurs, it's caused by this command: assert(run.step.result_failed_command == null); run.step.result_failed_command = try Step.allocPrintCmd(options.gpa, child_cwd, .{ - .child = env_map, - .parent = &graph.env_map, + .child = environ_map, + .parent = &graph.environ_map, }, argv); var spawn_options: process.SpawnOptions = .{ .argv = argv, .cwd = child_cwd, - .env_map = env_map, + .environ_map = environ_map, .request_resource_usage_statistics = true, .stdin = if (run.stdin != .none) s: { assert(run.stdio != .inherit); @@ -1595,7 +1595,7 @@ fn spawnChildAndCollect( break :m stderr.terminal_mode; } else .no_color; defer if (inherit) io.unlockStderr(); - try setColorEnvironmentVariables(run, env_map, terminal_mode); + try setColorEnvironmentVariables(run, environ_map, terminal_mode); var timer = try std.time.Timer.start(); const res = try evalGeneric(run, spawn_options); run.step.result_duration_ns = timer.read(); @@ -1603,16 +1603,16 @@ fn spawnChildAndCollect( } } -fn setColorEnvironmentVariables(run: *Run, env_map: *EnvMap, terminal_mode: Io.Terminal.Mode) !void { +fn setColorEnvironmentVariables(run: *Run, environ_map: *EnvMap, terminal_mode: Io.Terminal.Mode) !void { color: switch (run.color) { .manual => {}, .enable => { - try env_map.put("CLICOLOR_FORCE", "1"); - _ = env_map.swapRemove("NO_COLOR"); + try environ_map.put("CLICOLOR_FORCE", "1"); + _ = environ_map.swapRemove("NO_COLOR"); }, .disable => { - try env_map.put("NO_COLOR", "1"); - _ = env_map.swapRemove("CLICOLOR_FORCE"); + try environ_map.put("NO_COLOR", "1"); + _ = environ_map.swapRemove("CLICOLOR_FORCE"); }, .inherit => switch (terminal_mode) { .no_color, .windows_api => continue :color .disable, diff --git a/lib/std/Build/WebServer.zig b/lib/std/Build/WebServer.zig index 9d1ad45524e9ee20e63b9132bbfece57bf667e2f..cb201e991d78a6256426535d0d86cb3517b998bd 100644 --- a/lib/std/Build/WebServer.zig +++ b/lib/std/Build/WebServer.zig @@ -568,7 +568,7 @@ fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.Optim var child = try std.process.spawn(io, .{ .argv = argv.items, - .env_map = &graph.env_map, + .environ_map = &graph.environ_map, .stdin = .pipe, .stdout = .pipe, .stderr = .pipe, diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index a515766e6ddf93846acc538aebe25709c3624494..cd5ac69680504860187482e6c44a91f5fa8a111d 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -12887,8 +12887,8 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp const envp: [*:null]const ?[*:0]const u8 = m: { const prog_fd: i32 = if (prog_pipe[1] == -1) -1 else prog_fileno; - if (options.env_map) |env_map| { - break :m (try env_map.createBlockPosix(arena, .{ + if (options.environ_map) |environ_map| { + break :m (try environ_map.createBlockPosix(arena, .{ .zig_progress_fd = prog_fd, })).ptr; } @@ -13436,7 +13436,7 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro const cwd_w = if (options.cwd) |cwd| try std.unicode.wtf8ToWtf16LeAllocZ(arena, cwd) else null; const cwd_w_ptr = if (cwd_w) |cwd| cwd.ptr else null; - const maybe_envp_buf = if (options.env_map) |env_map| try env_map.createBlockWindows(arena) else null; + const maybe_envp_buf = if (options.environ_map) |environ_map| try environ_map.createBlockWindows(arena) else null; const envp_ptr = if (maybe_envp_buf) |envp_buf| envp_buf.ptr else null; const app_name_wtf8 = options.argv[0]; diff --git a/lib/std/fs/path.zig b/lib/std/fs/path.zig index 4af6547249d76904317e5faf14731ca6643a8ec0..cd189cc12246df7437c9a8d21d386afadebc980f 100644 --- a/lib/std/fs/path.zig +++ b/lib/std/fs/path.zig @@ -1506,16 +1506,16 @@ fn testBasenameWindows(input: []const u8, expected_output: []const u8) !void { /// on each), a zero-length string is returned. /// /// See `relativePosix` and `relativeWindows` for operating system specific -/// details and for how `env_map` is used. +/// details and for how `environ_map` is used. pub fn relative( gpa: Allocator, cwd: []const u8, - env_map: ?*const std.process.Environ.Map, + environ_map: ?*const std.process.Environ.Map, from: []const u8, to: []const u8, ) Allocator.Error![]u8 { if (native_os == .windows) { - return relativeWindows(gpa, cwd, env_map, from, to); + return relativeWindows(gpa, cwd, environ_map, from, to); } else { return relativePosix(gpa, cwd, from, to); } @@ -1536,12 +1536,12 @@ pub fn relative( /// Per-drive CWDs are stored in special semi-hidden environment variables of /// the format `=:`, e.g. `=C:`. This type of CWD is purely a /// shell concept, so there's no guarantee that it'll be set or that it'll even -/// be accurate. This is the only reason for the `env_map` parameter. `null` is +/// be accurate. This is the only reason for the `environ_map` parameter. `null` is /// treated equivalent to the environment variable missing. pub fn relativeWindows( gpa: Allocator, cwd: []const u8, - env_map: ?*const std.process.Environ.Map, + environ_map: ?*const std.process.Environ.Map, from: []const u8, to: []const u8, ) Allocator.Error![]u8 { @@ -1565,13 +1565,13 @@ pub fn relativeWindows( }; if (result_is_always_to) { - return windowsResolveAgainstCwd(gpa, cwd, env_map, to, parsed_to); + return windowsResolveAgainstCwd(gpa, cwd, environ_map, to, parsed_to); } - const resolved_from = try windowsResolveAgainstCwd(gpa, cwd, env_map, from, parsed_from); + const resolved_from = try windowsResolveAgainstCwd(gpa, cwd, environ_map, from, parsed_from); defer gpa.free(resolved_from); var clean_up_resolved_to = true; - const resolved_to = try windowsResolveAgainstCwd(gpa, cwd, env_map, to, parsed_to); + const resolved_to = try windowsResolveAgainstCwd(gpa, cwd, environ_map, to, parsed_to); defer if (clean_up_resolved_to) gpa.free(resolved_to); const parsed_resolved_from = parsePathWindows(u8, resolved_from); @@ -1637,7 +1637,7 @@ pub fn relativeWindows( fn windowsResolveAgainstCwd( gpa: Allocator, cwd: []const u8, - env_map: ?*const std.process.Environ.Map, + environ_map: ?*const std.process.Environ.Map, path: []const u8, parsed: WindowsPath2(u8), ) ![]u8 { @@ -1679,7 +1679,7 @@ fn windowsResolveAgainstCwd( if (drive_letters_match) break :drive_cwd cwd; - if (env_map) |m| { + if (environ_map) |m| { if (m.get(&.{ '=', parsed.root[0], ':' })) |v| { break :drive_cwd try temp_allocator.dupe(u8, v); } diff --git a/lib/std/http/Client.zig b/lib/std/http/Client.zig index d07ba89c6116cea401231a354a1eb7e91107657e..3979a18029e9b4ddb01635a247691fc4af11b3a2 100644 --- a/lib/std/http/Client.zig +++ b/lib/std/http/Client.zig @@ -1307,7 +1307,7 @@ pub fn deinit(client: *Client) void { /// Asserts the client has no active connections. /// Uses `arena` for a few small allocations that must outlive the client, or /// at least until those fields are set to different values. -pub fn initDefaultProxies(client: *Client, arena: Allocator, env_map: *std.process.Environ.Map) !void { +pub fn initDefaultProxies(client: *Client, arena: Allocator, environ_map: *std.process.Environ.Map) !void { // Prevent any new connections from being created. client.connection_pool.mutex.lock(); defer client.connection_pool.mutex.unlock(); @@ -1315,13 +1315,13 @@ pub fn initDefaultProxies(client: *Client, arena: Allocator, env_map: *std.proce assert(client.connection_pool.used.first == null); // There are active requests. if (client.http_proxy == null) { - client.http_proxy = try createProxyFromEnvVar(arena, env_map, &.{ + client.http_proxy = try createProxyFromEnvVar(arena, environ_map, &.{ "http_proxy", "HTTP_PROXY", "all_proxy", "ALL_PROXY", }); } if (client.https_proxy == null) { - client.https_proxy = try createProxyFromEnvVar(arena, env_map, &.{ + client.https_proxy = try createProxyFromEnvVar(arena, environ_map, &.{ "https_proxy", "HTTPS_PROXY", "all_proxy", "ALL_PROXY", }); } @@ -1329,11 +1329,11 @@ pub fn initDefaultProxies(client: *Client, arena: Allocator, env_map: *std.proce fn createProxyFromEnvVar( arena: Allocator, - env_map: *std.process.Environ.Map, + environ_map: *std.process.Environ.Map, env_var_names: []const []const u8, ) !?*Proxy { const content = for (env_var_names) |name| { - const content = env_map.get(name) orelse continue; + const content = environ_map.get(name) orelse continue; if (content.len == 0) continue; break content; } else return null; diff --git a/lib/std/process.zig b/lib/std/process.zig index c226b0c453a33bdfc87f13d8681016aadb972705..96da85ea1774052984fa73e6fc47afdb07091752 100644 --- a/lib/std/process.zig +++ b/lib/std/process.zig @@ -46,7 +46,7 @@ pub const Init = struct { /// configuration. Debug mode will set up leak checking. io: Io, /// Environment variables, initialized with `gpa`. Not threadsafe. - env_map: *Environ.Map, + environ_map: *Environ.Map, /// Alternative to `Init` as the first parameter of the main function. pub const Minimal = struct { @@ -295,7 +295,7 @@ pub const ReplaceOptions = struct { arg0_expand: ArgExpansion = .no_expand, /// Replaces the environment when provided. The PATH value from here is /// never used to resolve `argv[0]`. - env_map: ?*const Environ.Map = null, + environ_map: ?*const Environ.Map = null, }; /// Replaces the current process image with the executed process. If this @@ -377,7 +377,7 @@ pub const SpawnOptions = struct { /// Replaces the child environment when provided. The PATH value from here /// is not used to resolve `argv[0]`; that resolution always uses parent /// environment. - env_map: ?*const Environ.Map = null, + environ_map: ?*const Environ.Map = null, expand_arg0: ArgExpansion = .no_expand, /// When populated, a pipe will be created for the child process to /// communicate progress back to the parent. The file descriptor of the @@ -475,7 +475,7 @@ pub const RunOptions = struct { /// Replaces the child environment when provided. The PATH value from here /// is not used to resolve `argv[0]`; that resolution always uses parent /// environment. - env_map: ?*const Environ.Map = null, + environ_map: ?*const Environ.Map = null, expand_arg0: ArgExpansion = .no_expand, /// When populated, a pipe will be created for the child process to /// communicate progress back to the parent. The file descriptor of the @@ -505,7 +505,7 @@ pub fn run(gpa: Allocator, io: Io, options: RunOptions) RunError!RunResult { .argv = options.argv, .cwd = options.cwd, .cwd_dir = options.cwd_dir, - .env_map = options.env_map, + .environ_map = options.environ_map, .expand_arg0 = options.expand_arg0, .progress_node = options.progress_node, .create_no_window = options.create_no_window, diff --git a/lib/std/start.zig b/lib/std/start.zig index 543e871d277d8ec531b84a5c7339c56c87e28340..bebfc852f2473fd80376bdffcc661b11d0d1236c 100644 --- a/lib/std/start.zig +++ b/lib/std/start.zig @@ -699,9 +699,9 @@ inline fn callMain(args: std.process.Args.Vector, environ: std.process.Environ.B }); defer threaded.deinit(); - var env_map = std.process.Environ.createMap(.{ .block = environ }, gpa) catch |err| + var environ_map = std.process.Environ.createMap(.{ .block = environ }, gpa) catch |err| std.process.fatal("failed to parse environment variables: {t}", .{err}); - defer env_map.deinit(); + defer environ_map.deinit(); return wrapMain(root.main(.{ .minimal = .{ @@ -711,7 +711,7 @@ inline fn callMain(args: std.process.Args.Vector, environ: std.process.Environ.B .arena = &arena_allocator, .gpa = gpa, .io = threaded.io(), - .env_map = &env_map, + .environ_map = &environ_map, })); } diff --git a/lib/std/zig/LibCDirs.zig b/lib/std/zig/LibCDirs.zig index 45a51e9d2978e5d93f0e62f33b12a6ba0cee885b..ca43fe127f8f51c7446c93793fd6b94119d1cf3e 100644 --- a/lib/std/zig/LibCDirs.zig +++ b/lib/std/zig/LibCDirs.zig @@ -28,7 +28,7 @@ pub fn detect( is_native_abi: bool, link_libc: bool, libc_installation: ?*const LibCInstallation, - env_map: *const std.process.Environ.Map, + environ_map: *const std.process.Environ.Map, ) LibCInstallation.FindError!LibCDirs { if (!link_libc) { return .{ @@ -50,7 +50,7 @@ pub fn detect( const libc = try arena.create(LibCInstallation); libc.* = LibCInstallation.findNative(arena, io, .{ .target = target, - .env_map = env_map, + .environ_map = environ_map, }) catch |err| switch (err) { error.CCompilerExitCode, error.CCompilerCrashed, @@ -91,7 +91,7 @@ pub fn detect( libc.* = try LibCInstallation.findNative(arena, io, .{ .verbose = true, .target = target, - .env_map = env_map, + .environ_map = environ_map, }); return detectFromInstallation(arena, target, libc); } diff --git a/lib/std/zig/LibCInstallation.zig b/lib/std/zig/LibCInstallation.zig index 2f7650b0fea25a95a6ab320f9213e8e398e28397..02b3df54dce74ee89913d717747f3740d08c8d37 100644 --- a/lib/std/zig/LibCInstallation.zig +++ b/lib/std/zig/LibCInstallation.zig @@ -168,7 +168,7 @@ pub fn render(self: LibCInstallation, out: *std.Io.Writer) !void { pub const FindNativeOptions = struct { target: *const std.Target, - env_map: *const Environ.Map, + environ_map: *const Environ.Map, /// If enabled, will print human-friendly errors to stderr. verbose: bool = false, @@ -193,7 +193,7 @@ pub fn findNative(gpa: Allocator, io: Io, args: FindNativeOptions) FindError!Lib }); return self; } else if (is_windows) { - const sdk = std.zig.WindowsSdk.find(gpa, io, args.target.cpu.arch, args.env_map) catch |err| switch (err) { + const sdk = std.zig.WindowsSdk.find(gpa, io, args.target.cpu.arch, args.environ_map) catch |err| switch (err) { error.NotFound => return error.WindowsSdkNotFound, error.PathTooLong => return error.WindowsSdkNotFound, error.OutOfMemory => return error.OutOfMemory, @@ -240,17 +240,17 @@ pub fn deinit(self: *LibCInstallation, allocator: Allocator) void { fn findNativeIncludeDirPosix(self: *LibCInstallation, gpa: Allocator, io: Io, args: FindNativeOptions) FindError!void { // Detect infinite loops. - var env_map = try args.env_map.clone(gpa); - defer env_map.deinit(); - const skip_cc_env_var = if (env_map.get(inf_loop_env_key)) |phase| blk: { + var environ_map = try args.environ_map.clone(gpa); + defer environ_map.deinit(); + const skip_cc_env_var = if (environ_map.get(inf_loop_env_key)) |phase| blk: { if (std.mem.eql(u8, phase, "1")) { - try env_map.put(inf_loop_env_key, "2"); + try environ_map.put(inf_loop_env_key, "2"); break :blk true; } else { return error.ZigIsTheCCompiler; } } else blk: { - try env_map.put(inf_loop_env_key, "1"); + try environ_map.put(inf_loop_env_key, "1"); break :blk false; }; @@ -259,7 +259,7 @@ fn findNativeIncludeDirPosix(self: *LibCInstallation, gpa: Allocator, io: Io, ar var argv = std.array_list.Managed([]const u8).init(gpa); defer argv.deinit(); - try appendCcExe(&argv, skip_cc_env_var, &env_map); + try appendCcExe(&argv, skip_cc_env_var, &environ_map); try argv.appendSlice(&.{ "-E", "-Wp,-v", @@ -270,7 +270,7 @@ fn findNativeIncludeDirPosix(self: *LibCInstallation, gpa: Allocator, io: Io, ar const run_res = std.process.run(gpa, io, .{ .max_output_bytes = 1024 * 1024, .argv = argv.items, - .env_map = &env_map, + .environ_map = &environ_map, // Some C compilers, such as Clang, are known to rely on argv[0] to find the path // to their own executable, without even bothering to resolve PATH. This results in the message: // error: unable to execute command: Executable "" doesn't exist! @@ -446,7 +446,7 @@ fn findNativeCrtDirWindows( fn findNativeCrtDirPosix(self: *LibCInstallation, gpa: Allocator, io: Io, args: FindNativeOptions) FindError!void { self.crt_dir = try ccPrintFileName(gpa, io, .{ - .env_map = args.env_map, + .environ_map = args.environ_map, .search_basename = switch (args.target.os.tag) { .linux => if (args.target.abi.isAndroid()) "crtbegin_dynamic.o" else "crt1.o", else => "crt1.o", @@ -551,7 +551,7 @@ fn findNativeMsvcLibDir( } pub const CCPrintFileNameOptions = struct { - env_map: *const Environ.Map, + environ_map: *const Environ.Map, search_basename: []const u8, want_dirname: enum { full_path, only_dir }, verbose: bool = false, @@ -560,17 +560,17 @@ pub const CCPrintFileNameOptions = struct { /// caller owns returned memory fn ccPrintFileName(gpa: Allocator, io: Io, args: CCPrintFileNameOptions) ![]u8 { // Detect infinite loops. - var env_map = try args.env_map.clone(gpa); - defer env_map.deinit(); - const skip_cc_env_var = if (env_map.get(inf_loop_env_key)) |phase| blk: { + var environ_map = try args.environ_map.clone(gpa); + defer environ_map.deinit(); + const skip_cc_env_var = if (environ_map.get(inf_loop_env_key)) |phase| blk: { if (std.mem.eql(u8, phase, "1")) { - try env_map.put(inf_loop_env_key, "2"); + try environ_map.put(inf_loop_env_key, "2"); break :blk true; } else { return error.ZigIsTheCCompiler; } } else blk: { - try env_map.put(inf_loop_env_key, "1"); + try environ_map.put(inf_loop_env_key, "1"); break :blk false; }; @@ -580,13 +580,13 @@ fn ccPrintFileName(gpa: Allocator, io: Io, args: CCPrintFileNameOptions) ![]u8 { const arg1 = try std.fmt.allocPrint(gpa, "-print-file-name={s}", .{args.search_basename}); defer gpa.free(arg1); - try appendCcExe(&argv, skip_cc_env_var, &env_map); + try appendCcExe(&argv, skip_cc_env_var, &environ_map); try argv.append(arg1); const run_res = std.process.run(gpa, io, .{ .max_output_bytes = 1024 * 1024, .argv = argv.items, - .env_map = &env_map, + .environ_map = &environ_map, // Some C compilers, such as Clang, are known to rely on argv[0] to find the path // to their own executable, without even bothering to resolve PATH. This results in the message: // error: unable to execute command: Executable "" doesn't exist! @@ -669,7 +669,7 @@ const inf_loop_env_key = "ZIG_IS_DETECTING_LIBC_PATHS"; fn appendCcExe( args: *std.array_list.Managed([]const u8), skip_cc_env_var: bool, - env_map: *const Environ.Map, + environ_map: *const Environ.Map, ) !void { const default_cc_exe = if (is_windows) "cc.exe" else "cc"; try args.ensureUnusedCapacity(1); @@ -677,7 +677,7 @@ fn appendCcExe( args.appendAssumeCapacity(default_cc_exe); return; } - const cc_env_var = std.zig.EnvVar.CC.get(env_map) orelse { + const cc_env_var = std.zig.EnvVar.CC.get(environ_map) orelse { args.appendAssumeCapacity(default_cc_exe); return; }; diff --git a/lib/std/zig/WindowsSdk.zig b/lib/std/zig/WindowsSdk.zig index cbffff4af76d6c09fac28188862f8b9a8a76ebf1..d6e7e35130c5da5f50405f06d1afd73005fcf358 100644 --- a/lib/std/zig/WindowsSdk.zig +++ b/lib/std/zig/WindowsSdk.zig @@ -29,7 +29,7 @@ pub fn find( gpa: Allocator, io: Io, arch: std.Target.Cpu.Arch, - env_map: *const Environ.Map, + environ_map: *const Environ.Map, ) error{ OutOfMemory, NotFound, PathTooLong }!WindowsSdk { if (builtin.os.tag != .windows) return error.NotFound; @@ -55,7 +55,7 @@ pub fn find( }; errdefer if (windows81sdk) |*w| w.free(gpa); - const msvc_lib_dir: ?[]const u8 = MsvcLibDir.find(gpa, io, arch, env_map) catch |err| switch (err) { + const msvc_lib_dir: ?[]const u8 = MsvcLibDir.find(gpa, io, arch, environ_map) catch |err| switch (err) { error.MsvcLibDirNotFound => null, error.OutOfMemory => return error.OutOfMemory, }; @@ -680,7 +680,7 @@ const MsvcLibDir = struct { fn findInstancesDir( gpa: Allocator, io: Io, - env_map: *const Environ.Map, + environ_map: *const Environ.Map, ) error{ OutOfMemory, PathNotFound }!Dir { // First, try getting the packages cache path from the registry. // This only seems to exist when the path is different from the default. @@ -701,7 +701,7 @@ const MsvcLibDir = struct { // If that can't be found, fall back to manually appending // `Microsoft\VisualStudio\Packages\_Instances` to %PROGRAMDATA% method3: { - const program_data = std.zig.EnvVar.PROGRAMDATA.get(env_map) orelse break :method3; + const program_data = std.zig.EnvVar.PROGRAMDATA.get(environ_map) orelse break :method3; if (!Dir.path.isAbsolute(program_data)) break :method3; @@ -765,13 +765,13 @@ const MsvcLibDir = struct { gpa: Allocator, io: Io, arch: std.Target.Cpu.Arch, - env_map: *const Environ.Map, + environ_map: *const Environ.Map, ) error{ OutOfMemory, PathNotFound }![]const u8 { // Typically `%PROGRAMDATA%\Microsoft\VisualStudio\Packages\_Instances` // This will contain directories with names of instance IDs like 80a758ca, // which will contain `state.json` files that have the version and // installation directory. - var instances_dir = try findInstancesDir(gpa, io, env_map); + var instances_dir = try findInstancesDir(gpa, io, environ_map); defer instances_dir.close(io); var state_subpath_buf: [Dir.max_name_bytes + 32]u8 = undefined; @@ -872,12 +872,12 @@ const MsvcLibDir = struct { gpa: Allocator, io: Io, arch: std.Target.Cpu.Arch, - env_map: *const Environ.Map, + environ_map: *const Environ.Map, ) error{ OutOfMemory, PathNotFound }![]const u8 { // %localappdata%\Microsoft\VisualStudio\ // %appdata%\Local\Microsoft\VisualStudio\ - const local_app_data_path = std.zig.EnvVar.LOCALAPPDATA.get(env_map) orelse return error.PathNotFound; + const local_app_data_path = std.zig.EnvVar.LOCALAPPDATA.get(environ_map) orelse return error.PathNotFound; const visualstudio_folder_path = try Dir.path.join(gpa, &.{ local_app_data_path, "Microsoft\\VisualStudio\\", }); @@ -968,11 +968,11 @@ const MsvcLibDir = struct { gpa: Allocator, io: Io, arch: std.Target.Cpu.Arch, - env_map: *const Environ.Map, + environ_map: *const Environ.Map, ) error{ OutOfMemory, PathNotFound }![]const u8 { var base_path: std.array_list.Managed(u8) = base_path: { try_env: { - if (env_map.get("VS140COMNTOOLS")) |VS140COMNTOOLS| { + if (environ_map.get("VS140COMNTOOLS")) |VS140COMNTOOLS| { if (VS140COMNTOOLS.len < "C:\\Common7\\Tools".len) break :try_env; if (!Dir.path.isAbsolute(VS140COMNTOOLS)) break :try_env; var list = std.array_list.Managed(u8).init(gpa); @@ -1046,13 +1046,13 @@ const MsvcLibDir = struct { gpa: Allocator, io: Io, arch: std.Target.Cpu.Arch, - env_map: *const Environ.Map, + environ_map: *const Environ.Map, ) error{ OutOfMemory, MsvcLibDirNotFound }![]const u8 { - const full_path = MsvcLibDir.findViaCOM(gpa, io, arch, env_map) catch |err1| switch (err1) { + const full_path = MsvcLibDir.findViaCOM(gpa, io, arch, environ_map) catch |err1| switch (err1) { error.OutOfMemory => return error.OutOfMemory, - error.PathNotFound => MsvcLibDir.findViaRegistry(gpa, io, arch, env_map) catch |err2| switch (err2) { + error.PathNotFound => MsvcLibDir.findViaRegistry(gpa, io, arch, environ_map) catch |err2| switch (err2) { error.OutOfMemory => return error.OutOfMemory, - error.PathNotFound => MsvcLibDir.findViaVs7Key(gpa, io, arch, env_map) catch |err3| switch (err3) { + error.PathNotFound => MsvcLibDir.findViaVs7Key(gpa, io, arch, environ_map) catch |err3| switch (err3) { error.OutOfMemory => return error.OutOfMemory, error.PathNotFound => return error.MsvcLibDirNotFound, }, diff --git a/lib/std/zig/system/NativePaths.zig b/lib/std/zig/system/NativePaths.zig index fd717f38b30212936f9da9ccdbc86260012dfb5f..f8003cd62061b4cfeb936533448e9a4b9f20dbb4 100644 --- a/lib/std/zig/system/NativePaths.zig +++ b/lib/std/zig/system/NativePaths.zig @@ -18,12 +18,12 @@ pub fn detect( arena: Allocator, io: Io, native_target: *const std.Target, - env_map: *process.Environ.Map, + environ_map: *process.Environ.Map, ) !NativePaths { var self: NativePaths = .{ .arena = arena }; var is_nix = false; - if (std.zig.EnvVar.NIX_CFLAGS_COMPILE.get(env_map)) |nix_cflags_compile| { + if (std.zig.EnvVar.NIX_CFLAGS_COMPILE.get(environ_map)) |nix_cflags_compile| { is_nix = true; var it = mem.tokenizeScalar(u8, nix_cflags_compile, ' '); while (true) { @@ -49,7 +49,7 @@ pub fn detect( } } - if (std.zig.EnvVar.NIX_LDFLAGS.get(env_map)) |nix_ldflags| { + if (std.zig.EnvVar.NIX_LDFLAGS.get(environ_map)) |nix_ldflags| { is_nix = true; var it = mem.tokenizeScalar(u8, nix_ldflags, ' '); while (true) { @@ -78,7 +78,7 @@ pub fn detect( } } - if (std.zig.EnvVar.NIX_CFLAGS_LINK.get(env_map)) |nix_cflags_link| { + if (std.zig.EnvVar.NIX_CFLAGS_LINK.get(environ_map)) |nix_cflags_link| { is_nix = true; var it = mem.tokenizeScalar(u8, nix_cflags_link, ' '); while (true) { @@ -121,7 +121,7 @@ pub fn detect( } // Check for homebrew paths - if (std.zig.EnvVar.HOMEBREW_PREFIX.get(env_map)) |prefix| { + if (std.zig.EnvVar.HOMEBREW_PREFIX.get(environ_map)) |prefix| { try self.addLibDir(try std.fs.path.join(arena, &.{ prefix, "/lib" })); try self.addIncludeDir(try std.fs.path.join(arena, &.{ prefix, "/include" })); } @@ -177,21 +177,21 @@ pub fn detect( // Distros like guix don't use FHS, so they rely on environment // variables to search for headers and libraries. - if (std.zig.EnvVar.C_INCLUDE_PATH.get(env_map)) |c_include_path| { + if (std.zig.EnvVar.C_INCLUDE_PATH.get(environ_map)) |c_include_path| { var it = mem.tokenizeScalar(u8, c_include_path, ':'); while (it.next()) |dir| { try self.addIncludeDir(dir); } } - if (std.zig.EnvVar.CPLUS_INCLUDE_PATH.get(env_map)) |cplus_include_path| { + if (std.zig.EnvVar.CPLUS_INCLUDE_PATH.get(environ_map)) |cplus_include_path| { var it = mem.tokenizeScalar(u8, cplus_include_path, ':'); while (it.next()) |dir| { try self.addIncludeDir(dir); } } - if (std.zig.EnvVar.LIBRARY_PATH.get(env_map)) |library_path| { + if (std.zig.EnvVar.LIBRARY_PATH.get(environ_map)) |library_path| { var it = mem.tokenizeScalar(u8, library_path, ':'); while (it.next()) |dir| { try self.addLibDir(dir); diff --git a/src/Compilation.zig b/src/Compilation.zig index d55f5ad48fd65164d431995c78a886999adea23d..4b5b1e3ff8552225fb22933a0d8c099bdc6b81ca 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -762,7 +762,7 @@ pub const Directories = struct { .wasi => void, else => []const u8, }, - env_map: *const std.process.Environ.Map, + environ_map: *const std.process.Environ.Map, ) Directories { const wasi = builtin.target.os.tag == .wasi; @@ -781,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"); - const path = introspect.resolveGlobalCacheDir(arena, env_map) catch |err| { + const path = introspect.resolveGlobalCacheDir(arena, environ_map) catch |err| { fatal("unable to resolve zig cache directory: {t}", .{err}); }; break :d openUnresolved(arena, io, cwd, path, .@"global cache"); @@ -5713,7 +5713,7 @@ pub fn translateC( translated_basename: []const u8, owner_mod: *Package.Module, prog_node: std.Progress.Node, - env_map: *const std.process.Environ.Map, + environ_map: *const std.process.Environ.Map, ) !CImportResult { dev.check(.translate_c_command); @@ -5783,7 +5783,7 @@ pub fn translateC( } var stdout: []u8 = undefined; - try @import("main.zig").translateC(gpa, arena, io, argv.items, env_map, prog_node, &stdout); + try @import("main.zig").translateC(gpa, arena, io, argv.items, environ_map, prog_node, &stdout); if (out_dep_path) |dep_file_path| add_deps: { if (comp.verbose_cimport) log.info("processing dep file at {s}", .{dep_file_path}); diff --git a/src/introspect.zig b/src/introspect.zig index 7c5a2e17e763a45afaa626acba62f51b9a3ca85e..fa04e7de58a1fda94f9311fc1bb21a7266558e3f 100644 --- a/src/introspect.zig +++ b/src/introspect.zig @@ -102,25 +102,25 @@ pub fn findZigLibDirFromSelfExe( return error.FileNotFound; } -pub fn resolveGlobalCacheDir(arena: Allocator, env_map: *const std.process.Environ.Map) ![]const u8 { - if (std.zig.EnvVar.ZIG_GLOBAL_CACHE_DIR.get(env_map)) |value| return value; +pub fn resolveGlobalCacheDir(arena: Allocator, environ_map: *const std.process.Environ.Map) ![]const u8 { + if (std.zig.EnvVar.ZIG_GLOBAL_CACHE_DIR.get(environ_map)) |value| return value; const app_name = "zig"; switch (builtin.os.tag) { .wasi => @compileError("on WASI the global cache dir must be resolved with preopens"), .windows => { - const local_app_data_dir = std.zig.EnvVar.LOCALAPPDATA.get(env_map) orelse + const local_app_data_dir = std.zig.EnvVar.LOCALAPPDATA.get(environ_map) orelse return error.AppDataDirUnavailable; return Dir.path.join(arena, &.{ local_app_data_dir, app_name }); }, else => { - if (std.zig.EnvVar.XDG_CACHE_HOME.get(env_map)) |cache_root| { + if (std.zig.EnvVar.XDG_CACHE_HOME.get(environ_map)) |cache_root| { if (cache_root.len > 0) { return Dir.path.join(arena, &.{ cache_root, app_name }); } } - if (std.zig.EnvVar.HOME.get(env_map)) |home| { + if (std.zig.EnvVar.HOME.get(environ_map)) |home| { if (home.len > 0) { return Dir.path.join(arena, &.{ home, ".cache", app_name }); } diff --git a/src/main.zig b/src/main.zig index 3d90e031d1f85902bafae21ea6f055d249527108..ff17b86a3c9d9505a0c7848c522e90306d33d998 100644 --- a/src/main.zig +++ b/src/main.zig @@ -191,7 +191,7 @@ pub fn main(init: std.process.Init.Minimal) anyerror!void { fatal("expected command argument", .{}); } - var env_map = init.environ.createMap(arena) catch |err| fatal("failed to parse environment: {t}", .{err}); + var environ_map = init.environ.createMap(arena) catch |err| fatal("failed to parse environment: {t}", .{err}); Compilation.setMainThread(); @@ -206,14 +206,14 @@ pub fn main(init: std.process.Init.Minimal) anyerror!void { if (tracy.enable_allocation) { var gpa_tracy = tracy.tracyAllocator(gpa); - return mainArgs(gpa_tracy.allocator(), arena, io, args, &env_map); + return mainArgs(gpa_tracy.allocator(), arena, io, args, &environ_map); } if (native_os == .wasi) { wasi_preopens = try fs.wasi.preopensAlloc(arena); } - return mainArgs(gpa, arena, io, args, &env_map); + return mainArgs(gpa, arena, io, args, &environ_map); } fn mainArgs( @@ -221,9 +221,9 @@ fn mainArgs( arena: Allocator, io: Io, args: []const [:0]const u8, - env_map: *process.Environ.Map, + environ_map: *process.Environ.Map, ) !void { - if (process.can_replace and EnvVar.ZIG_IS_DETECTING_LIBC_PATHS.isSet(env_map)) { + if (process.can_replace and EnvVar.ZIG_IS_DETECTING_LIBC_PATHS.isSet(environ_map)) { dev.check(.cc_command); // In this case we have accidentally invoked ourselves as "the system C compiler" // to figure out where libc is installed. This is essentially infinite recursion @@ -233,7 +233,7 @@ fn mainArgs( // why we have this additional environment variable here to check. const inf_loop_env_key: EnvVar = .ZIG_IS_TRYING_TO_NOT_CALL_ITSELF; - if (inf_loop_env_key.isSet(env_map)) { + if (inf_loop_env_key.isSet(environ_map)) { fatal("{s}", .{ "The compilation links against libc, but Zig is unable to provide a libc " ++ "for this operating system, and no --libc " ++ @@ -242,17 +242,17 @@ fn mainArgs( "compiler is `zig cc`, so no libc installation was found.", }); } - try env_map.put(@tagName(inf_loop_env_key), "1"); + try environ_map.put(@tagName(inf_loop_env_key), "1"); // Some programs such as CMake will strip the `cc` and subsequent args from the // CC environment variable. We detect and support this scenario here because of // the ZIG_IS_DETECTING_LIBC_PATHS environment variable. if (mem.eql(u8, args[1], "cc")) { - return process.replace(io, .{ .argv = args[1..], .env_map = env_map }); + return process.replace(io, .{ .argv = args[1..], .environ_map = environ_map }); } else { const modified_args = try arena.dupe([]const u8, args); modified_args[0] = "cc"; - return process.replace(io, .{ .argv = modified_args, .env_map = env_map }); + return process.replace(io, .{ .argv = modified_args, .environ_map = environ_map }); } } @@ -260,22 +260,22 @@ fn mainArgs( const cmd_args = args[2..]; if (mem.eql(u8, cmd, "build-exe")) { dev.check(.build_exe_command); - return buildOutputType(gpa, arena, io, args, .{ .build = .Exe }, env_map); + return buildOutputType(gpa, arena, io, args, .{ .build = .Exe }, environ_map); } else if (mem.eql(u8, cmd, "build-lib")) { dev.check(.build_lib_command); - return buildOutputType(gpa, arena, io, args, .{ .build = .Lib }, env_map); + return buildOutputType(gpa, arena, io, args, .{ .build = .Lib }, environ_map); } else if (mem.eql(u8, cmd, "build-obj")) { dev.check(.build_obj_command); - return buildOutputType(gpa, arena, io, args, .{ .build = .Obj }, env_map); + return buildOutputType(gpa, arena, io, args, .{ .build = .Obj }, environ_map); } else if (mem.eql(u8, cmd, "test")) { dev.check(.test_command); - return buildOutputType(gpa, arena, io, args, .zig_test, env_map); + return buildOutputType(gpa, arena, io, args, .zig_test, environ_map); } else if (mem.eql(u8, cmd, "test-obj")) { dev.check(.test_command); - return buildOutputType(gpa, arena, io, args, .zig_test_obj, env_map); + return buildOutputType(gpa, arena, io, args, .zig_test_obj, environ_map); } else if (mem.eql(u8, cmd, "run")) { dev.check(.run_command); - return buildOutputType(gpa, arena, io, args, .run, env_map); + return buildOutputType(gpa, arena, io, args, .run, environ_map); } else if (mem.eql(u8, cmd, "dlltool") or mem.eql(u8, cmd, "ranlib") or mem.eql(u8, cmd, "lib") or @@ -285,7 +285,7 @@ fn mainArgs( return process.exit(try llvmArMain(arena, args)); } else if (mem.eql(u8, cmd, "build")) { dev.check(.build_command); - return cmdBuild(gpa, arena, io, cmd_args, env_map); + return cmdBuild(gpa, arena, io, cmd_args, environ_map); } else if (mem.eql(u8, cmd, "clang") or mem.eql(u8, cmd, "-cc1") or mem.eql(u8, cmd, "-cc1as")) { @@ -299,16 +299,16 @@ fn mainArgs( return process.exit(try lldMain(arena, args, true)); } else if (mem.eql(u8, cmd, "cc")) { dev.check(.cc_command); - return buildOutputType(gpa, arena, io, args, .cc, env_map); + return buildOutputType(gpa, arena, io, args, .cc, environ_map); } else if (mem.eql(u8, cmd, "c++")) { dev.check(.cc_command); - return buildOutputType(gpa, arena, io, args, .cpp, env_map); + return buildOutputType(gpa, arena, io, args, .cpp, environ_map); } else if (mem.eql(u8, cmd, "translate-c")) { dev.check(.translate_c_command); - return buildOutputType(gpa, arena, io, args, .translate_c, env_map); + return buildOutputType(gpa, arena, io, args, .translate_c, environ_map); } else if (mem.eql(u8, cmd, "rc")) { const use_server = cmd_args.len > 0 and std.mem.eql(u8, cmd_args[0], "--zig-integration"); - return jitCmd(gpa, arena, io, cmd_args, env_map, .{ + return jitCmd(gpa, arena, io, cmd_args, environ_map, .{ .cmd_name = "resinator", .root_src_path = "resinator/main.zig", .depend_on_aro = true, @@ -319,20 +319,20 @@ fn mainArgs( dev.check(.fmt_command); return @import("fmt.zig").run(gpa, arena, io, cmd_args); } else if (mem.eql(u8, cmd, "objcopy")) { - return jitCmd(gpa, arena, io, cmd_args, env_map, .{ + return jitCmd(gpa, arena, io, cmd_args, environ_map, .{ .cmd_name = "objcopy", .root_src_path = "objcopy.zig", }); } else if (mem.eql(u8, cmd, "fetch")) { - return cmdFetch(gpa, arena, io, cmd_args, env_map); + return cmdFetch(gpa, arena, io, cmd_args, environ_map); } else if (mem.eql(u8, cmd, "libc")) { - return jitCmd(gpa, arena, io, cmd_args, env_map, .{ + return jitCmd(gpa, arena, io, cmd_args, environ_map, .{ .cmd_name = "libc", .root_src_path = "libc.zig", .prepend_zig_lib_dir_path = true, }); } else if (mem.eql(u8, cmd, "std")) { - return jitCmd(gpa, arena, io, cmd_args, env_map, .{ + return jitCmd(gpa, arena, io, cmd_args, environ_map, .{ .cmd_name = "std", .root_src_path = "std-docs.zig", .prepend_zig_lib_dir_path = true, @@ -362,11 +362,11 @@ fn mainArgs( args, if (native_os == .wasi) wasi_preopens, &host, - env_map, + environ_map, ); return stdout_writer.interface.flush(); } else if (mem.eql(u8, cmd, "reduce")) { - return jitCmd(gpa, arena, io, cmd_args, env_map, .{ + return jitCmd(gpa, arena, io, cmd_args, environ_map, .{ .cmd_name = "reduce", .root_src_path = "reduce.zig", }); @@ -811,7 +811,7 @@ fn buildOutputType( io: Io, all_args: []const []const u8, arg_mode: ArgMode, - env_map: *process.Environ.Map, + environ_map: *process.Environ.Map, ) !void { var provided_name: ?[]const u8 = null; var root_src_file: ?[]const u8 = null; @@ -824,9 +824,9 @@ fn buildOutputType( var debug_compile_errors = false; var debug_incremental = false; var verbose_link = (native_os != .wasi or builtin.link_libc) and - EnvVar.ZIG_VERBOSE_LINK.isSet(env_map); + EnvVar.ZIG_VERBOSE_LINK.isSet(environ_map); var verbose_cc = (native_os != .wasi or builtin.link_libc) and - EnvVar.ZIG_VERBOSE_CC.isSet(env_map); + EnvVar.ZIG_VERBOSE_CC.isSet(environ_map); var verbose_air = false; var verbose_intern_pool = false; var verbose_generic_instances = false; @@ -898,9 +898,9 @@ fn buildOutputType( var runtime_args_start: ?usize = null; var test_filters: std.ArrayList([]const u8) = .empty; var test_runner_path: ?[]const u8 = null; - var override_local_cache_dir: ?[]const u8 = EnvVar.ZIG_LOCAL_CACHE_DIR.get(env_map); - var override_global_cache_dir: ?[]const u8 = EnvVar.ZIG_GLOBAL_CACHE_DIR.get(env_map); - var override_lib_dir: ?[]const u8 = EnvVar.ZIG_LIB_DIR.get(env_map); + var override_local_cache_dir: ?[]const u8 = EnvVar.ZIG_LOCAL_CACHE_DIR.get(environ_map); + var override_global_cache_dir: ?[]const u8 = EnvVar.ZIG_GLOBAL_CACHE_DIR.get(environ_map); + var override_lib_dir: ?[]const u8 = EnvVar.ZIG_LIB_DIR.get(environ_map); var clang_preprocessor_mode: Compilation.ClangPreprocessorMode = .no; var subsystem: ?std.zig.Subsystem = null; var major_subsystem_version: ?u16 = null; @@ -997,7 +997,7 @@ fn buildOutputType( .framework_dirs = .{}, .rpath_list = .{}, .each_lib_rpath = null, - .libc_paths_file = EnvVar.ZIG_LIBC.get(env_map), + .libc_paths_file = EnvVar.ZIG_LIBC.get(environ_map), .native_system_include_paths = &.{}, }; defer create_module.link_inputs.deinit(gpa); @@ -1006,9 +1006,9 @@ fn buildOutputType( // if set, default the color setting to .off or .on, respectively // explicit --color arguments will still override this setting. // Disable color on WASI per https://github.com/WebAssembly/WASI/issues/162 - var color: Color = if (native_os == .wasi or EnvVar.NO_COLOR.isSet(env_map)) + var color: Color = if (native_os == .wasi or EnvVar.NO_COLOR.isSet(environ_map)) .off - else if (EnvVar.CLICOLOR_FORCE.isSet(env_map)) + else if (EnvVar.CLICOLOR_FORCE.isSet(environ_map)) .on else .auto; @@ -3106,7 +3106,7 @@ fn buildOutputType( }, if (native_os == .wasi) wasi_preopens, self_exe_path, - env_map, + environ_map, ); defer dirs.deinit(io); @@ -3118,7 +3118,7 @@ fn buildOutputType( create_module.opts.emit_bin = emit_bin != .no; create_module.opts.any_c_source_files = create_module.c_source_files.items.len != 0; - const main_mod = try createModule(gpa, arena, io, &create_module, 0, null, color, env_map); + const main_mod = try createModule(gpa, arena, io, &create_module, 0, null, color, environ_map); for (create_module.modules.keys(), create_module.modules.values()) |key, cli_mod| { if (cli_mod.resolved == null) fatal("module '{s}' declared but not used", .{key}); @@ -3595,7 +3595,7 @@ fn buildOutputType( .global_cc_argv = try cc_argv.toOwnedSlice(arena), .file_system_inputs = &file_system_inputs, .debug_compiler_runtime_libs = debug_compiler_runtime_libs, - .environ_map = env_map, + .environ_map = environ_map, }) catch |err| switch (err) { error.CreateFail => switch (create_diag) { .cross_libc_unavailable => { @@ -3659,7 +3659,7 @@ fn buildOutputType( arg_mode, all_args, runtime_args_start, - env_map, + environ_map, ); return cleanExit(io); }, @@ -3686,7 +3686,7 @@ fn buildOutputType( arg_mode, all_args, runtime_args_start, - env_map, + environ_map, ); return cleanExit(io); }, @@ -3699,7 +3699,7 @@ fn buildOutputType( defer root_prog_node.end(); if (arg_mode == .translate_c) { - return cmdTranslateC(comp, arena, null, null, root_prog_node, env_map); + return cmdTranslateC(comp, arena, null, null, root_prog_node, environ_map); } updateModule(comp, color, root_prog_node) catch |err| switch (err) { @@ -3767,7 +3767,7 @@ fn buildOutputType( all_args, runtime_args_start, create_module.resolved_options.link_libc, - env_map, + environ_map, ); } @@ -3823,7 +3823,7 @@ fn createModule( index: usize, parent: ?*Package.Module, color: std.zig.Color, - env_map: *process.Environ.Map, + environ_map: *process.Environ.Map, ) Allocator.Error!*Package.Module { const cli_mod = &create_module.modules.values()[index]; if (cli_mod.resolved) |m| return m; @@ -4003,7 +4003,7 @@ fn createModule( resolved_target.is_native_os and resolved_target.is_native_abi and create_module.want_native_include_dirs) { - var paths = std.zig.system.NativePaths.detect(arena, io, target, env_map) catch |err| + var paths = std.zig.system.NativePaths.detect(arena, io, target, environ_map) catch |err| fatal("unable to detect native system paths: {t}", .{err}); for (paths.warnings.items) |warning| { warn("{s}", .{warning}); @@ -4030,7 +4030,7 @@ fn createModule( create_module.libc_installation = LibCInstallation.findNative(arena, io, .{ .verbose = true, .target = target, - .env_map = env_map, + .environ_map = environ_map, }) catch |err| { fatal("unable to find native libc installation: {t}", .{err}); }; @@ -4135,7 +4135,7 @@ fn createModule( for (cli_mod.deps) |dep| { const dep_index = create_module.modules.getIndex(dep.value) orelse fatal("module '{s}' depends on non-existent module '{s}'", .{ name, dep.key }); - const dep_mod = try createModule(gpa, arena, io, create_module, dep_index, mod, color, env_map); + const dep_mod = try createModule(gpa, arena, io, create_module, dep_index, mod, color, environ_map); try mod.deps.put(arena, dep.key, dep_mod); } @@ -4157,7 +4157,7 @@ fn serve( arg_mode: ArgMode, all_args: []const []const u8, runtime_args_start: ?usize, - env_map: *process.Environ.Map, + environ_map: *process.Environ.Map, ) !void { const gpa = comp.gpa; const io = comp.io; @@ -4205,7 +4205,7 @@ fn serve( defer arena_instance.deinit(); const arena = arena_instance.allocator(); var output: Compilation.CImportResult = undefined; - try cmdTranslateC(comp, arena, &output, file_system_inputs, main_progress_node, env_map); + try cmdTranslateC(comp, arena, &output, file_system_inputs, main_progress_node, environ_map); defer output.deinit(gpa); if (file_system_inputs.items.len != 0) { @@ -4405,7 +4405,7 @@ fn runOrTest( all_args: []const []const u8, runtime_args_start: ?usize, link_libc: bool, - env_map: *process.Environ.Map, + environ_map: *process.Environ.Map, ) !void { const raw_emit_bin = comp.emit_bin orelse return; const exe_path = switch (comp.cache_use) { @@ -4442,14 +4442,14 @@ fn runOrTest( if (runtime_args_start) |i| { try argv.appendSlice(all_args[i..]); } - try env_map.put("ZIG_EXE", self_exe_path); + try environ_map.put("ZIG_EXE", self_exe_path); // We do not execve for tests because if the test fails we want to print // the error message and invocation below. if (process.can_replace and arg_mode == .run) { // process replacement releases the locks; no need to destroy the Compilation here. _ = try io.lockStderr(&.{}, .no_color); - const err = process.replace(io, .{ .argv = argv.items, .env_map = env_map }); + const err = process.replace(io, .{ .argv = argv.items, .environ_map = environ_map }); io.unlockStderr(); try warnAboutForeignBinaries(io, arena, arg_mode, target, link_libc); const cmd = try std.mem.join(arena, " ", argv.items); @@ -4471,7 +4471,7 @@ fn runOrTest( var child = std.process.spawn(io, .{ .argv = argv.items, - .env_map = env_map, + .environ_map = environ_map, .stdin = .inherit, .stdout = .inherit, .stderr = .inherit, @@ -4626,7 +4626,7 @@ fn cmdTranslateC( fancy_output: ?*Compilation.CImportResult, file_system_inputs: ?*std.ArrayList(u8), prog_node: std.Progress.Node, - env_map: *process.Environ.Map, + environ_map: *process.Environ.Map, ) !void { dev.check(.translate_c_command); @@ -4660,7 +4660,7 @@ fn cmdTranslateC( translated_basename, comp.root_mod, prog_node, - env_map, + environ_map, ); if (result.errors.errorMessageCount() != 0) { @@ -4708,11 +4708,11 @@ pub fn translateC( arena: Allocator, io: Io, argv: []const []const u8, - env_map: *const process.Environ.Map, + environ_map: *const process.Environ.Map, prog_node: std.Progress.Node, capture: ?*[]u8, ) !void { - try jitCmd(gpa, arena, io, argv, env_map, .{ + try jitCmd(gpa, arena, io, argv, environ_map, .{ .cmd_name = "translate-c", .root_src_path = "translate-c/main.zig", .depend_on_aro = true, @@ -4869,21 +4869,21 @@ test sanitizeExampleName { try std.testing.expectEqualStrings("test_project", try sanitizeExampleName(arena, "test project")); } -fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8, env_map: *process.Environ.Map) !void { +fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8, environ_map: *process.Environ.Map) !void { dev.check(.build_command); var build_file: ?[]const u8 = null; - var override_lib_dir: ?[]const u8 = EnvVar.ZIG_LIB_DIR.get(env_map); - var override_global_cache_dir: ?[]const u8 = EnvVar.ZIG_GLOBAL_CACHE_DIR.get(env_map); - var override_local_cache_dir: ?[]const u8 = EnvVar.ZIG_LOCAL_CACHE_DIR.get(env_map); - var override_build_runner: ?[]const u8 = EnvVar.ZIG_BUILD_RUNNER.get(env_map); + var override_lib_dir: ?[]const u8 = EnvVar.ZIG_LIB_DIR.get(environ_map); + var override_global_cache_dir: ?[]const u8 = EnvVar.ZIG_GLOBAL_CACHE_DIR.get(environ_map); + var override_local_cache_dir: ?[]const u8 = EnvVar.ZIG_LOCAL_CACHE_DIR.get(environ_map); + var override_build_runner: ?[]const u8 = EnvVar.ZIG_BUILD_RUNNER.get(environ_map); var child_argv = std.array_list.Managed([]const u8).init(arena); var reference_trace: ?u32 = null; var debug_compile_errors = false; var verbose_link = (native_os != .wasi or builtin.link_libc) and - EnvVar.ZIG_VERBOSE_LINK.isSet(env_map); + EnvVar.ZIG_VERBOSE_LINK.isSet(environ_map); var verbose_cc = (native_os != .wasi or builtin.link_libc) and - EnvVar.ZIG_VERBOSE_CC.isSet(env_map); + EnvVar.ZIG_VERBOSE_CC.isSet(environ_map); var verbose_air = false; var verbose_intern_pool = false; var verbose_generic_instances = false; @@ -5080,7 +5080,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8, } const work_around_btrfs_bug = native_os == .linux and - EnvVar.ZIG_BTRFS_WORKAROUND.isSet(env_map); + EnvVar.ZIG_BTRFS_WORKAROUND.isSet(environ_map); const root_prog_node = std.Progress.start(io, .{ .disable_printing = (color == .off), .root_name = "Compile Build Script", @@ -5140,7 +5140,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8, } }, {}, self_exe_path, - env_map, + environ_map, ); defer dirs.deinit(io); @@ -5243,7 +5243,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8, job_queue.read_only = true; cleanup_build_dir = job_queue.global_cache.handle; } else { - try http_client.initDefaultProxies(arena, env_map); + try http_client.initDefaultProxies(arena, environ_map); } try job_queue.all_fetches.ensureUnusedCapacity(gpa, 1); @@ -5397,7 +5397,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8, .cache_mode = .whole, .reference_trace = reference_trace, .debug_compile_errors = debug_compile_errors, - .environ_map = env_map, + .environ_map = environ_map, }) catch |err| switch (err) { error.CreateFail => fatal("failed to create compilation: {f}", .{create_diag}), else => fatal("failed to create compilation: {s}", .{@errorName(err)}), @@ -5516,7 +5516,7 @@ fn jitCmd( arena: Allocator, io: Io, args: []const []const u8, - env_map: *const process.Environ.Map, + environ_map: *const process.Environ.Map, options: JitCmdOptions, ) !void { dev.check(.jit_command); @@ -5538,13 +5538,13 @@ fn jitCmd( const self_exe_path = process.executablePathAlloc(io, arena) catch |err| fatal("unable to find self exe path: {t}", .{err}); - const optimize_mode: std.builtin.OptimizeMode = if (EnvVar.ZIG_DEBUG_CMD.isSet(env_map)) + const optimize_mode: std.builtin.OptimizeMode = if (EnvVar.ZIG_DEBUG_CMD.isSet(environ_map)) .Debug else .ReleaseFast; const strip = optimize_mode != .Debug; - const override_lib_dir: ?[]const u8 = EnvVar.ZIG_LIB_DIR.get(env_map); - const override_global_cache_dir: ?[]const u8 = EnvVar.ZIG_GLOBAL_CACHE_DIR.get(env_map); + const override_lib_dir: ?[]const u8 = EnvVar.ZIG_LIB_DIR.get(environ_map); + const override_global_cache_dir: ?[]const u8 = EnvVar.ZIG_GLOBAL_CACHE_DIR.get(environ_map); // This `init` calls `fatal` on error. var dirs: Compilation.Directories = .init( @@ -5555,7 +5555,7 @@ fn jitCmd( .global, if (native_os == .wasi) wasi_preopens, self_exe_path, - env_map, + environ_map, ); defer dirs.deinit(io); @@ -5629,7 +5629,7 @@ fn jitCmd( .self_exe_path = self_exe_path, .thread_limit = thread_limit, .cache_mode = .whole, - .environ_map = env_map, + .environ_map = environ_map, }) catch |err| switch (err) { error.CreateFail => fatal("failed to create compilation: {f}", .{create_diag}), else => fatal("failed to create compilation: {s}", .{@errorName(err)}), @@ -5676,11 +5676,11 @@ fn jitCmd( child_argv.appendSliceAssumeCapacity(args); if (process.can_replace and options.capture == null) { - if (EnvVar.ZIG_DEBUG_CMD.isSet(env_map)) { + if (EnvVar.ZIG_DEBUG_CMD.isSet(environ_map)) { const cmd = try std.mem.join(arena, " ", child_argv.items); std.debug.print("{s}\n", .{cmd}); } - const err = process.replace(io, .{ .argv = child_argv.items, .env_map = env_map }); + const err = process.replace(io, .{ .argv = child_argv.items, .environ_map = environ_map }); const cmd = try std.mem.join(arena, " ", child_argv.items); fatal("the following command failed to execve with '{t}':\n{s}", .{ err, cmd }); } @@ -6914,15 +6914,15 @@ fn cmdFetch( arena: Allocator, io: Io, args: []const []const u8, - env_map: *process.Environ.Map, + environ_map: *process.Environ.Map, ) !void { dev.check(.fetch_command); const color: Color = .auto; const work_around_btrfs_bug = native_os == .linux and - EnvVar.ZIG_BTRFS_WORKAROUND.isSet(env_map); + EnvVar.ZIG_BTRFS_WORKAROUND.isSet(environ_map); var opt_path_or_url: ?[]const u8 = null; - var override_global_cache_dir: ?[]const u8 = EnvVar.ZIG_GLOBAL_CACHE_DIR.get(env_map); + var override_global_cache_dir: ?[]const u8 = EnvVar.ZIG_GLOBAL_CACHE_DIR.get(environ_map); var debug_hash: bool = false; var save: union(enum) { no, @@ -6968,7 +6968,7 @@ fn cmdFetch( var http_client: std.http.Client = .{ .allocator = gpa, .io = io }; defer http_client.deinit(); - try http_client.initDefaultProxies(arena, env_map); + try http_client.initDefaultProxies(arena, environ_map); var root_prog_node = std.Progress.start(io, .{ .root_name = "Fetch", @@ -6976,7 +6976,7 @@ fn cmdFetch( defer root_prog_node.end(); var global_cache_directory: Directory = l: { - const p = override_global_cache_dir orelse try introspect.resolveGlobalCacheDir(arena, env_map); + const p = override_global_cache_dir orelse try introspect.resolveGlobalCacheDir(arena, environ_map); break :l .{ .handle = try Io.Dir.cwd().createDirPathOpen(io, p, .{}), .path = p, diff --git a/src/print_env.zig b/src/print_env.zig index 3f9bf1033efc82ee478d2a0e62422429e58fdb95..ac0578852e5f9d43c925ee4894904cd78062e826 100644 --- a/src/print_env.zig +++ b/src/print_env.zig @@ -19,10 +19,10 @@ pub fn cmdEnv( else => void, }, host: *const std.Target, - env_map: *std.process.Environ.Map, + environ_map: *std.process.Environ.Map, ) !void { - const override_lib_dir: ?[]const u8 = EnvVar.ZIG_LIB_DIR.get(env_map); - const override_global_cache_dir: ?[]const u8 = EnvVar.ZIG_GLOBAL_CACHE_DIR.get(env_map); + const override_lib_dir: ?[]const u8 = EnvVar.ZIG_LIB_DIR.get(environ_map); + const override_global_cache_dir: ?[]const u8 = EnvVar.ZIG_GLOBAL_CACHE_DIR.get(environ_map); const self_exe_path = switch (builtin.target.os.tag) { .wasi => args[0], @@ -39,7 +39,7 @@ pub fn cmdEnv( .global, if (builtin.target.os.tag == .wasi) wasi_preopens, if (builtin.target.os.tag != .wasi) self_exe_path, - env_map, + environ_map, ); defer dirs.deinit(io); @@ -59,7 +59,7 @@ pub fn cmdEnv( try root.field("target", triple, .{}); var env = try root.beginStructField("env", .{}); inline for (@typeInfo(EnvVar).@"enum".fields) |field| { - try env.field(field.name, @field(EnvVar, field.name).get(env_map), .{}); + try env.field(field.name, @field(EnvVar, field.name).get(environ_map), .{}); } try env.end(); try root.end(); diff --git a/test/standalone/child_process/main.zig b/test/standalone/child_process/main.zig index bfd84a5361c885391b8d40b1caabc929d6cd8926..2776c3e95c88d5158b9db122d6b9669871ba23e9 100644 --- a/test/standalone/child_process/main.zig +++ b/test/standalone/child_process/main.zig @@ -12,8 +12,8 @@ pub fn main(init: std.process.Init.Minimal) !void { const process_cwd_path = try std.process.getCwdAlloc(gpa); defer gpa.free(process_cwd_path); - var env_map = try init.environ.createMap(gpa); - defer env_map.deinit(); + var environ_map = try init.environ.createMap(gpa); + defer environ_map.deinit(); var it = try init.args.iterateAllocator(gpa); defer it.deinit(); @@ -23,7 +23,7 @@ pub fn main(init: std.process.Init.Minimal) !void { const cwd_path = it.next() orelse break :child_path .{ child_path, false }; // If there is a third argument, it is the current CWD somewhere within the cache directory. // In that case, modify the child path in order to test spawning a path with a leading `..` component. - break :child_path .{ try std.fs.path.relative(gpa, process_cwd_path, &env_map, cwd_path, child_path), true }; + break :child_path .{ try std.fs.path.relative(gpa, process_cwd_path, &environ_map, cwd_path, child_path), true }; }; defer if (needs_free) gpa.free(child_path); diff --git a/test/standalone/empty_env/main.zig b/test/standalone/empty_env/main.zig index 80331d5e5ff615007a0cae122f3e08735d5907dd..170a46de9e28856fc4d257df5f721bef8db9da1c 100644 --- a/test/standalone/empty_env/main.zig +++ b/test/standalone/empty_env/main.zig @@ -1,5 +1,5 @@ const std = @import("std"); pub fn main(init: std.process.Init) !void { - try std.testing.expectEqual(0, init.env_map.count()); + try std.testing.expectEqual(0, init.environ_map.count()); } diff --git a/test/standalone/env_vars/main.zig b/test/standalone/env_vars/main.zig index 04778baae115a604c9f876a86df353535d52668c..09167f285fe9960fbedb8f9fcb179bc354ca15f4 100644 --- a/test/standalone/env_vars/main.zig +++ b/test/standalone/env_vars/main.zig @@ -126,26 +126,26 @@ pub fn main(init: std.process.Init) !void { // Environ.Map { - var env_map = try environ.createMap(allocator); - defer env_map.deinit(); + var environ_map = try environ.createMap(allocator); + defer environ_map.deinit(); - try std.testing.expectEqualSlices(u8, "123", env_map.get("FOO").?); - try std.testing.expectEqual(null, env_map.get("FO")); - try std.testing.expectEqual(null, env_map.get("FOOO")); + try std.testing.expectEqualSlices(u8, "123", environ_map.get("FOO").?); + try std.testing.expectEqual(null, environ_map.get("FO")); + try std.testing.expectEqual(null, environ_map.get("FOOO")); if (builtin.os.tag == .windows) { - try std.testing.expectEqualSlices(u8, "123", env_map.get("foo").?); + try std.testing.expectEqualSlices(u8, "123", environ_map.get("foo").?); } - try std.testing.expectEqualSlices(u8, "ABC=123", env_map.get("EQUALS").?); - try std.testing.expectEqual(null, env_map.get("EQUALS=ABC")); - try std.testing.expectEqualSlices(u8, "non-ascii አማርኛ \u{10FFFF}", env_map.get("КИРиллИЦА").?); + try std.testing.expectEqualSlices(u8, "ABC=123", environ_map.get("EQUALS").?); + try std.testing.expectEqual(null, environ_map.get("EQUALS=ABC")); + try std.testing.expectEqualSlices(u8, "non-ascii አማርኛ \u{10FFFF}", environ_map.get("КИРиллИЦА").?); if (builtin.os.tag == .windows) { - try std.testing.expectEqualSlices(u8, "non-ascii አማርኛ \u{10FFFF}", env_map.get("кирИЛЛица").?); + try std.testing.expectEqualSlices(u8, "non-ascii አማርኛ \u{10FFFF}", environ_map.get("кирИЛЛица").?); } - try std.testing.expectEqualSlices(u8, "", env_map.get("NO_VALUE").?); - try std.testing.expectEqual(null, env_map.get("NOT_SET")); + try std.testing.expectEqualSlices(u8, "", environ_map.get("NO_VALUE").?); + try std.testing.expectEqual(null, environ_map.get("NOT_SET")); if (builtin.os.tag == .windows) { - try std.testing.expectEqualSlices(u8, "hi", env_map.get("=HIDDEN").?); - try std.testing.expectEqualSlices(u8, "\xed\xa0\x80", env_map.get("INVALID_UTF16_\xed\xa0\x80").?); + try std.testing.expectEqualSlices(u8, "hi", environ_map.get("=HIDDEN").?); + try std.testing.expectEqualSlices(u8, "\xed\xa0\x80", environ_map.get("INVALID_UTF16_\xed\xa0\x80").?); } } } diff --git a/test/standalone/self_exe_symlink/create-symlink.zig b/test/standalone/self_exe_symlink/create-symlink.zig index 32918fe5df86ad933f4ce32aad392b08b44433e9..32610ebcde6d07a4d1ef14b1dca4fab3dd3bd90b 100644 --- a/test/standalone/self_exe_symlink/create-symlink.zig +++ b/test/standalone/self_exe_symlink/create-symlink.zig @@ -12,7 +12,7 @@ pub fn main(init: std.process.Init) !void { const cwd = try std.process.getCwdAlloc(init.arena.allocator()); // If `exe_path` is relative to our cwd, we need to convert it to be relative to the dirname of `symlink_path`. - const exe_rel_path = try std.fs.path.relative(gpa, cwd, init.env_map, std.fs.path.dirname(symlink_path) orelse ".", exe_path); + const exe_rel_path = try std.fs.path.relative(gpa, cwd, init.environ_map, std.fs.path.dirname(symlink_path) orelse ".", exe_path); defer gpa.free(exe_rel_path); try std.Io.Dir.cwd().symLink(io, exe_rel_path, symlink_path, .{}); diff --git a/test/standalone/windows_bat_args/fuzz.zig b/test/standalone/windows_bat_args/fuzz.zig index 0f29f4039856ed4a73eb606b8fa85f747909c086..e5d84fa4b5d514e886cb657ec7001da31ffde3d7 100644 --- a/test/standalone/windows_bat_args/fuzz.zig +++ b/test/standalone/windows_bat_args/fuzz.zig @@ -94,7 +94,7 @@ fn testExecBat(gpa: Allocator, io: Io, bat: []const u8, args: []const []const u8 const can_have_trailing_empty_args = std.mem.eql(u8, bat, "args3.bat"); const result = try std.process.run(gpa, io, .{ - .env_map = env, + .environ_map = env, .argv = argv, }); defer gpa.free(result.stdout); diff --git a/test/standalone/windows_bat_args/test.zig b/test/standalone/windows_bat_args/test.zig index c1841925994fb9c1dddd5f1801adcaeed4c5fd85..520617aa38bef953d515db575aaa2d1382dfa9c7 100644 --- a/test/standalone/windows_bat_args/test.zig +++ b/test/standalone/windows_bat_args/test.zig @@ -141,7 +141,7 @@ fn testExecBat(gpa: Allocator, io: Io, bat: []const u8, args: []const []const u8 const can_have_trailing_empty_args = std.mem.eql(u8, bat, "args3.bat"); const result = try std.process.run(gpa, io, .{ - .env_map = env, + .environ_map = env, .argv = argv, }); defer gpa.free(result.stdout); diff --git a/test/standalone/windows_paths/test.zig b/test/standalone/windows_paths/test.zig index b874dea3e9e345e4c4a60b5ac1f437b7adb2964f..b248cc9277ea6b20850fec480bf74e50cab508c0 100644 --- a/test/standalone/windows_paths/test.zig +++ b/test/standalone/windows_paths/test.zig @@ -96,12 +96,12 @@ fn checkRelative( expected_stdout: []const u8, argv: []const []const u8, cwd: ?[]const u8, - env_map: ?*const std.process.Environ.Map, + environ_map: ?*const std.process.Environ.Map, ) !void { const result = try std.process.run(allocator, io, .{ .argv = argv, .cwd = cwd, - .env_map = env_map, + .environ_map = environ_map, }); defer allocator.free(result.stdout); defer allocator.free(result.stderr); diff --git a/tools/doctest.zig b/tools/doctest.zig index 7ec04ba8c217b84c7872abc0e2fcc13e65e6c127..7f4b8fbda803714765b1b616b1e60677ea0911d5 100644 --- a/tools/doctest.zig +++ b/tools/doctest.zig @@ -32,10 +32,10 @@ const usage = pub fn main(init: std.process.Init) !void { const arena = init.arena.allocator(); const io = init.io; - const env_map = init.env_map; + const environ_map = init.environ_map; const cwd_path = try std.process.getCwdAlloc(arena); - try env_map.put("CLICOLOR_FORCE", "1"); + try environ_map.put("CLICOLOR_FORCE", "1"); var args_it = try init.minimal.args.iterateAllocator(arena); if (!args_it.skip()) fatal("missing argv[0]", .{}); @@ -101,13 +101,13 @@ pub fn main(init: std.process.Init) !void { out, code, tmp_dir_path, - try Dir.path.relative(arena, cwd_path, env_map, tmp_dir_path, zig_path), - try Dir.path.relative(arena, cwd_path, env_map, tmp_dir_path, input_path), + try Dir.path.relative(arena, cwd_path, environ_map, tmp_dir_path, zig_path), + try Dir.path.relative(arena, cwd_path, environ_map, tmp_dir_path, input_path), if (opt_zig_lib_dir) |zig_lib_dir| - try Dir.path.relative(arena, cwd_path, env_map, tmp_dir_path, zig_lib_dir) + try Dir.path.relative(arena, cwd_path, environ_map, tmp_dir_path, zig_lib_dir) else null, - env_map, + environ_map, ); try out_file_writer.end(); @@ -126,7 +126,7 @@ fn printOutput( input_path: []const u8, /// Relative to `tmp_dir_path`. opt_zig_lib_dir: ?[]const u8, - env_map: *const process.Environ.Map, + environ_map: *const process.Environ.Map, ) !void { const host = try std.zig.system.resolveTargetQuery(io, .{}); const obj_ext = builtin.object_format.fileExt(builtin.cpu.arch); @@ -199,7 +199,7 @@ fn printOutput( const result = try process.run(arena, io, .{ .argv = build_args.items, .cwd = tmp_dir_path, - .env_map = env_map, + .environ_map = environ_map, .max_output_bytes = max_doc_file_size, }); switch (result.term) { @@ -221,7 +221,7 @@ fn printOutput( try shell_out.writeAll(colored_stderr); break :code_block; } - const exec_result = run(arena, io, env_map, tmp_dir_path, build_args.items) catch + const exec_result = run(arena, io, environ_map, tmp_dir_path, build_args.items) catch fatal("example failed to compile", .{}); if (code.verbose_cimport) { @@ -254,7 +254,7 @@ fn printOutput( const result = if (expected_outcome == .fail) blk: { const result = try process.run(arena, io, .{ .argv = run_args, - .env_map = env_map, + .environ_map = environ_map, .cwd = tmp_dir_path, .max_output_bytes = max_doc_file_size, }); @@ -271,7 +271,7 @@ fn printOutput( } break :blk result; } else blk: { - break :blk run(arena, io, env_map, tmp_dir_path, run_args) catch + break :blk run(arena, io, environ_map, tmp_dir_path, run_args) catch fatal("example crashed", .{}); }; @@ -340,7 +340,7 @@ fn printOutput( } } - const result = run(arena, io, env_map, tmp_dir_path, test_args.items) catch + const result = run(arena, io, environ_map, tmp_dir_path, test_args.items) catch fatal("test failed", .{}); const escaped_stderr = try escapeHtml(arena, result.stderr); const escaped_stdout = try escapeHtml(arena, result.stdout); @@ -373,7 +373,7 @@ fn printOutput( } const result = try process.run(arena, io, .{ .argv = test_args.items, - .env_map = env_map, + .environ_map = environ_map, .cwd = tmp_dir_path, .max_output_bytes = max_doc_file_size, }); @@ -429,7 +429,7 @@ fn printOutput( const result = try process.run(arena, io, .{ .argv = test_args.items, - .env_map = env_map, + .environ_map = environ_map, .cwd = tmp_dir_path, .max_output_bytes = max_doc_file_size, }); @@ -505,7 +505,7 @@ fn printOutput( if (maybe_error_match) |error_match| { const result = try process.run(arena, io, .{ .argv = build_args.items, - .env_map = env_map, + .environ_map = environ_map, .cwd = tmp_dir_path, .max_output_bytes = max_doc_file_size, }); @@ -531,7 +531,7 @@ fn printOutput( const colored_stderr = try termColor(arena, escaped_stderr); try shell_out.print("\n{s} ", .{colored_stderr}); } else { - _ = run(arena, io, env_map, tmp_dir_path, build_args.items) catch fatal("example failed to compile", .{}); + _ = run(arena, io, environ_map, tmp_dir_path, build_args.items) catch fatal("example failed to compile", .{}); } try shell_out.writeAll("\n"); }, @@ -590,7 +590,7 @@ fn printOutput( try test_args.append(option); try shell_out.print("{s} ", .{option}); } - const result = run(arena, io, env_map, tmp_dir_path, test_args.items) catch fatal("test failed", .{}); + const result = run(arena, io, environ_map, tmp_dir_path, test_args.items) catch fatal("test failed", .{}); const escaped_stderr = try escapeHtml(arena, result.stderr); const escaped_stdout = try escapeHtml(arena, result.stdout); try shell_out.print("\n{s}{s}\n", .{ escaped_stderr, escaped_stdout }); @@ -1123,13 +1123,13 @@ fn in(slice: []const u8, number: u8) bool { fn run( allocator: Allocator, io: Io, - env_map: *const process.Environ.Map, + environ_map: *const process.Environ.Map, cwd: []const u8, args: []const []const u8, ) !process.RunResult { const result = try process.run(allocator, io, .{ .argv = args, - .env_map = env_map, + .environ_map = environ_map, .cwd = cwd, .max_output_bytes = max_doc_file_size, }); diff --git a/tools/incr-check.zig b/tools/incr-check.zig index 1404d44f71f8b35ee2d63149808bd5280e8c3c61..2dfb23447a13f5744124b306159d018507f3b6d2 100644 --- a/tools/incr-check.zig +++ b/tools/incr-check.zig @@ -31,7 +31,7 @@ pub fn main(init: std.process.Init) !void { const fatal = std.process.fatal; const arena = init.arena.allocator(); const io = init.io; - const env_map = init.env_map; + const environ_map = init.environ_map; const cwd_path = try std.process.getCwdAlloc(arena); var opt_zig_exe: ?[]const u8 = null; @@ -113,9 +113,9 @@ pub fn main(init: std.process.Init) !void { } // Convert paths to be relative to the cwd of the subprocess. - const resolved_zig_exe = try Dir.path.relative(arena, cwd_path, env_map, tmp_dir_path, zig_exe); + const resolved_zig_exe = try Dir.path.relative(arena, cwd_path, environ_map, tmp_dir_path, zig_exe); const opt_resolved_lib_dir = if (opt_lib_dir) |lib_dir| - try Dir.path.relative(arena, cwd_path, env_map, tmp_dir_path, lib_dir) + try Dir.path.relative(arena, cwd_path, environ_map, tmp_dir_path, lib_dir) else null; @@ -176,7 +176,7 @@ pub fn main(init: std.process.Init) !void { var cc_child_args: std.ArrayList([]const u8) = .empty; if (target.backend == .cbe) { const resolved_cc_zig_exe = if (opt_cc_zig) |cc_zig_exe| - try Dir.path.relative(arena, cwd_path, env_map, tmp_dir_path, cc_zig_exe) + try Dir.path.relative(arena, cwd_path, environ_map, tmp_dir_path, cc_zig_exe) else resolved_zig_exe; diff --git a/tools/process_headers.zig b/tools/process_headers.zig index a3e6bbc439abe3e42d102e1dbc41e6b6126643df..deb1f01b5ebde66a342846d105c8fd426c52cedf 100644 --- a/tools/process_headers.zig +++ b/tools/process_headers.zig @@ -132,7 +132,7 @@ pub fn main(init: std.process.Init) !void { const io = init.io; const args = try init.minimal.args.toSlice(arena); const cwd_path = try std.process.getCwdAlloc(arena); - const env_map = init.env_map; + const environ_map = init.environ_map; var search_paths = std.array_list.Managed([]const u8).init(arena); var opt_out_dir: ?[]const u8 = null; @@ -256,7 +256,7 @@ pub fn main(init: std.process.Init) !void { switch (entry.kind) { .directory => try dir_stack.append(full_path), .file, .sym_link => { - const rel_path = try Dir.path.relative(arena, cwd_path, env_map, target_include_dir, full_path); + const rel_path = try Dir.path.relative(arena, cwd_path, environ_map, target_include_dir, full_path); const max_size = 2 * 1024 * 1024 * 1024; const raw_bytes = try Dir.cwd().readFileAlloc(io, full_path, arena, .limited(max_size)); const trimmed = std.mem.trim(u8, raw_bytes, " \r\n\t"); diff --git a/tools/update-linux-headers.zig b/tools/update-linux-headers.zig index 26cf1fd1439c15dd37830e6e3351d7654ced573c..e05ba082031cbee62f0aab9638f127bfbc2ae0ff 100644 --- a/tools/update-linux-headers.zig +++ b/tools/update-linux-headers.zig @@ -145,7 +145,7 @@ pub fn main(init: std.process.Init) !void { const arena = init.arena.allocator(); const io = init.io; const args = try init.minimal.args.toSlice(arena); - const env_map = init.env_map; + const environ_map = init.environ_map; const cwd = try std.process.getCwdAlloc(arena); var search_paths = std.array_list.Managed([]const u8).init(arena); @@ -209,7 +209,7 @@ pub fn main(init: std.process.Init) !void { switch (entry.kind) { .directory => try dir_stack.append(full_path), .file => { - const rel_path = try Dir.path.relative(arena, cwd, env_map, target_include_dir, full_path); + const rel_path = try Dir.path.relative(arena, cwd, environ_map, target_include_dir, full_path); const max_size = 2 * 1024 * 1024 * 1024; const raw_bytes = try Dir.cwd().readFileAlloc(io, full_path, arena, .limited(max_size)); const trimmed = std.mem.trim(u8, raw_bytes, " \r\n\t"); -- 2.54.0 From b32a38ad2753df8df40008e279fb9f645de29c47 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 2 Jan 2026 22:02:40 -0800 Subject: [PATCH 36/60] build: fix file system watching compilation on macOS --- lib/compiler/build_runner.zig | 2 +- lib/std/Build/Watch.zig | 21 +++++++++++---------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/lib/compiler/build_runner.zig b/lib/compiler/build_runner.zig index 0d18d835a4295550f304f1dd1c854e20e6538f89..82fc696618893810090a42a104de7a3e35611560 100644 --- a/lib/compiler/build_runner.zig +++ b/lib/compiler/build_runner.zig @@ -544,7 +544,7 @@ pub fn main(init: process.Init.Minimal) !void { var w: Watch = w: { if (!watch) break :w undefined; if (!Watch.have_impl) fatal("--watch not yet implemented for {t}", .{builtin.os.tag}); - break :w try .init(); + break :w try .init(graph.cache.cwd); }; const now = Io.Clock.Timestamp.now(io, .awake) catch |err| fatal("failed to collect timestamp: {t}", .{err}); diff --git a/lib/std/Build/Watch.zig b/lib/std/Build/Watch.zig index 9042241b5539935956f79d86c1306764f428c8ad..0a0178a018b535f5d06c11275c87753a4a6aef2d 100644 --- a/lib/std/Build/Watch.zig +++ b/lib/std/Build/Watch.zig @@ -99,7 +99,8 @@ const Os = switch (builtin.os.tag) { }; }; - fn init() !Watch { + fn init(cwd_path: []const u8) !Watch { + _ = cwd_path; return .{ .dir_table = .{}, .dir_count = 0, @@ -427,7 +428,8 @@ const Os = switch (builtin.os.tag) { } }; - fn init() !Watch { + fn init(cwd_path: []const u8) !Watch { + _ = cwd_path; return .{ .dir_table = .{}, .dir_count = 0, @@ -658,14 +660,13 @@ const Os = switch (builtin.os.tag) { const EV = std.c.EV; const NOTE = std.c.NOTE; - fn init() !Watch { - const kq_fd = try posix.kqueue(); - errdefer posix.close(kq_fd); + fn init(cwd_path: []const u8) !Watch { + _ = cwd_path; return .{ .dir_table = .{}, .dir_count = 0, .os = .{ - .kq_fd = kq_fd, + .kq_fd = try posix.kqueue(), .handles = .empty, }, .generation = 0, @@ -841,9 +842,9 @@ const Os = switch (builtin.os.tag) { .macos => struct { fse: FsEvents, - fn init() !Watch { + fn init(cwd_path: []const u8) !Watch { return .{ - .os = .{ .fse = try .init() }, + .os = .{ .fse = try .init(cwd_path) }, .dir_count = 0, .dir_table = undefined, .generation = undefined, @@ -863,8 +864,8 @@ const Os = switch (builtin.os.tag) { else => void, }; -pub fn init() !Watch { - return Os.init(); +pub fn init(cwd_path: []const u8) !Watch { + return Os.init(cwd_path); } pub const Match = struct { -- 2.54.0 From 84da158afb991c1c56d31c0132e177696c72194f Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 2 Jan 2026 22:03:58 -0800 Subject: [PATCH 37/60] test-stack-traces: update to new main API --- test/src/convert-stack-trace.zig | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/test/src/convert-stack-trace.zig b/test/src/convert-stack-trace.zig index d23623c396826b9488a75eb67dcb2a3fb5137567..272c43e31171494f8df667690e4a725c404bb9b7 100644 --- a/test/src/convert-stack-trace.zig +++ b/test/src/convert-stack-trace.zig @@ -24,20 +24,13 @@ //! //! With these transformations, the test harness can safely do string comparisons. -pub fn main() !void { - var arena_instance: std.heap.ArenaAllocator = .init(std.heap.page_allocator); - defer arena_instance.deinit(); - const arena = arena_instance.allocator(); +pub fn main(init: std.process.Init) !void { + const arena = init.arena.allocator(); + const io = init.io; + const args = try init.minimal.args.toSlice(arena); - const args = try std.process.argsAlloc(arena); if (args.len != 2) std.process.fatal("usage: convert-stack-trace path/to/test/output", .{}); - const gpa = arena; - - var threaded: std.Io.Threaded = .init(gpa, .{}); - defer threaded.deinit(); - const io = threaded.io(); - var read_buf: [1024]u8 = undefined; var write_buf: [1024]u8 = undefined; -- 2.54.0 From 08cc9e8d59b8af37cadee1d4529fa4d3a83be0a1 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 2 Jan 2026 22:11:44 -0800 Subject: [PATCH 38/60] std.Io.Threaded: std.process -> process --- lib/std/Io/Threaded.zig | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index cd5ac69680504860187482e6c44a91f5fa8a111d..a50618ceb9dcd02870edc770c6612eb63fd9cbba 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -12764,13 +12764,13 @@ fn scanEnviron(t: *Threaded) void { } } -fn processReplace(userdata: ?*anyopaque, options: std.process.ReplaceOptions) std.process.ReplaceError { +fn processReplace(userdata: ?*anyopaque, options: process.ReplaceOptions) process.ReplaceError { _ = userdata; _ = options; @panic("TODO processReplace"); } -fn processReplacePath(userdata: ?*anyopaque, dir: Dir, options: std.process.ReplaceOptions) std.process.ReplaceError { +fn processReplacePath(userdata: ?*anyopaque, dir: Dir, options: process.ReplaceOptions) process.ReplaceError { _ = userdata; _ = dir; _ = options; @@ -13020,7 +13020,7 @@ fn processSpawnPosix(userdata: ?*anyopaque, options: process.SpawnOptions) proce }; } -fn childWait(userdata: ?*anyopaque, child: *std.process.Child) process.Child.WaitError!process.Child.Term { +fn childWait(userdata: ?*anyopaque, child: *process.Child) process.Child.WaitError!process.Child.Term { if (native_os == .wasi) unreachable; const t: *Threaded = @ptrCast(@alignCast(userdata)); switch (native_os) { @@ -13029,7 +13029,7 @@ fn childWait(userdata: ?*anyopaque, child: *std.process.Child) process.Child.Wai } } -fn childKill(userdata: ?*anyopaque, child: *std.process.Child) void { +fn childKill(userdata: ?*anyopaque, child: *process.Child) void { if (native_os == .wasi) unreachable; const t: *Threaded = @ptrCast(@alignCast(userdata)); if (is_windows) { -- 2.54.0 From baa49e59294cb8a637eff7c0b3f07c523c91c18b Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 2 Jan 2026 22:25:51 -0800 Subject: [PATCH 39/60] std.Io.Threaded: implement processReplace --- lib/std/Io/Threaded.zig | 60 ++++++++++++++++++++++++++--------------- lib/std/process.zig | 4 +-- 2 files changed, 40 insertions(+), 24 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index a50618ceb9dcd02870edc770c6612eb63fd9cbba..6d221d8c011ecf5fdfd35cd91b96fe587ba3281d 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -1122,6 +1122,7 @@ const Syscall = struct { const max_iovecs_len = 8; const splat_buffer_size = 64; +const default_PATH = "/usr/local/bin:/bin/:/usr/bin"; comptime { if (@TypeOf(posix.IOV_MAX) != void) assert(max_iovecs_len <= posix.IOV_MAX); @@ -12765,12 +12766,37 @@ fn scanEnviron(t: *Threaded) void { } fn processReplace(userdata: ?*anyopaque, options: process.ReplaceOptions) process.ReplaceError { - _ = userdata; - _ = options; - @panic("TODO processReplace"); + const t: *Threaded = @ptrCast(@alignCast(userdata)); + + if (!process.can_replace) return error.OperationUnsupported; + + t.scanEnviron(); // for PATH + const PATH = t.environ.string.PATH orelse default_PATH; + + var arena_allocator = std.heap.ArenaAllocator.init(t.allocator); + defer arena_allocator.deinit(); + const arena = arena_allocator.allocator(); + + const argv_buf = try arena.allocSentinel(?[*:0]const u8, options.argv.len, null); + for (options.argv, 0..) |arg, i| argv_buf[i] = (try arena.dupeZ(u8, arg)).ptr; + + const envp: [*:null]const ?[*:0]const u8 = m: { + const prog_fd: i32 = -1; + if (options.environ_map) |environ_map| { + break :m (try environ_map.createBlockPosix(arena, .{ + .zig_progress_fd = prog_fd, + })).ptr; + } + break :m (try process.Environ.createBlockPosix(t.environ.process_environ, arena, .{ + .zig_progress_fd = prog_fd, + })).ptr; + }; + + return posixExecv(options.expand_arg0, argv_buf.ptr[0].?, argv_buf.ptr, envp, PATH); } fn processReplacePath(userdata: ?*anyopaque, dir: Dir, options: process.ReplaceOptions) process.ReplaceError { + if (!process.can_replace) return error.OperationUnsupported; _ = userdata; _ = dir; _ = options; @@ -12778,6 +12804,7 @@ fn processReplacePath(userdata: ?*anyopaque, dir: Dir, options: process.ReplaceO } fn processSpawnPath(userdata: ?*anyopaque, dir: Dir, options: process.SpawnOptions) process.SpawnError!process.Child { + if (!process.can_spawn) return error.OperationUnsupported; _ = userdata; _ = dir; _ = options; @@ -12903,7 +12930,7 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp errdefer destroyPipe(err_pipe); t.scanEnviron(); // for PATH - const PATH = t.environ.string.PATH orelse "/usr/local/bin:/bin/:/usr/bin"; + const PATH = t.environ.string.PATH orelse default_PATH; const pid_result = try posix.fork(); if (pid_result == 0) { @@ -12954,7 +12981,7 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp } } - const err = execvpeZ_expandArg0(options.expand_arg0, argv_buf.ptr[0].?, argv_buf.ptr, envp, PATH); + const err = posixExecv(options.expand_arg0, argv_buf.ptr[0].?, argv_buf.ptr, envp, PATH); forkBail(err_pipe[1], err); } @@ -14361,7 +14388,7 @@ fn testArgvToCommandLineWindows(argv: []const []const u8, expected_cmd_line: []c try std.testing.expectEqualStrings(expected_cmd_line, cmd_line); } -fn execvpeZ_expandArg0( +fn posixExecv( arg0_expand: process.ArgExpansion, file: [*:0]const u8, child_argv: [*:null]?[*:0]const u8, @@ -14369,10 +14396,10 @@ fn execvpeZ_expandArg0( PATH: []const u8, ) process.ReplaceError { const file_slice = std.mem.sliceTo(file, 0); - if (std.mem.findScalar(u8, file_slice, '/') != null) return execveZ(file, child_argv, envp); + if (std.mem.findScalar(u8, file_slice, '/') != null) return posixExecvPath(file, child_argv, envp); // Use of PATH_MAX here is valid as the path_buf will be passed - // directly to the operating system in execveZ. + // directly to the operating system in posixExecvPath. var path_buf: [posix.PATH_MAX]u8 = undefined; var it = std.mem.tokenizeScalar(u8, PATH, ':'); var seen_eacces = false; @@ -14397,7 +14424,7 @@ fn execvpeZ_expandArg0( .expand => child_argv[0] = full_path, .no_expand => {}, } - err = execveZ(full_path, child_argv, envp); + err = posixExecvPath(full_path, child_argv, envp); switch (err) { error.AccessDenied => seen_eacces = true, error.FileNotFound, error.NotDir => {}, @@ -14408,8 +14435,8 @@ fn execvpeZ_expandArg0( return err; } -/// This function ignores PATH environment variable. See `execvpeZ` for that. -pub fn execveZ( +/// This function ignores PATH environment variable. +pub fn posixExecvPath( path: [*:0]const u8, child_argv: [*:null]const ?[*:0]const u8, envp: [*:null]const ?[*:0]const u8, @@ -14447,17 +14474,6 @@ pub fn execveZ( } } -/// This function also uses the PATH environment variable to get the full path to the executable. -/// If `file` is an absolute path, this is the same as `execveZ`. -pub fn execvpeZ( - file: [*:0]const u8, - argv_ptr: [*:null]const ?[*:0]const u8, - envp: [*:null]const ?[*:0]const u8, - PATH: []const u8, -) process.ReplaceError { - return execvpeZ_expandArg0(.no_expand, file, argv_ptr, envp, PATH); -} - fn windowsMakePipeIn(rd: *?windows.HANDLE, wr: *?windows.HANDLE, sattr: *const windows.SECURITY_ATTRIBUTES) !void { var rd_h: windows.HANDLE = undefined; var wr_h: windows.HANDLE = undefined; diff --git a/lib/std/process.zig b/lib/std/process.zig index 96da85ea1774052984fa73e6fc47afdb07091752..f7f0e8c30f29b326bade43a2f8490a2b1e26d4ce 100644 --- a/lib/std/process.zig +++ b/lib/std/process.zig @@ -288,11 +288,11 @@ pub const ReplaceError = error{ FileBusy, ProcessFdQuotaExceeded, SystemFdQuotaExceeded, -} || Io.Dir.PathNameError || Io.Cancelable || Io.UnexpectedError; +} || Allocator.Error || Io.Dir.PathNameError || Io.Cancelable || Io.UnexpectedError; pub const ReplaceOptions = struct { argv: []const []const u8, - arg0_expand: ArgExpansion = .no_expand, + expand_arg0: ArgExpansion = .no_expand, /// Replaces the environment when provided. The PATH value from here is /// never used to resolve `argv[0]`. environ_map: ?*const Environ.Map = null, -- 2.54.0 From e55fa3d525aeb72d0b8efec96daa84c32c38a1c5 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 2 Jan 2026 22:49:02 -0800 Subject: [PATCH 40/60] fix two standalone tests on windows --- test/standalone/empty_env/build.zig | 2 +- test/standalone/windows_argv/build.zig | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/standalone/empty_env/build.zig b/test/standalone/empty_env/build.zig index 0eb8087ed2b9f2df6494113feb16812602ab4b01..97f77b9e3ed30f8fee19cac3c9f0fe4a1b0a1cfe 100644 --- a/test/standalone/empty_env/build.zig +++ b/test/standalone/empty_env/build.zig @@ -7,7 +7,7 @@ pub fn build(b: *std.Build) void { const optimize: std.builtin.OptimizeMode = .Debug; - if (builtin.os.tag == .windows and std.process.hasEnvVarConstant("ConEmuHWND")) { + if (builtin.os.tag == .windows and b.graph.environ_map.contains("ConEmuHWND")) { // ConEmu injects environment variables into processes before they are executed // depending on user settings. This obviously invalidates the test, so skipping // it is the best option. diff --git a/test/standalone/windows_argv/build.zig b/test/standalone/windows_argv/build.zig index afe6dd80e5da2a950a317aa13b0b290c88a14781..9b507e978a1d53665800f9c89830690943c26270 100644 --- a/test/standalone/windows_argv/build.zig +++ b/test/standalone/windows_argv/build.zig @@ -67,7 +67,7 @@ pub fn build(b: *std.Build) !void { // Only target the MSVC ABI if MSVC/Windows SDK is available const has_msvc = has_msvc: { - const sdk = std.zig.WindowsSdk.find(b.allocator, b.graph.io, builtin.cpu.arch) catch |err| switch (err) { + const sdk = std.zig.WindowsSdk.find(b.allocator, b.graph.io, builtin.cpu.arch, &b.graph.environ_map) catch |err| switch (err) { error.OutOfMemory => @panic("oom"), else => break :has_msvc false, }; -- 2.54.0 From dd7be75f7c5a52f838eca0d3f3cd3c7b552adff1 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 3 Jan 2026 00:52:15 -0800 Subject: [PATCH 41/60] std.process: add missing error.OperationUnsupported --- lib/std/Io/Threaded.zig | 2 +- lib/std/process.zig | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 6d221d8c011ecf5fdfd35cd91b96fe587ba3281d..fc2ddd8a24ca78ae8f8130eb0fe54177ceef8a81 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -12820,7 +12820,7 @@ const processSpawn = switch (native_os) { fn processSpawnUnsupported(userdata: ?*anyopaque, options: process.SpawnOptions) process.SpawnError!process.Child { _ = userdata; _ = options; - return error.Unexpected; + return error.OperationUnsupported; } const Spawned = struct { diff --git a/lib/std/process.zig b/lib/std/process.zig index f7f0e8c30f29b326bade43a2f8490a2b1e26d4ce..5e4fc267db5eed72bf8005da3fa2fbe31d8a158f 100644 --- a/lib/std/process.zig +++ b/lib/std/process.zig @@ -328,6 +328,8 @@ pub const ArgExpansion = enum { expand, no_expand }; pub const WindowsExtension = enum { bat, cmd, com, exe }; pub const SpawnError = error{ + /// The operating system does not support creating child processes. + OperationUnsupported, OutOfMemory, /// POSIX-only. `StdIo.ignore` was selected and opening `/dev/null` returned ENODEV. NoDevice, -- 2.54.0 From 88dd682155a38843517ca79ae84f1f0a0d3c5cf9 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 3 Jan 2026 00:52:37 -0800 Subject: [PATCH 42/60] Compilation: revert bad code transformation I added `unreachable` in this branch based on a misunderstanding of the original control flow. --- src/Compilation.zig | 61 ++++++++++++++++++++++----------------------- 1 file changed, 30 insertions(+), 31 deletions(-) diff --git a/src/Compilation.zig b/src/Compilation.zig index 4b5b1e3ff8552225fb22933a0d8c099bdc6b81ca..82f45ee9f0189bdc1d26b292624e4c2ab42726ea 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -6374,41 +6374,40 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr }, else => std.process.abort(), } - unreachable; - } + } else { + var child = try std.process.spawn(io, .{ + .argv = argv.items, + .stdin = .ignore, + .stdout = .ignore, + .stderr = .pipe, + }); - var child = try std.process.spawn(io, .{ - .argv = argv.items, - .stdin = .ignore, - .stdout = .ignore, - .stderr = .pipe, - }); + var stderr_reader = child.stderr.?.readerStreaming(io, &.{}); + const stderr = try stderr_reader.interface.allocRemaining(arena, .limited(std.math.maxInt(u32))); - var stderr_reader = child.stderr.?.readerStreaming(io, &.{}); - const stderr = try stderr_reader.interface.allocRemaining(arena, .limited(std.math.maxInt(u32))); + const term = child.wait(io) catch |err| + return comp.failCObj(c_object, "failed to spawn zig clang {s}: {t}", .{ argv.items[0], err }); - const term = child.wait(io) catch |err| - return comp.failCObj(c_object, "failed to spawn zig clang {s}: {t}", .{ argv.items[0], err }); - - switch (term) { - .exited => |code| if (code != 0) if (out_diag_path) |diag_file_path| { - const bundle = CObject.Diag.Bundle.parse(gpa, io, diag_file_path) catch |err| { - log.err("{}: failed to parse clang diagnostics: {s}", .{ err, stderr }); + switch (term) { + .exited => |code| if (code != 0) if (out_diag_path) |diag_file_path| { + const bundle = CObject.Diag.Bundle.parse(gpa, io, diag_file_path) catch |err| { + log.err("{}: failed to parse clang diagnostics: {s}", .{ err, stderr }); + return comp.failCObj(c_object, "clang exited with code {d}", .{code}); + }; + return comp.failCObjWithOwnedDiagBundle(c_object, bundle); + } else { + log.err("clang failed with stderr: {s}", .{stderr}); return comp.failCObj(c_object, "clang exited with code {d}", .{code}); - }; - return comp.failCObjWithOwnedDiagBundle(c_object, bundle); - } else { - log.err("clang failed with stderr: {s}", .{stderr}); - return comp.failCObj(c_object, "clang exited with code {d}", .{code}); - }, - .signal => |sig| { - log.err("clang failed with stderr: {s}", .{stderr}); - return comp.failCObj(c_object, "clang terminated with signal {t}", .{sig}); - }, - else => { - log.err("clang terminated with stderr: {s}", .{stderr}); - return comp.failCObj(c_object, "clang terminated unexpectedly", .{}); - }, + }, + .signal => |sig| { + log.err("clang failed with stderr: {s}", .{stderr}); + return comp.failCObj(c_object, "clang terminated with signal {t}", .{sig}); + }, + else => { + log.err("clang terminated with stderr: {s}", .{stderr}); + return comp.failCObj(c_object, "clang terminated unexpectedly", .{}); + }, + } } } else { const exit_code = try clangMain(arena, argv.items); -- 2.54.0 From 0b856d12a09df8e9b3a09494ec5488b771c20d0b Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 3 Jan 2026 01:22:30 -0800 Subject: [PATCH 43/60] build: remove freebsd max_rss special casing error: memory usage peaked at 6.05GB (6047436800 bytes), exceeding the declared upper bound of 6.04GB (6044158771 bytes) This value isn't meant to be OS-specific anyway. --- build.zig | 4 ---- 1 file changed, 4 deletions(-) diff --git a/build.zig b/build.zig index 1cef23d2066f7b5e3a05dc8f08361daf80be99d8..2bd61e605d566eec94efb26a138ce8d36d981008 100644 --- a/build.zig +++ b/build.zig @@ -830,10 +830,6 @@ fn addCompilerStep(b: *std.Build, options: AddCompilerModOptions) *std.Build.Ste const exe = b.addExecutable(.{ .name = "zig", .max_rss = switch (b.graph.host.result.os.tag) { - .freebsd => switch (b.graph.host.result.cpu.arch) { - .x86_64 => 6_044_158_771, - else => 6_100_000_000, - }, .linux => switch (b.graph.host.result.cpu.arch) { .aarch64 => 6_240_805_683, .loongarch64 => 5_024_158_515, -- 2.54.0 From 2fee64ceb08328ccc9f98879a544eae971248493 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 3 Jan 2026 01:35:36 -0800 Subject: [PATCH 44/60] update init template for new main API --- lib/init/src/main.zig | 20 +++++++++++--------- lib/std/Build/Step/Run.zig | 21 ++++++++++++++------- test/tests.zig | 2 +- 3 files changed, 26 insertions(+), 17 deletions(-) diff --git a/lib/init/src/main.zig b/lib/init/src/main.zig index 865e6c53228c9c69f441eec4427656d671cb9ce3..2c6df25be543819dfd345b0c8b5e69588ed51afe 100644 --- a/lib/init/src/main.zig +++ b/lib/init/src/main.zig @@ -3,19 +3,21 @@ const Io = std.Io; const _NAME = @import(".NAME"); -pub fn main() !void { +pub fn main(init: std.process.Init) !void { // Prints to stderr, unbuffered, ignoring potential errors. std.debug.print("All your {s} are belong to us.\n", .{"codebase"}); - // In order to allocate memory we must construct an `Allocator` instance. - var debug_allocator: std.heap.DebugAllocator(.{}) = .init; - defer _ = debug_allocator.deinit(); // This checks for leaks. - const gpa = debug_allocator.allocator(); + // This is appropriate for anything that lives as long as the process. + const arena: std.mem.Allocator = init.arena.allocator(); - // In order to do I/O operations we must construct an `Io` instance. - var threaded: std.Io.Threaded = .init(gpa, .{}); - defer threaded.deinit(); - const io = threaded.io(); + // Accessing command line arguments: + const args = try init.minimal.args.toSlice(arena); + for (args) |arg| { + std.log.info("arg: {s}", .{arg}); + } + + // In order to do I/O operations need an `Io` instance. + const io = init.io; // Stdout is for the actual output of your application, for example if you // are implementing gzip, then only the compressed bytes should be sent to diff --git a/lib/std/Build/Step/Run.zig b/lib/std/Build/Step/Run.zig index 7ae9849f645f11a5f9b37396ecb2bef9ce415d18..1084c7fbfd89478853a7b4c607df1b538c18ae7c 100644 --- a/lib/std/Build/Step/Run.zig +++ b/lib/std/Build/Step/Run.zig @@ -603,18 +603,25 @@ pub fn removeEnvironmentVariable(run: *Run, key: []const u8) void { /// Adds a check for exact stderr match. Does not add any other checks. pub fn expectStdErrEqual(run: *Run, bytes: []const u8) void { - const new_check: StdIo.Check = .{ .expect_stderr_exact = run.step.owner.dupe(bytes) }; - run.addCheck(new_check); + run.addCheck(.{ .expect_stderr_exact = run.step.owner.dupe(bytes) }); +} + +pub fn expectStdErrMatch(run: *Run, bytes: []const u8) void { + run.addCheck(.{ .expect_stderr_match = run.step.owner.dupe(bytes) }); } /// Adds a check for exact stdout match as well as a check for exit code 0, if /// there is not already an expected termination check. pub fn expectStdOutEqual(run: *Run, bytes: []const u8) void { - const new_check: StdIo.Check = .{ .expect_stdout_exact = run.step.owner.dupe(bytes) }; - run.addCheck(new_check); - if (!run.hasTermCheck()) { - run.expectExitCode(0); - } + run.addCheck(.{ .expect_stdout_exact = run.step.owner.dupe(bytes) }); + if (!run.hasTermCheck()) run.expectExitCode(0); +} + +/// Adds a check for stdout match as well as a check for exit code 0, if there +/// is not already an expected termination check. +pub fn expectStdOutMatch(run: *Run, bytes: []const u8) void { + run.addCheck(.{ .expect_stdout_match = run.step.owner.dupe(bytes) }); + if (!run.hasTermCheck()) run.expectExitCode(0); } pub fn expectExitCode(run: *Run, code: u8) void { diff --git a/test/tests.zig b/test/tests.zig index 1634112daec7289248b9dca1ba7518e577a162ad..5fc4a967af41d3666230ad0e94db498464e7a302 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -2062,7 +2062,7 @@ pub fn addCliTests(b: *std.Build) *Step { run_run.setCwd(.{ .cwd_relative = tmp_path }); run_run.setName("zig build run"); run_run.expectStdOutEqual("Run `zig build test` to run the tests.\n"); - run_run.expectStdErrEqual("All your codebase are belong to us.\n"); + run_run.expectStdErrMatch("All your codebase are belong to us.\n"); run_run.step.dependOn(&init_exe.step); const cleanup = b.addRemoveDirTree(.{ .cwd_relative = tmp_path }); -- 2.54.0 From e2c04a46518f1c75076e642f56ae9d5bae3da3f3 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 3 Jan 2026 03:17:01 -0800 Subject: [PATCH 45/60] fix some windows compilation errors --- lib/compiler/resinator/main.zig | 2 +- lib/std/process/Environ.zig | 7 ++++-- test/standalone/windows_argv/fuzz.zig | 24 ++++++++----------- test/standalone/windows_argv/lib.zig | 7 ++++-- .../standalone/windows_bat_args/echo-args.zig | 11 ++++----- test/standalone/windows_bat_args/fuzz.zig | 2 +- test/standalone/windows_bat_args/test.zig | 4 ++-- test/standalone/windows_paths/relative.zig | 19 +++++---------- test/standalone/windows_paths/test.zig | 12 ++++------ test/standalone/windows_spawn/main.zig | 5 ++-- 10 files changed, 41 insertions(+), 52 deletions(-) diff --git a/lib/compiler/resinator/main.zig b/lib/compiler/resinator/main.zig index 9e654b4947bfb87b11d8586c8d1f3a3d9dc11885..d1aaa24264d2d57ed9072390ac44cc0477e6d756 100644 --- a/lib/compiler/resinator/main.zig +++ b/lib/compiler/resinator/main.zig @@ -633,7 +633,7 @@ fn getIncludePaths( }; const target = std.zig.resolveTargetQueryOrFatal(io, target_query); const is_native_abi = target_query.isNativeAbi(); - const detected_libc = std.zig.LibCDirs.detect(arena, io, zig_lib_dir, &target, is_native_abi, true, null) catch { + const detected_libc = std.zig.LibCDirs.detect(arena, io, zig_lib_dir, &target, is_native_abi, true, null, environ_map) catch { if (includes == .any) { // fall back to mingw includes = .gnu; diff --git a/lib/std/process/Environ.zig b/lib/std/process/Environ.zig index 39ca80ca8b21e3ba8337c8b9cc40a6da764ea1a5..ba2f70a843a5aec49085d6c96ecb00506925d541 100644 --- a/lib/std/process/Environ.zig +++ b/lib/std/process/Environ.zig @@ -542,14 +542,17 @@ pub fn getPosix(environ: Environ, key: []const u8) ?[:0]const u8 { /// * `contains` pub fn getWindows(environ: Environ, key: [*:0]const u16) ?[:0]const u16 { comptime assert(native_os == .windows); + comptime assert(@TypeOf(environ.block) == void); // '=' anywhere but the start makes this an invalid environment variable name. const key_slice = mem.sliceTo(key, 0); if (key_slice.len > 0 and mem.findScalar(u16, key_slice[1..], '=') != null) return null; + const ptr = std.os.windows.peb().ProcessParameters.Environment; + var i: usize = 0; - while (environ.block[i] != 0) { - const key_value = mem.sliceTo(environ.block[i..], 0); + while (ptr[i] != 0) { + const key_value = mem.sliceTo(ptr[i..], 0); // There are some special environment variables that start with =, // so we need a special case to not treat = as a key/value separator diff --git a/test/standalone/windows_argv/fuzz.zig b/test/standalone/windows_argv/fuzz.zig index bdf40aa4b1d3991b24abde4aa710d10c07a92ee5..7e7f43a6af0742221f5da394f1be1bda90cb0ce8 100644 --- a/test/standalone/windows_argv/fuzz.zig +++ b/test/standalone/windows_argv/fuzz.zig @@ -3,19 +3,15 @@ const builtin = @import("builtin"); const windows = std.os.windows; const Allocator = std.mem.Allocator; -pub fn main() !void { - var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init; - defer std.debug.assert(gpa.deinit() == .ok); - const allocator = gpa.allocator(); - - const args = try std.process.argsAlloc(allocator); - defer std.process.argsFree(allocator, args); +pub fn main(init: std.process.Init) !void { + const gpa = init.gpa; + const args = try init.minimal.args.toSlice(init.arena.allocator()); if (args.len < 2) return error.MissingArgs; const verify_path_wtf8 = args[1]; - const verify_path_w = try std.unicode.wtf8ToWtf16LeAllocZ(allocator, verify_path_wtf8); - defer allocator.free(verify_path_w); + const verify_path_w = try std.unicode.wtf8ToWtf16LeAllocZ(gpa, verify_path_wtf8); + defer gpa.free(verify_path_w); const iterations: u64 = iterations: { if (args.len < 3) break :iterations 0; @@ -41,14 +37,14 @@ pub fn main() !void { std.debug.print("rand seed: {}\n", .{seed}); } - var cmd_line_w_buf = std.array_list.Managed(u16).init(allocator); + var cmd_line_w_buf = std.array_list.Managed(u16).init(gpa); defer cmd_line_w_buf.deinit(); var i: u64 = 0; var errors: u64 = 0; while (iterations == 0 or i < iterations) { - const cmd_line_w = try randomCommandLineW(allocator, rand); - defer allocator.free(cmd_line_w); + const cmd_line_w = try randomCommandLineW(gpa, rand); + defer gpa.free(cmd_line_w); // avoid known difference for 0-length command lines if (cmd_line_w.len == 0 or cmd_line_w[0] == '\x00') continue; @@ -56,8 +52,8 @@ pub fn main() !void { const exit_code = try spawnVerify(verify_path_w, cmd_line_w); if (exit_code != 0) { std.debug.print(">>> found discrepancy <<<\n", .{}); - const cmd_line_wtf8 = try std.unicode.wtf16LeToWtf8Alloc(allocator, cmd_line_w); - defer allocator.free(cmd_line_wtf8); + const cmd_line_wtf8 = try std.unicode.wtf16LeToWtf8Alloc(gpa, cmd_line_w); + defer gpa.free(cmd_line_wtf8); std.debug.print("\"{f}\"\n\n", .{std.zig.fmtString(cmd_line_wtf8)}); errors += 1; diff --git a/test/standalone/windows_argv/lib.zig b/test/standalone/windows_argv/lib.zig index 4171d13dab40f06b0308bf7c7f0a3ceb8eb88293..750501edd21539e5eebe51848aaabd3ace812f59 100644 --- a/test/standalone/windows_argv/lib.zig +++ b/test/standalone/windows_argv/lib.zig @@ -5,7 +5,7 @@ export fn verify(argc: c_int, argv: [*]const [*:0]const u16) c_int { const argv_slice = argv[0..@intCast(argc)]; testArgv(argv_slice) catch |err| switch (err) { error.OutOfMemory => @panic("oom"), - error.Overflow => @panic("bytes needed to contain args would overflow usize"), + error.Unexpected => @panic("unexpected error"), error.ArgvMismatch => return 0, }; return 1; @@ -16,7 +16,10 @@ fn testArgv(expected_args: []const [*:0]const u16) !void { defer arena_state.deinit(); const allocator = arena_state.allocator(); - const args = try std.process.argsAlloc(allocator); + const cmd_line = std.os.windows.peb().ProcessParameters.CommandLine; + const cmd_line_w = cmd_line.Buffer.?[0..@divExact(cmd_line.Length, 2)]; + const raw_args: std.process.Args = .{ .vector = cmd_line_w }; + const args = try raw_args.toSlice(allocator); var wtf8_buf = std.array_list.Managed(u8).init(allocator); var eql = true; diff --git a/test/standalone/windows_bat_args/echo-args.zig b/test/standalone/windows_bat_args/echo-args.zig index 6aeb43d56ca6fb70b5d6d9b8a918c943d66cda9a..a867a1bb2482ac376412958c6f052026653c2dc3 100644 --- a/test/standalone/windows_bat_args/echo-args.zig +++ b/test/standalone/windows_bat_args/echo-args.zig @@ -1,15 +1,12 @@ const std = @import("std"); -pub fn main() !void { - var arena_state = std.heap.ArenaAllocator.init(std.heap.page_allocator); - defer arena_state.deinit(); - const arena = arena_state.allocator(); - - const io = std.Options.debug_io; +pub fn main(init: std.process.Init) !void { + const arena = init.arena.allocator(); + const io = init.io; + const args = try init.minimal.args.toSlice(arena); var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{}); const stdout = &stdout_writer.interface; - var args = try std.process.argsAlloc(arena); for (args[1..], 1..) |arg, i| { try stdout.writeAll(arg); if (i != args.len - 1) try stdout.writeByte('\x00'); diff --git a/test/standalone/windows_bat_args/fuzz.zig b/test/standalone/windows_bat_args/fuzz.zig index e5d84fa4b5d514e886cb657ec7001da31ffde3d7..b82ec8b458e7e589b9d5c5d09d3c84458fe14713 100644 --- a/test/standalone/windows_bat_args/fuzz.zig +++ b/test/standalone/windows_bat_args/fuzz.zig @@ -8,7 +8,7 @@ pub fn main(init: std.process.Init) !void { const gpa = init.gpa; const io = init.io; - var it = try init.minimal.argsAllocator(gpa); + var it = try init.minimal.args.iterateAllocator(gpa); defer it.deinit(); _ = it.next() orelse unreachable; // skip binary name const child_exe_path_orig = it.next() orelse unreachable; diff --git a/test/standalone/windows_bat_args/test.zig b/test/standalone/windows_bat_args/test.zig index 520617aa38bef953d515db575aaa2d1382dfa9c7..fde627a5fec983005acace78a0c92773ded362bc 100644 --- a/test/standalone/windows_bat_args/test.zig +++ b/test/standalone/windows_bat_args/test.zig @@ -6,7 +6,7 @@ pub fn main(init: std.process.Init) !void { const gpa = init.gpa; const io = init.io; - var it = try init.minimal.argsAllocator(gpa); + var it = try init.minimal.args.iterateAllocator(gpa); defer it.deinit(); _ = it.next() orelse unreachable; // skip binary name const child_exe_path_orig = it.next() orelse unreachable; @@ -105,7 +105,7 @@ pub fn main(init: std.process.Init) !void { try std.testing.expectError(error.FileNotFound, testExecBat(gpa, io, absolute_with_trailing, &.{"abc"}, null)); var env = env: { - var env = try std.process.getEnvMap(gpa); + var env = try init.environ_map.clone(gpa); errdefer env.deinit(); // No escaping try env.put("FOO", "123"); diff --git a/test/standalone/windows_paths/relative.zig b/test/standalone/windows_paths/relative.zig index 7b6a51b283a54f6e765900116a9218103727b753..7b3725e4eec0b5e5774f7ac3650321b959193504 100644 --- a/test/standalone/windows_paths/relative.zig +++ b/test/standalone/windows_paths/relative.zig @@ -1,21 +1,14 @@ const std = @import("std"); -pub fn main() !void { - var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init; - defer std.debug.assert(gpa.deinit() == .ok); - const allocator = gpa.allocator(); - - const args = try std.process.argsAlloc(allocator); - defer std.process.argsFree(allocator, args); +pub fn main(init: std.process.Init) !void { + const arena = init.arena.allocator(); + const args = try init.minimal.args.toSlice(arena); + const io = init.io; + const cwd_path = try std.process.getCwdAlloc(arena); if (args.len < 3) return error.MissingArgs; - var threaded: std.Io.Threaded = .init(allocator, .{}); - defer threaded.deinit(); - const io = threaded.io(); - - const relative = try std.fs.path.relative(allocator, args[1], args[2]); - defer allocator.free(relative); + const relative = try std.fs.path.relative(arena, cwd_path, init.environ_map, args[1], args[2]); var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{}); const stdout = &stdout_writer.interface; diff --git a/test/standalone/windows_paths/test.zig b/test/standalone/windows_paths/test.zig index b248cc9277ea6b20850fec480bf74e50cab508c0..94afe163372b1cfdceaa5bd4ac32908b0c711657 100644 --- a/test/standalone/windows_paths/test.zig +++ b/test/standalone/windows_paths/test.zig @@ -1,17 +1,13 @@ const std = @import("std"); const Io = std.Io; -pub fn main() anyerror!void { - var arena_state = std.heap.ArenaAllocator.init(std.heap.page_allocator); - defer arena_state.deinit(); - const arena = arena_state.allocator(); - - const args = try std.process.argsAlloc(arena); +pub fn main(init: std.process.Init) !void { + const arena = init.arena.allocator(); + const args = try init.minimal.args.toSlice(arena); + const io = init.io; if (args.len < 2) return error.MissingArgs; - const io = std.Io.Threaded.global_single_threaded.ioBasic(); - const exe_path = args[1]; const cwd_path = try std.process.getCwdAlloc(arena); diff --git a/test/standalone/windows_spawn/main.zig b/test/standalone/windows_spawn/main.zig index b1fba1bed65c135eeb1ad88972b6db36a30b7476..4a64ca48d6260475b927cea0f0f1cf3252f0dbcb 100644 --- a/test/standalone/windows_spawn/main.zig +++ b/test/standalone/windows_spawn/main.zig @@ -8,8 +8,9 @@ const utf16Literal = std.unicode.utf8ToUtf16LeStringLiteral; pub fn main(init: std.process.Init) !void { const gpa = init.gpa; const io = init.io; + const process_cwd_path = try std.process.getCwdAlloc(init.arena.allocator()); - var it = try init.minimal.argsAllocator(gpa); + var it = try init.minimal.args.iterateAllocator(gpa); defer it.deinit(); _ = it.next() orelse unreachable; // skip binary name const hello_exe_cache_path = it.next() orelse unreachable; @@ -23,7 +24,7 @@ pub fn main(init: std.process.Init) !void { defer gpa.free(tmp_absolute_path_w); const cwd_absolute_path = try Io.Dir.cwd().realPathFileAlloc(io, ".", gpa); defer gpa.free(cwd_absolute_path); - const tmp_relative_path = try std.fs.path.relative(gpa, cwd_absolute_path, tmp_absolute_path); + const tmp_relative_path = try std.fs.path.relative(gpa, process_cwd_path, init.environ_map, cwd_absolute_path, tmp_absolute_path); defer gpa.free(tmp_relative_path); // Clear PATH -- 2.54.0 From e23d980e11cb0d522a651c1dc1d8768050c60822 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 3 Jan 2026 03:20:15 -0800 Subject: [PATCH 46/60] std.process.Environ: skip BE createMapWide test coverage it would be good to fix this but master branch doesn't have coverage either. one thing at a time. --- lib/std/process/Environ.zig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/std/process/Environ.zig b/lib/std/process/Environ.zig index ba2f70a843a5aec49085d6c96ecb00506925d541..5255e1af16ab86f4e1e29158cbfda3c8c8830c66 100644 --- a/lib/std/process/Environ.zig +++ b/lib/std/process/Environ.zig @@ -801,6 +801,8 @@ test "convert from Environ to Map and back again" { } test createMapWide { + if (builtin.cpu.arch.endian() == .big) return error.SkipZigTest; // TODO + const gpa = testing.allocator; var map: Map = .init(gpa); -- 2.54.0 From e19c686c22d6e1666244075909b9fe1993e1ee2b Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 3 Jan 2026 10:28:02 -0800 Subject: [PATCH 47/60] build: bump max_rss for zig this was exceeded on CI. this value is not meant to be so precisely tweaked per OS and arch --- build.zig | 21 +-------------------- 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/build.zig b/build.zig index 2bd61e605d566eec94efb26a138ce8d36d981008..ed2c0f7581f32e0584fc5984e3b9781e1bcb7f98 100644 --- a/build.zig +++ b/build.zig @@ -829,26 +829,7 @@ fn addCompilerMod(b: *std.Build, options: AddCompilerModOptions) *std.Build.Modu fn addCompilerStep(b: *std.Build, options: AddCompilerModOptions) *std.Build.Step.Compile { const exe = b.addExecutable(.{ .name = "zig", - .max_rss = switch (b.graph.host.result.os.tag) { - .linux => switch (b.graph.host.result.cpu.arch) { - .aarch64 => 6_240_805_683, - .loongarch64 => 5_024_158_515, - .powerpc64le => 5_224_914_534, - .riscv64 => 6_996_309_196, - .s390x => 4_997_174_476, - .x86_64 => 6_664_025_702, - else => 7_000_000_000, - }, - .macos => switch (b.graph.host.result.cpu.arch) { - .aarch64 => 6_639_145_779, - else => 6_700_000_000, - }, - .windows => switch (b.graph.host.result.cpu.arch) { - .x86_64 => 5_770_394_009, - else => 5_800_000_000, - }, - else => 7_000_000_000, - }, + .max_rss = 7_000_000_000, .root_module = addCompilerMod(b, options), }); exe.stack_size = stack_size; -- 2.54.0 From 5b9e6a2b2daf6b3c17adee4cea32d9319337dd2e Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 3 Jan 2026 10:31:32 -0800 Subject: [PATCH 48/60] compiler: fix wasi compilation --- src/main.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main.zig b/src/main.zig index ff17b86a3c9d9505a0c7848c522e90306d33d998..8bc3b583910ca58db23dbfdae9fb9f8a46e2632e 100644 --- a/src/main.zig +++ b/src/main.zig @@ -56,11 +56,11 @@ pub const panic = crash_report.panic; pub const debug = crash_report.debug; var wasi_preopens: fs.wasi.Preopens = undefined; -pub fn wasi_cwd() std.os.wasi.fd_t { +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], ".")); - return cwd_fd; + return .{ .handle = cwd_fd }; } const fatal = std.process.fatal; -- 2.54.0 From ff67f70cf983ce772b0b4ef4a891bca1a51781c4 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 3 Jan 2026 14:52:04 -0800 Subject: [PATCH 49/60] start: tweak default allocator choices On wasm targets, when libc is linked, we have to go through libc. --- lib/std/process.zig | 5 +++-- lib/std/start.zig | 27 ++++++++++++++++----------- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/lib/std/process.zig b/lib/std/process.zig index 5e4fc267db5eed72bf8005da3fa2fbe31d8a158f..d8db82cc0582821c4c2b9a6243ca32149905491a 100644 --- a/lib/std/process.zig +++ b/lib/std/process.zig @@ -40,10 +40,11 @@ pub const Init = struct { /// exit. Not threadsafe. arena: *std.heap.ArenaAllocator, /// A default-selected general purpose allocator for temporary heap - /// allocations. Debug mode will set up leak checking. Threadsafe. + /// allocations. Debug mode will set up leak checking if possible. + /// Threadsafe. gpa: Allocator, /// An appropriate default Io implementation based on the target - /// configuration. Debug mode will set up leak checking. + /// configuration. Debug mode will set up leak checking if possible. io: Io, /// Environment variables, initialized with `gpa`. Not threadsafe. environ_map: *Environ.Map, diff --git a/lib/std/start.zig b/lib/std/start.zig index bebfc852f2473fd80376bdffcc661b11d0d1236c..8d2e2a9df2df2ff95459d6a9d5cfd695d24aa1d7 100644 --- a/lib/std/start.zig +++ b/lib/std/start.zig @@ -1,13 +1,16 @@ // This file is included in the compilation unit when exporting an executable. -const root = @import("root"); -const std = @import("std.zig"); const builtin = @import("builtin"); +const native_arch = builtin.cpu.arch; +const native_os = builtin.os.tag; +const is_wasm = native_arch.isWasm(); + +const std = @import("std.zig"); const assert = std.debug.assert; const uefi = std.os.uefi; const elf = std.elf; -const native_arch = builtin.cpu.arch; -const native_os = builtin.os.tag; + +const root = @import("root"); const start_sym_name = if (native_arch.isMIPS()) "__start" else "_start"; @@ -22,7 +25,7 @@ comptime { } } else if (builtin.output_mode == .Exe or @hasDecl(root, "main")) { if (builtin.link_libc and @hasDecl(root, "main")) { - if (native_arch.isWasm()) { + if (is_wasm) { @export(&mainWithoutEnv, .{ .name = "__main_argc_argv" }); } else if (!@typeInfo(@TypeOf(root.main)).@"fn".calling_convention.eql(.c)) { @export(&main, .{ .name = "main" }); @@ -57,7 +60,7 @@ comptime { // case it's not required to provide an entrypoint such as main. @export(&startWasi, .{ .name = wasm_start_sym }); } - } else if (native_arch.isWasm() and native_os == .freestanding) { + } else if (is_wasm and native_os == .freestanding) { // Only call main when defined. For WebAssembly it's allowed to pass `-fno-entry` in which // case it's not required to provide an entrypoint such as main. if (!@hasDecl(root, start_sym_name) and @hasDecl(root, "main")) @export(&wasm_freestanding_start, .{ .name = start_sym_name }); @@ -660,7 +663,7 @@ fn mainWithoutEnv(c_argc: c_int, c_argv: [*][*:0]c_char) callconv(.c) c_int { /// General error message for a malformed return type const bad_main_ret = "expected return type of main to be 'void', '!void', 'noreturn', 'u8', or '!u8'"; -const use_debug_allocator = !native_arch.isWasm() and switch (builtin.mode) { +const use_debug_allocator = !is_wasm and switch (builtin.mode) { .Debug => true, .ReleaseSafe => !builtin.link_libc, // Not ideal, but the best we have for now. .ReleaseFast, .ReleaseSmall => !builtin.link_libc and builtin.single_threaded, // Also not ideal. @@ -675,12 +678,12 @@ inline fn callMain(args: std.process.Args.Vector, environ: std.process.Environ.B .environ = .{ .block = environ }, })); - const gpa = if (native_arch.isWasm()) - std.heap.wasm_allocator - else if (use_debug_allocator) + const gpa = if (use_debug_allocator) debug_allocator.allocator() else if (builtin.link_libc) std.heap.c_allocator + else if (is_wasm) + std.heap.wasm_allocator else if (!builtin.single_threaded) std.heap.smp_allocator else @@ -690,7 +693,9 @@ inline fn callMain(args: std.process.Args.Vector, environ: std.process.Environ.B _ = debug_allocator.deinit(); // Leaks do not affect return code. }; - var arena_allocator = std.heap.ArenaAllocator.init(std.heap.page_allocator); + const arena_backing_allocator = if (is_wasm) gpa else std.heap.page_allocator; + + var arena_allocator = std.heap.ArenaAllocator.init(arena_backing_allocator); defer arena_allocator.deinit(); var threaded: std.Io.Threaded = .init(gpa, .{ -- 2.54.0 From be977e1934c39fd276bd67e7da718a12a6e0f668 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 3 Jan 2026 15:40:24 -0800 Subject: [PATCH 50/60] std.Io.Threaded: integrate with new cancel mechanism --- lib/std/Io/Threaded.zig | 108 +++++++++++++++++++++++------------ lib/std/Io/Threaded/test.zig | 5 +- 2 files changed, 75 insertions(+), 38 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index fc2ddd8a24ca78ae8f8130eb0fe54177ceef8a81..3ea10428412a497263158f5023b62dada00d013e 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -1188,7 +1188,6 @@ pub fn init( .argv0 = options.argv0, .worker_threads = .init(null), .environ = .{ .process_environ = options.environ }, - .robust_cancel = options.robust_cancel, }; if (posix.Sigaction != void) { @@ -13050,9 +13049,10 @@ fn processSpawnPosix(userdata: ?*anyopaque, options: process.SpawnOptions) proce fn childWait(userdata: ?*anyopaque, child: *process.Child) process.Child.WaitError!process.Child.Term { if (native_os == .wasi) unreachable; const t: *Threaded = @ptrCast(@alignCast(userdata)); + _ = t; switch (native_os) { - .windows => return childWaitWindows(t, child), - else => return childWaitPosix(Thread.getCurrent(t), child), + .windows => return childWaitWindows(child), + else => return childWaitPosix(child), } } @@ -13062,7 +13062,8 @@ fn childKill(userdata: ?*anyopaque, child: *process.Child) void { if (is_windows) { childKillWindows(t, child, 1) catch childCleanupWindows(child); } else { - childKillPosix(Thread.getCurrent(t), child) catch childCleanupPosix(child); + childKillPosix(child) catch {}; + childCleanupPosix(child); } } @@ -13087,21 +13088,24 @@ fn childKillWindows(t: *Threaded, child: *process.Child, exit_code: windows.UINT childCleanupWindows(child); } -fn childWaitWindows(t: *Threaded, child: *process.Child) process.Child.WaitError!process.Child.Term { - const current_thread = Thread.getCurrent(t); +fn childWaitWindows(child: *process.Child) process.Child.WaitError!process.Child.Term { const handle = child.id.?; - while (true) { - try current_thread.checkCancel(); - switch (windows.kernel32.WaitForSingleObjectEx(handle, windows.INFINITE, windows.FALSE)) { - windows.WAIT_OBJECT_0 => break, - windows.WAIT_ABANDONED, windows.WAIT_TIMEOUT => continue, - windows.WAIT_FAILED => switch (windows.GetLastError()) { + var syscall: Syscall = try .start(); + while (true) switch (windows.kernel32.WaitForSingleObjectEx(handle, windows.INFINITE, windows.FALSE)) { + windows.WAIT_OBJECT_0 => break syscall.finish(), + windows.WAIT_ABANDONED, windows.WAIT_TIMEOUT => { + try syscall.checkCancel(); + continue; + }, + windows.WAIT_FAILED => { + syscall.finish(); + switch (windows.GetLastError()) { else => |err| return windows.unexpectedError(err), - }, - else => return error.Unexpected, - } - } + } + }, + else => return syscall.fail(error.Unexpected), + }; const term: process.Child.Term = x: { var exit_code: windows.DWORD = undefined; @@ -13142,7 +13146,7 @@ fn childCleanupWindows(child: *process.Child) void { } } -fn childWaitPosix(current_thread: *Thread, child: *process.Child) process.Child.WaitError!process.Child.Term { +fn childWaitPosix(child: *process.Child) process.Child.WaitError!process.Child.Term { defer childCleanupPosix(child); const pid = child.id.?; @@ -13152,29 +13156,29 @@ fn childWaitPosix(current_thread: *Thread, child: *process.Child) process.Child. if (have_wait4) { var status: if (builtin.link_libc) c_int else u32 = undefined; - try current_thread.beginSyscall(); + const syscall: Syscall = try .start(); while (true) switch (posix.errno(posix.system.wait4(pid, &status, 0, ru_ptr))) { .SUCCESS => { - current_thread.endSyscall(); + syscall.finish(); if (ru_ptr) |p| child.resource_usage_statistics.rusage = p.*; return statusToTerm(@bitCast(status)); }, .INTR => { - try current_thread.checkCancel(); + try syscall.checkCancel(); continue; }, - .CHILD => |err| return current_thread.endSyscallErrnoBug(err), // Double-free. - else => |err| return current_thread.endSyscallUnexpectedErrno(err), + .CHILD => |err| return syscall.errnoBug(err), // Double-free. + else => |err| return syscall.unexpectedErrno(err), }; } if (have_waitid) { const linux = std.os.linux; // Bypass libc which has the wrong signature. var info: linux.siginfo_t = undefined; - try current_thread.beginSyscall(); + const syscall: Syscall = try .start(); while (true) switch (linux.errno(linux.waitid(.PID, pid, &info, linux.W.EXITED, ru_ptr))) { .SUCCESS => { - current_thread.endSyscall(); + syscall.finish(); if (ru_ptr) |p| child.resource_usage_statistics.rusage = p.*; const status: u32 = @bitCast(info.fields.common.second.sigchld.status); const code: linux.CLD = @enumFromInt(info.code); @@ -13186,26 +13190,27 @@ fn childWaitPosix(current_thread: *Thread, child: *process.Child) process.Child. }; }, .INTR => { - try current_thread.checkCancel(); + try syscall.checkCancel(); continue; }, - .CHILD => |err| return current_thread.endSyscallErrnoBug(err), // Double-free. - else => |err| return current_thread.endSyscallUnexpectedErrno(err), + .CHILD => |err| return syscall.errnoBug(err), // Double-free. + else => |err| return syscall.unexpectedErrno(err), }; } var status: if (builtin.link_libc) c_int else u32 = undefined; + const syscall: Syscall = try .start(); while (true) switch (posix.errno(posix.system.waitpid(pid, &status, 0))) { .SUCCESS => { - current_thread.endSyscall(); + syscall.finish(); return statusToTerm(@bitCast(status)); }, .INTR => { - try current_thread.checkCancel(); + try syscall.checkCancel(); continue; }, - .CHILD => |err| return current_thread.endSyscallErrnoBug(err), // Double-free. - else => |err| return current_thread.endSyscallUnexpectedErrno(err), + .CHILD => |err| return syscall.errnoBug(err), // Double-free. + else => |err| return syscall.unexpectedErrno(err), }; } @@ -13220,9 +13225,12 @@ fn statusToTerm(status: u32) process.Child.Term { .{ .unknown = status }; } -fn childKillPosix(current_thread: *Thread, child: *process.Child) !void { - // Intentionally uncancelable. - while (true) switch (posix.errno(posix.system.kill(child.id.?, .TERM))) { +fn childKillPosix(child: *process.Child) !void { + // Entire function body is intentionally uncancelable. + + const pid = child.id.?; + + while (true) switch (posix.errno(posix.system.kill(pid, .TERM))) { .SUCCESS => break, .INTR => continue, .PERM => return error.PermissionDenied, @@ -13230,7 +13238,35 @@ fn childKillPosix(current_thread: *Thread, child: *process.Child) !void { .SRCH => |err| return errnoBug(err), else => |err| return posix.unexpectedErrno(err), }; - _ = try childWaitPosix(current_thread, child); + + if (have_wait4) { + var status: if (builtin.link_libc) c_int else u32 = undefined; + while (true) switch (posix.errno(posix.system.wait4(pid, &status, 0, null))) { + .SUCCESS => return, + .INTR => continue, + .CHILD => |err| return errnoBug(err), // Double-free. + else => |err| return posix.unexpectedErrno(err), + }; + } + + if (have_waitid) { + const linux = std.os.linux; // Bypass libc which has the wrong signature. + var info: linux.siginfo_t = undefined; + while (true) switch (linux.errno(linux.waitid(.PID, pid, &info, linux.W.EXITED, null))) { + .SUCCESS => return, + .INTR => continue, + .CHILD => |err| return errnoBug(err), // Double-free. + else => |err| return posix.unexpectedErrno(err), + }; + } + + var status: if (builtin.link_libc) c_int else u32 = undefined; + while (true) switch (posix.errno(posix.system.waitpid(pid, &status, 0))) { + .SUCCESS => return, + .INTR => continue, + .CHILD => |err| return errnoBug(err), // Double-free. + else => |err| return posix.unexpectedErrno(err), + }; } fn childCleanupPosix(child: *process.Child) void { @@ -13537,7 +13573,6 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro } windowsCreateProcessPathExt( - t, arena, &dir_buf, &app_buf, @@ -13573,7 +13608,6 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro try dir_buf.appendSlice(arena, search_path); if (windowsCreateProcessPathExt( - t, arena, &dir_buf, &app_buf, diff --git a/lib/std/Io/Threaded/test.zig b/lib/std/Io/Threaded/test.zig index f41493653fb62769323a24c61b2fe7102bb6e753..774949d6762477bc4f223751a57a0ab936d3302a 100644 --- a/lib/std/Io/Threaded/test.zig +++ b/lib/std/Io/Threaded/test.zig @@ -170,7 +170,10 @@ test "cancel blocked read from pipe" { } }; - var threaded: std.Io.Threaded = .init(std.testing.allocator, .{}); + var threaded: std.Io.Threaded = .init(std.testing.allocator, .{ + .argv0 = .empty, + .environ = .empty, + }); defer threaded.deinit(); const io = threaded.io(); -- 2.54.0 From 2b326d27d572156b534a096005182976b2ac3fe1 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 3 Jan 2026 16:57:55 -0800 Subject: [PATCH 51/60] std.Io.Threaded: improve various Windows logic * cache nul handle for child process execution * make opening the nul file integrate properly with cancelation * replace all calls to SleepEx to parking_sleep.sleep instead, making them properly cancelable. These sleeps are workarounds for Windows kernel bugs. Now you can even cancel while waiting for kernel bug workarounds! --- lib/std/Io/Threaded.zig | 145 +++++++++++++++++++++++++++++++--------- 1 file changed, 113 insertions(+), 32 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 3ea10428412a497263158f5023b62dada00d013e..c96ec0c0e8aa1e02c78e20348684aeee38bed9e1 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -64,6 +64,8 @@ stderr_writer_initialized: bool = false, argv0: Argv0, environ: Environ, +nul_handle: if (is_windows) ?windows.HANDLE else void = if (is_windows) null else {}, + pub const Argv0 = switch (native_os) { .openbsd, .haiku => struct { value: ?[*:0]const u8, @@ -1247,8 +1249,13 @@ pub fn setAsyncLimit(t: *Threaded, new_limit: Io.Limit) void { pub fn deinit(t: *Threaded) void { t.join(); - if (is_windows and t.wsa.status == .initialized) { - if (ws2_32.WSACleanup() != 0) recoverableOsBugDetected(); + if (is_windows) { + if (t.wsa.status == .initialized) { + if (ws2_32.WSACleanup() != 0) recoverableOsBugDetected(); + } + if (t.nul_handle) |handle| { + windows.CloseHandle(handle); + } } if (posix.Sigaction != void and t.have_signal_handler) { if (have_sig_io) posix.sigaction(.IO, &t.old_sig_io, null); @@ -3666,7 +3673,10 @@ pub fn dirOpenFileWtf16( // kernel bug with retry attempts. syscall.finish(); if (max_attempts - attempt == 0) return error.SharingViolation; - _ = w.kernel32.SleepEx((@as(u32, 1) << attempt) >> 1, w.TRUE); + try parking_sleep.sleep(.{ .duration = .{ + .raw = .fromMilliseconds((@as(u32, 1) << attempt) >> 1), + .clock = .awake, + } }); attempt += 1; syscall = try .start(); continue; @@ -3688,7 +3698,10 @@ pub fn dirOpenFileWtf16( // fixed by sleeping and retrying until the error goes away. syscall.finish(); if (max_attempts - attempt == 0) return error.SharingViolation; - _ = w.kernel32.SleepEx((@as(u32, 1) << attempt) >> 1, w.TRUE); + try parking_sleep.sleep(.{ .duration = .{ + .raw = .fromMilliseconds((@as(u32, 1) << attempt) >> 1), + .clock = .awake, + } }); attempt += 1; syscall = try .start(); continue; @@ -13377,34 +13390,7 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro options.stdout == .ignore or options.stderr == .ignore; - // TODO: cache the handle to null file! - const nul_handle = if (any_ignore) - // "\Device\Null" or "\??\NUL" - windows.OpenFile(&[_]u16{ '\\', 'D', 'e', 'v', 'i', 'c', 'e', '\\', 'N', 'u', 'l', 'l' }, .{ - .access_mask = .{ - .STANDARD = .{ .SYNCHRONIZE = true }, - .GENERIC = .{ .WRITE = true, .READ = true }, - }, - .sa = &saAttr, - .creation = .OPEN, - }) catch |err| switch (err) { - error.PathAlreadyExists => return error.Unexpected, // not possible for "NUL" - error.PipeBusy => return error.Unexpected, // not possible for "NUL" - error.NoDevice => return error.Unexpected, // not possible for "NUL" - error.FileNotFound => return error.Unexpected, // not possible for "NUL" - error.AccessDenied => return error.Unexpected, // not possible for "NUL" - error.NameTooLong => return error.Unexpected, // not possible for "NUL" - error.WouldBlock => return error.Unexpected, // not possible for "NUL" - error.NetworkNotFound => return error.Unexpected, // not possible for "NUL" - error.AntivirusInterference => return error.Unexpected, // not possible for "NUL" - error.OperationCanceled => return error.Unexpected, // we're not canceling the operation - else => |e| return e, - } - else - undefined; - defer { - if (any_ignore) posix.close(nul_handle); - } + const nul_handle = if (any_ignore) try getNulHandle(t) else undefined; var g_hChildStd_IN_Rd: ?windows.HANDLE = null; var g_hChildStd_IN_Wr: ?windows.HANDLE = null; @@ -13647,6 +13633,101 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro }; } +fn getNulHandle(t: *Threaded) !windows.HANDLE { + { + t.mutex.lock(); + defer t.mutex.unlock(); + if (t.nul_handle) |handle| return handle; + } + + const device_path = [_]u16{ '\\', 'D', 'e', 'v', 'i', 'c', 'e', '\\', 'N', 'u', 'l', 'l' }; + var nt_name: windows.UNICODE_STRING = .{ + .Length = device_path.len * 2, + .MaximumLength = device_path.len * 2, + .Buffer = @constCast(&device_path), + }; + const attr: windows.OBJECT_ATTRIBUTES = .{ + .Length = @sizeOf(windows.OBJECT_ATTRIBUTES), + .RootDirectory = null, + .Attributes = .{ + .INHERIT = true, + }, + .ObjectName = &nt_name, + .SecurityDescriptor = null, + .SecurityQualityOfService = null, + }; + var io_status_block: windows.IO_STATUS_BLOCK = undefined; + var fresh_handle: windows.HANDLE = undefined; + var syscall: Syscall = try .start(); + while (true) switch (windows.ntdll.NtCreateFile( + &fresh_handle, + .{ + .STANDARD = .{ .SYNCHRONIZE = true }, + .GENERIC = .{ .WRITE = true, .READ = true }, + }, + &attr, + &io_status_block, + null, + .{ .NORMAL = true }, + .VALID_FLAGS, + .OPEN, + .{ + .DIRECTORY_FILE = false, + .NON_DIRECTORY_FILE = true, + .IO = .SYNCHRONOUS_NONALERT, + .OPEN_REPARSE_POINT = false, + }, + null, + 0, + )) { + .SUCCESS => { + syscall.finish(); + t.mutex.lock(); // Another thread might have won the race. + defer t.mutex.unlock(); + if (t.nul_handle) |prev_handle| { + windows.CloseHandle(fresh_handle); + return prev_handle; + } else { + t.nul_handle = fresh_handle; + return fresh_handle; + } + }, + .DELETE_PENDING => { + // This error means that there *was* a file in this location on + // the file system, but it was deleted. However, the OS is not + // finished with the deletion operation, and so this CreateFile + // call has failed. There is not really a sane way to handle + // this other than retrying the creation after the OS finishes + // the deletion. + syscall.finish(); + try parking_sleep.sleep(.{ .duration = .{ + .raw = .fromMilliseconds(1), + .clock = .awake, + } }); + syscall = try .start(); + continue; + }, + .CANCELLED => { + try syscall.checkCancel(); + continue; + }, + .INVALID_PARAMETER => |status| return syscall.ntstatusBug(status), + .OBJECT_PATH_SYNTAX_BAD => |status| return syscall.ntstatusBug(status), + .INVALID_HANDLE => |status| return syscall.ntstatusBug(status), + .OBJECT_NAME_INVALID => return syscall.fail(error.BadPathName), + .OBJECT_NAME_NOT_FOUND => return syscall.fail(error.FileNotFound), + .OBJECT_PATH_NOT_FOUND => return syscall.fail(error.FileNotFound), + .NO_MEDIA_IN_DEVICE => return syscall.fail(error.NoDevice), + .SHARING_VIOLATION => return syscall.fail(error.AccessDenied), + .ACCESS_DENIED => return syscall.fail(error.AccessDenied), + .PIPE_NOT_AVAILABLE => return syscall.fail(error.NoDevice), + .FILE_IS_A_DIRECTORY => return syscall.fail(error.IsDir), + .NOT_A_DIRECTORY => return syscall.fail(error.NotDir), + .USER_MAPPED_FILE => return syscall.fail(error.AccessDenied), + else => |status| return syscall.unexpectedNtstatus(status), + }; +} + /// Expects `app_buf` to contain exactly the app name, and `dir_buf` to contain exactly the dir path. /// After return, `app_buf` will always contain exactly the app name and `dir_buf` will always contain exactly the dir path. /// Note: `app_buf` should not contain any leading path separators. -- 2.54.0 From 0317e95aade4f5651e8f556ca19d1598030715da Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 3 Jan 2026 17:02:42 -0800 Subject: [PATCH 52/60] std.posix: delete some mkdir functions These are handled by Io.Dir now. This is part of an effort to eliminate error.OperationCanceled from the std lib. Also an effort to delete all std.posix functions. --- lib/std/posix.zig | 113 ----------------------------------------- lib/std/posix/test.zig | 2 +- 2 files changed, 1 insertion(+), 114 deletions(-) diff --git a/lib/std/posix.zig b/lib/std/posix.zig index c703897d98d24a57222713892eca66defc2a47a7..33cccb64f4f85626a1bc9a97bf75fc23ea0c1519 100644 --- a/lib/std/posix.zig +++ b/lib/std/posix.zig @@ -832,123 +832,10 @@ pub fn getcwd(out_buffer: []u8) GetCwdError![]u8 { } } -/// On Windows, `sub_dir_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). -/// On WASI, `sub_dir_path` should be encoded as valid UTF-8. -/// On other platforms, `sub_dir_path` is an opaque sequence of bytes with no particular encoding. -pub fn mkdirat(dir_fd: fd_t, sub_dir_path: []const u8, mode: mode_t) MakeDirError!void { - if (native_os == .windows) { - @compileError("use std.Io instead"); - } else if (native_os == .wasi and !builtin.link_libc) { - @compileError("use std.Io instead"); - } else { - const sub_dir_path_c = try toPosixPath(sub_dir_path); - return mkdiratZ(dir_fd, &sub_dir_path_c, mode); - } -} - -/// Same as `mkdirat` except the parameters are null-terminated. -pub fn mkdiratZ(dir_fd: fd_t, sub_dir_path: [*:0]const u8, mode: mode_t) MakeDirError!void { - if (native_os == .windows) { - @compileError("use std.Io instead"); - } else if (native_os == .wasi and !builtin.link_libc) { - @compileError("use std.Io instead"); - } - switch (errno(system.mkdirat(dir_fd, sub_dir_path, mode))) { - .SUCCESS => return, - .ACCES => return error.AccessDenied, - .BADF => unreachable, - .PERM => return error.PermissionDenied, - .DQUOT => return error.DiskQuota, - .EXIST => return error.PathAlreadyExists, - .FAULT => unreachable, - .LOOP => return error.SymLinkLoop, - .MLINK => return error.LinkQuotaExceeded, - .NAMETOOLONG => return error.NameTooLong, - .NOENT => return error.FileNotFound, - .NOMEM => return error.SystemResources, - .NOSPC => return error.NoSpaceLeft, - .NOTDIR => return error.NotDir, - .ROFS => return error.ReadOnlyFileSystem, - // dragonfly: when dir_fd is unlinked from filesystem - .NOTCONN => return error.FileNotFound, - .ILSEQ => return error.BadPathName, - else => |err| return unexpectedErrno(err), - } -} - -pub const MakeDirError = std.Io.Dir.CreateDirError; - -/// Create a directory. -/// `mode` is ignored on Windows and WASI. -/// On Windows, `dir_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). -/// On WASI, `dir_path` should be encoded as valid UTF-8. -/// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding. -pub fn mkdir(dir_path: []const u8, mode: mode_t) MakeDirError!void { - if (native_os == .wasi and !builtin.link_libc) { - return mkdirat(AT.FDCWD, dir_path, mode); - } else if (native_os == .windows) { - const dir_path_w = try windows.sliceToPrefixedFileW(null, dir_path); - return mkdirW(dir_path_w.span(), mode); - } else { - const dir_path_c = try toPosixPath(dir_path); - return mkdirZ(&dir_path_c, mode); - } -} - /// Same as `mkdir` but the parameter is null-terminated. /// On Windows, `dir_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). /// On WASI, `dir_path` should be encoded as valid UTF-8. /// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding. -pub fn mkdirZ(dir_path: [*:0]const u8, mode: mode_t) MakeDirError!void { - if (native_os == .windows) { - const dir_path_w = try windows.cStrToPrefixedFileW(null, dir_path); - return mkdirW(dir_path_w.span(), mode); - } else if (native_os == .wasi and !builtin.link_libc) { - return mkdir(mem.sliceTo(dir_path, 0), mode); - } - switch (errno(system.mkdir(dir_path, mode))) { - .SUCCESS => return, - .ACCES => return error.AccessDenied, - .PERM => return error.PermissionDenied, - .DQUOT => return error.DiskQuota, - .EXIST => return error.PathAlreadyExists, - .FAULT => unreachable, - .LOOP => return error.SymLinkLoop, - .MLINK => return error.LinkQuotaExceeded, - .NAMETOOLONG => return error.NameTooLong, - .NOENT => return error.FileNotFound, - .NOMEM => return error.SystemResources, - .NOSPC => return error.NoSpaceLeft, - .NOTDIR => return error.NotDir, - .ROFS => return error.ReadOnlyFileSystem, - .ILSEQ => return error.BadPathName, - else => |err| return unexpectedErrno(err), - } -} - -/// Windows-only. Same as `mkdir` but the parameters is WTF16LE encoded. -pub fn mkdirW(dir_path_w: []const u16, mode: mode_t) MakeDirError!void { - _ = mode; - const sub_dir_handle = windows.OpenFile(dir_path_w, .{ - .dir = Io.Dir.cwd().handle, - .access_mask = .{ - .STANDARD = .{ .SYNCHRONIZE = true }, - .GENERIC = .{ .READ = true }, - }, - .creation = .CREATE, - .filter = .dir_only, - }) catch |err| switch (err) { - error.IsDir => return error.Unexpected, - error.PipeBusy => return error.Unexpected, - error.NoDevice => return error.Unexpected, - error.WouldBlock => return error.Unexpected, - error.AntivirusInterference => return error.Unexpected, - error.OperationCanceled => return error.Unexpected, - else => |e| return e, - }; - windows.CloseHandle(sub_dir_handle); -} - pub const ChangeCurDirError = error{ AccessDenied, FileSystem, diff --git a/lib/std/posix/test.zig b/lib/std/posix/test.zig index 6b56f0170959783713cfaa6865a6510ac2ec22b1..d79dc547c315a1189e7c5855b203d29bb42aedf5 100644 --- a/lib/std/posix/test.zig +++ b/lib/std/posix/test.zig @@ -527,7 +527,7 @@ test "rename smoke test" { // Create some directory const file_path = try Dir.path.join(gpa, &.{ base_path, "some_dir" }); defer gpa.free(file_path); - try posix.mkdir(file_path, mode); + try Io.Dir.createDirAbsolute(io, file_path, .fromMode(mode)); // Rename the directory const new_file_path = try Dir.path.join(gpa, &.{ base_path, "some_other_dir" }); -- 2.54.0 From f072313e1e8881dcd3b360b03e30397d705c1d00 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 3 Jan 2026 17:09:56 -0800 Subject: [PATCH 53/60] std.Io.Threaded: delete dead comment The problem it talked about is solved now that the direct call to dirOpenDirWindows makes sense in this context. --- lib/std/Io/Threaded.zig | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index c96ec0c0e8aa1e02c78e20348684aeee38bed9e1..f5f7b5fcffca3d03262a6374a4eadefc27d5fe2a 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -13772,11 +13772,6 @@ fn windowsCreateProcessPathExt( // we iterate the matches and take note of any that are either the unappended version, // or a version with a supported PATHEXT appended. We then try calling CreateProcessW // with the found versions in the appropriate order. - - // In the future, child process execution needs to move to Io implementation. - // Under those conditions, here we will have access to lower level directory - // opening function knowing which implementation we are in. Here, we imitate - // that scenario. var dir = dir: { // needs to be null-terminated try dir_buf.append(arena, 0); -- 2.54.0 From 08d8b412e9217018d760666094b89918de02f10e Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 3 Jan 2026 17:32:22 -0800 Subject: [PATCH 54/60] std.Io.Threaded: more robust windows process creation error handling --- lib/std/Io/Threaded.zig | 130 +++++++++++++++++++++++++--------------- 1 file changed, 82 insertions(+), 48 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index f5f7b5fcffca3d03262a6374a4eadefc27d5fe2a..6814d2fe87a3e5734c0e27f5823d56bdad94d9cf 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -13780,7 +13780,29 @@ fn windowsCreateProcessPathExt( const prefixed_path = try windows.wToPrefixedFileW(null, dir_path_z); break :dir dirOpenDirWindows(.cwd(), prefixed_path.span(), .{ .iterate = true, - }) catch return error.FileNotFound; + }) catch |err| switch (err) { + // These errors must not be ignored because they should not be able + // to affect which file is chosen to execute. Also `error.Canceled` + // must never be swallowed. + error.Canceled, + error.SystemResources, + error.Unexpected, + error.ProcessFdQuotaExceeded, + error.SystemFdQuotaExceeded, + => |e| return e, + + error.AccessDenied, + error.PermissionDenied, + error.SymLinkLoop, + error.FileNotFound, + error.NotDir, + error.NoDevice, + error.NetworkNotFound, + error.NameTooLong, + error.BadPathName, + error.DeviceBusy, + => return error.FileNotFound, + }; }; defer windows.CloseHandle(dir.handle); @@ -13984,55 +14006,67 @@ fn windowsCreateProcess( lpStartupInfo: *windows.STARTUPINFOW, lpProcessInformation: *windows.PROCESS_INFORMATION, ) !void { - if (windows.kernel32.CreateProcessW( - app_name, - cmd_line, - null, - null, - windows.TRUE, - flags, - env_ptr, - cwd_ptr, - lpStartupInfo, - lpProcessInformation, - ) == 0) switch (windows.GetLastError()) { - .FILE_NOT_FOUND => return error.FileNotFound, - .PATH_NOT_FOUND => return error.FileNotFound, - .DIRECTORY => return error.FileNotFound, - .ACCESS_DENIED => return error.AccessDenied, - .INVALID_PARAMETER => unreachable, - .INVALID_NAME => return error.InvalidName, - .FILENAME_EXCED_RANGE => return error.NameTooLong, - .SHARING_VIOLATION => return error.FileBusy, + const syscall: Syscall = try .start(); + while (true) { + if (windows.kernel32.CreateProcessW( + app_name, + cmd_line, + null, + null, + windows.TRUE, + flags, + env_ptr, + cwd_ptr, + lpStartupInfo, + lpProcessInformation, + ) != 0) { + return syscall.finish(); + } else switch (windows.GetLastError()) { + .INVALID_PARAMETER => unreachable, + .OPERATION_ABORTED => { + try syscall.checkCancel(); + continue; + }, + .FILE_NOT_FOUND => return syscall.fail(error.FileNotFound), + .PATH_NOT_FOUND => return syscall.fail(error.FileNotFound), + .DIRECTORY => return syscall.fail(error.FileNotFound), + .ACCESS_DENIED => return syscall.fail(error.AccessDenied), + .INVALID_NAME => return syscall.fail(error.InvalidName), + .FILENAME_EXCED_RANGE => return syscall.fail(error.NameTooLong), + .SHARING_VIOLATION => return syscall.fail(error.FileBusy), + .COMMITMENT_LIMIT => return syscall.fail(error.SystemResources), - // These are all the system errors that are mapped to ENOEXEC by - // the undocumented _dosmaperr (old CRT) or __acrt_errno_map_os_error - // (newer CRT) functions. Their code can be found in crt/src/dosmap.c (old SDK) - // or urt/misc/errno.cpp (newer SDK) in the Windows SDK. - .BAD_FORMAT, - .INVALID_STARTING_CODESEG, // MIN_EXEC_ERROR in errno.cpp - .INVALID_STACKSEG, - .INVALID_MODULETYPE, - .INVALID_EXE_SIGNATURE, - .EXE_MARKED_INVALID, - .BAD_EXE_FORMAT, - .ITERATED_DATA_EXCEEDS_64k, - .INVALID_MINALLOCSIZE, - .DYNLINK_FROM_INVALID_RING, - .IOPL_NOT_ENABLED, - .INVALID_SEGDPL, - .AUTODATASEG_EXCEEDS_64k, - .RING2SEG_MUST_BE_MOVABLE, - .RELOC_CHAIN_XEEDS_SEGLIM, - .INFLOOP_IN_RELOC_CHAIN, // MAX_EXEC_ERROR in errno.cpp - // This one is not mapped to ENOEXEC but it is possible, for example - // when calling CreateProcessW on a plain text file with a .exe extension - .EXE_MACHINE_TYPE_MISMATCH, - => return error.InvalidExe, + // These are all the system errors that are mapped to ENOEXEC by + // the undocumented _dosmaperr (old CRT) or __acrt_errno_map_os_error + // (newer CRT) functions. Their code can be found in crt/src/dosmap.c (old SDK) + // or urt/misc/errno.cpp (newer SDK) in the Windows SDK. + .BAD_FORMAT, + .INVALID_STARTING_CODESEG, // MIN_EXEC_ERROR in errno.cpp + .INVALID_STACKSEG, + .INVALID_MODULETYPE, + .INVALID_EXE_SIGNATURE, + .EXE_MARKED_INVALID, + .BAD_EXE_FORMAT, + .ITERATED_DATA_EXCEEDS_64k, + .INVALID_MINALLOCSIZE, + .DYNLINK_FROM_INVALID_RING, + .IOPL_NOT_ENABLED, + .INVALID_SEGDPL, + .AUTODATASEG_EXCEEDS_64k, + .RING2SEG_MUST_BE_MOVABLE, + .RELOC_CHAIN_XEEDS_SEGLIM, + .INFLOOP_IN_RELOC_CHAIN, // MAX_EXEC_ERROR in errno.cpp + // This one is not mapped to ENOEXEC but it is possible, for example + // when calling CreateProcessW on a plain text file with a .exe extension + .EXE_MACHINE_TYPE_MISMATCH, + => return syscall.fail(error.InvalidExe), - .COMMITMENT_LIMIT => return error.SystemResources, - else => |err| return windows.unexpectedError(err), - }; + else => |err| { + syscall.finish(); + return windows.unexpectedError(err); + }, + } + } } /// Case-insensitive WTF-16 lookup -- 2.54.0 From 2c22c3dabf08156eee00b927baf7d6a845a4c98d Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 3 Jan 2026 18:07:02 -0800 Subject: [PATCH 55/60] std.Io.Threaded: make processReplace cancelable In between each attempt to call execve() on a particular file path, it will check cancelation before trying the next PATH. --- lib/std/Io/Threaded.zig | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 6814d2fe87a3e5734c0e27f5823d56bdad94d9cf..5fab16d429d1946e51613af94a6956d9bda16c13 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -1428,7 +1428,7 @@ pub fn io(t: *Threaded) Io { .tryLockStderr = tryLockStderr, .unlockStderr = unlockStderr, .processSetCurrentDir = processSetCurrentDir, - .processReplace = processReplace, // TODO audit for cancelation and unreachable + .processReplace = processReplace, .processReplacePath = processReplacePath, // TODO audit for cancelation and unreachable .processSpawn = processSpawn, // TODO audit for cancelation and unreachable .processSpawnPath = processSpawnPath, // TODO audit for cancelation and unreachable @@ -12946,7 +12946,9 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp const pid_result = try posix.fork(); if (pid_result == 0) { - // we are the child + // We are the child. + if (Thread.current) |current_thread| current_thread.cancel_protection = .blocked; + setUpChildIo(options.stdin, stdin_pipe[0], posix.STDIN_FILENO, dev_null_fd) catch |err| forkBail(err_pipe[1], err); setUpChildIo(options.stdout, stdout_pipe[1], posix.STDOUT_FILENO, dev_null_fd) catch |err| forkBail(err_pipe[1], err); setUpChildIo(options.stderr, stderr_pipe[1], posix.STDERR_FILENO, dev_null_fd) catch |err| forkBail(err_pipe[1], err); @@ -14585,8 +14587,8 @@ pub fn posixExecvPath( child_argv: [*:null]const ?[*:0]const u8, envp: [*:null]const ?[*:0]const u8, ) process.ReplaceError { + try Thread.checkCancel(); switch (posix.errno(posix.system.execve(path, child_argv, envp))) { - .SUCCESS => unreachable, .FAULT => |err| return errnoBug(err), // Bad pointer parameter. .@"2BIG" => return error.SystemResources, .MFILE => return error.ProcessFdQuotaExceeded, -- 2.54.0 From fa315b1060ac2550e3479775cd22871b3df164ad Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 3 Jan 2026 19:04:28 -0800 Subject: [PATCH 56/60] std.Io.Threaded: improve posix process creation * cache /dev/null after opening * make opening /dev/null cancelable * avoid unreachable even when OS does something unexpected --- lib/std/Io/Threaded.zig | 196 +++++++++++++++++++++++------- lib/std/Io/Threaded/test.zig | 2 +- lib/std/os/linux/IoUring/test.zig | 2 +- lib/std/posix.zig | 74 ----------- lib/std/posix/test.zig | 2 +- 5 files changed, 157 insertions(+), 119 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 5fab16d429d1946e51613af94a6956d9bda16c13..e654afaacd59613542e94509dedccca9bd8888dc 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -64,7 +64,7 @@ stderr_writer_initialized: bool = false, argv0: Argv0, environ: Environ, -nul_handle: if (is_windows) ?windows.HANDLE else void = if (is_windows) null else {}, +null_file: NullFile = .{}, pub const Argv0 = switch (native_os) { .openbsd, .haiku => struct { @@ -123,6 +123,34 @@ const Environ = struct { }; }; +pub const NullFile = switch (native_os) { + .windows => struct { + handle: ?windows.HANDLE = null, + + fn deinit(this: *@This()) void { + if (this.handle) |handle| { + windows.CloseHandle(handle); + this.handle = null; + } + } + }, + .wasi, .ios, .tvos, .visionos, .watchos => struct { + fn deinit(this: @This()) void { + _ = this; + } + }, + else => struct { + fd: posix.fd_t = -1, + + fn deinit(this: *@This()) void { + if (this.fd >= 0) { + posix.close(this.fd); + this.fd = -1; + } + } + }, +}; + pub const Pid = if (native_os == .linux) enum(posix.pid_t) { unknown = 0, _, @@ -1249,18 +1277,14 @@ pub fn setAsyncLimit(t: *Threaded, new_limit: Io.Limit) void { pub fn deinit(t: *Threaded) void { t.join(); - if (is_windows) { - if (t.wsa.status == .initialized) { - if (ws2_32.WSACleanup() != 0) recoverableOsBugDetected(); - } - if (t.nul_handle) |handle| { - windows.CloseHandle(handle); - } + if (is_windows and t.wsa.status == .initialized) { + if (ws2_32.WSACleanup() != 0) recoverableOsBugDetected(); } if (posix.Sigaction != void and t.have_signal_handler) { if (have_sig_io) posix.sigaction(.IO, &t.old_sig_io, null); if (have_sig_pipe) posix.sigaction(.PIPE, &t.old_sig_pipe, null); } + t.null_file.deinit(); t.* = undefined; } @@ -1429,9 +1453,9 @@ pub fn io(t: *Threaded) Io { .unlockStderr = unlockStderr, .processSetCurrentDir = processSetCurrentDir, .processReplace = processReplace, - .processReplacePath = processReplacePath, // TODO audit for cancelation and unreachable - .processSpawn = processSpawn, // TODO audit for cancelation and unreachable - .processSpawnPath = processSpawnPath, // TODO audit for cancelation and unreachable + .processReplacePath = processReplacePath, + .processSpawn = processSpawn, + .processSpawnPath = processSpawnPath, .childWait = childWait, // TODO audit for cancelation and unreachable .childKill = childKill, // TODO audit for cancelation and unreachable @@ -1656,6 +1680,7 @@ const have_wait4 = switch (native_os) { else => false, }; +const open_sym = if (posix.lfs64_abi) posix.system.open64 else posix.system.open; const openat_sym = if (posix.lfs64_abi) posix.system.openat64 else posix.system.openat; const fstat_sym = if (posix.lfs64_abi) posix.system.fstat64 else posix.system.fstat; const fstatat_sym = if (posix.lfs64_abi) posix.system.fstatat64 else posix.system.fstatat; @@ -12856,51 +12881,30 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp // turns out, we `dup2` everything anyway, so there's no need! const pipe_flags: posix.O = .{ .CLOEXEC = true }; - const stdin_pipe = if (options.stdin == .pipe) try posix.pipe2(pipe_flags) else undefined; + const stdin_pipe = if (options.stdin == .pipe) try pipe2(pipe_flags) else undefined; errdefer if (options.stdin == .pipe) { destroyPipe(stdin_pipe); }; - const stdout_pipe = if (options.stdout == .pipe) try posix.pipe2(pipe_flags) else undefined; + const stdout_pipe = if (options.stdout == .pipe) try pipe2(pipe_flags) else undefined; errdefer if (options.stdout == .pipe) { destroyPipe(stdout_pipe); }; - const stderr_pipe = if (options.stderr == .pipe) try posix.pipe2(pipe_flags) else undefined; + const stderr_pipe = if (options.stderr == .pipe) try pipe2(pipe_flags) else undefined; errdefer if (options.stderr == .pipe) { destroyPipe(stderr_pipe); }; const any_ignore = (options.stdin == .ignore or options.stdout == .ignore or options.stderr == .ignore); - // TODO: cache file handle of /dev/null! - const dev_null_fd = if (any_ignore) - posix.openZ("/dev/null", .{ .ACCMODE = .RDWR }, 0) catch |err| switch (err) { - error.PathAlreadyExists => unreachable, - error.NoSpaceLeft => unreachable, - error.FileTooBig => unreachable, - error.DeviceBusy => unreachable, - error.FileLocksUnsupported => unreachable, - error.BadPathName => unreachable, // Windows-only - error.WouldBlock => unreachable, - error.NetworkNotFound => unreachable, // Windows-only - error.Canceled => unreachable, // temporarily in the posix error set - error.SharingViolation => unreachable, // Windows-only - error.PipeBusy => unreachable, // not a pipe - error.AntivirusInterference => unreachable, // Windows-only - else => |e| return e, - } - else - undefined; - defer { - if (any_ignore) posix.close(dev_null_fd); - } + const dev_null_fd = if (any_ignore) try getDevNullFd(t) else undefined; const prog_pipe: [2]posix.fd_t = p: { if (options.progress_node.index == .none) { break :p .{ -1, -1 }; } else { // We use CLOEXEC for the same reason as in `pipe_flags`. - break :p try posix.pipe2(.{ .NONBLOCK = true, .CLOEXEC = true }); + break :p try pipe2(.{ .NONBLOCK = true, .CLOEXEC = true }); } }; errdefer destroyPipe(prog_pipe); @@ -12938,13 +12942,23 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp // This pipe communicates to the parent errors in the child between `fork` and `execvpe`. // It is closed by the child (via CLOEXEC) without writing if `execvpe` succeeds. - const err_pipe: [2]posix.fd_t = try posix.pipe2(.{ .CLOEXEC = true }); + const err_pipe: [2]posix.fd_t = try pipe2(.{ .CLOEXEC = true }); errdefer destroyPipe(err_pipe); t.scanEnviron(); // for PATH const PATH = t.environ.string.PATH orelse default_PATH; - const pid_result = try posix.fork(); + const pid_result: posix.pid_t = fork: { + const rc = posix.system.fork(); + switch (posix.errno(rc)) { + .SUCCESS => break :fork @intCast(rc), + .AGAIN => return error.SystemResources, + .NOMEM => return error.SystemResources, + .NOSYS => return error.OperationUnsupported, + else => |err| return posix.unexpectedErrno(err), + } + }; + if (pid_result == 0) { // We are the child. if (Thread.current) |current_thread| current_thread.cancel_protection = .blocked; @@ -13030,6 +13044,45 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp }; } +fn getDevNullFd(t: *Threaded) !posix.fd_t { + { + t.mutex.lock(); + defer t.mutex.unlock(); + if (t.null_file.fd != -1) return t.null_file.fd; + } + const syscall: Syscall = try .start(); + while (true) { + const rc = open_sym("/dev/null", .{ .ACCMODE = .RDWR }, 0); + switch (posix.errno(rc)) { + .SUCCESS => { + syscall.finish(); + const fresh_fd: posix.fd_t = @intCast(rc); + t.mutex.lock(); // Another thread might have won the race. + defer t.mutex.unlock(); + if (t.null_file.fd != -1) { + posix.close(fresh_fd); + return t.null_file.fd; + } else { + t.null_file.fd = fresh_fd; + return fresh_fd; + } + }, + .INTR => { + try syscall.checkCancel(); + continue; + }, + .ACCES => return syscall.fail(error.AccessDenied), + .MFILE => return syscall.fail(error.ProcessFdQuotaExceeded), + .NFILE => return syscall.fail(error.SystemFdQuotaExceeded), + .NODEV => return syscall.fail(error.NoDevice), + .NOENT => return syscall.fail(error.FileNotFound), + .NOMEM => return syscall.fail(error.SystemResources), + .PERM => return syscall.fail(error.PermissionDenied), + else => |err| return syscall.unexpectedErrno(err), + } + } +} + fn processSpawnPosix(userdata: ?*anyopaque, options: process.SpawnOptions) process.SpawnError!process.Child { const t: *Threaded = @ptrCast(@alignCast(userdata)); const spawned = try spawnPosix(t, options); @@ -13639,7 +13692,7 @@ fn getNulHandle(t: *Threaded) !windows.HANDLE { { t.mutex.lock(); defer t.mutex.unlock(); - if (t.nul_handle) |handle| return handle; + if (t.null_file.handle) |handle| return handle; } const device_path = [_]u16{ '\\', 'D', 'e', 'v', 'i', 'c', 'e', '\\', 'N', 'u', 'l', 'l' }; @@ -13686,11 +13739,11 @@ fn getNulHandle(t: *Threaded) !windows.HANDLE { syscall.finish(); t.mutex.lock(); // Another thread might have won the race. defer t.mutex.unlock(); - if (t.nul_handle) |prev_handle| { + if (t.null_file.handle) |prev_handle| { windows.CloseHandle(fresh_handle); return prev_handle; } else { - t.nul_handle = fresh_handle; + t.null_file.handle = fresh_handle; return fresh_handle; } }, @@ -15177,3 +15230,62 @@ fn unpark(tids: []const UnparkTid, addr_hint: ?*const anyopaque) void { else => comptime unreachable, } } + +pub const PipeError = error{ + SystemFdQuotaExceeded, + ProcessFdQuotaExceeded, +} || Io.UnexpectedError; + +pub fn pipe2(flags: posix.O) PipeError![2]posix.fd_t { + var fds: [2]posix.fd_t = undefined; + + if (@TypeOf(posix.system.pipe2) != void) { + switch (posix.errno(posix.system.pipe2(&fds, flags))) { + .SUCCESS => return fds, + .INVAL => |err| return errnoBug(err), // Invalid flags + .NFILE => return error.SystemFdQuotaExceeded, + .MFILE => return error.ProcessFdQuotaExceeded, + else => |err| return posix.unexpectedErrno(err), + } + } + + switch (posix.errno(posix.system.pipe(&fds))) { + .SUCCESS => {}, + .NFILE => return error.SystemFdQuotaExceeded, + .MFILE => return error.ProcessFdQuotaExceeded, + else => |err| return posix.unexpectedErrno(err), + } + errdefer { + posix.close(fds[0]); + posix.close(fds[1]); + } + + // https://github.com/ziglang/zig/issues/18882 + if (@as(u32, @bitCast(flags)) == 0) return fds; + + // CLOEXEC is special, it's a file descriptor flag and must be set using + // F.SETFD. + if (flags.CLOEXEC) for (fds) |fd| { + switch (posix.errno(posix.system.fcntl(fd, posix.F.SETFD, @as(u32, posix.FD_CLOEXEC)))) { + .SUCCESS => {}, + else => |err| return posix.unexpectedErrno(err), + } + }; + + const new_flags: u32 = f: { + var new_flags = flags; + new_flags.CLOEXEC = false; + break :f @bitCast(new_flags); + }; + + // Set every other flag affecting the file status using F.SETFL. + if (new_flags != 0) for (fds) |fd| { + switch (posix.errno(posix.system.fcntl(fd, posix.F.SETFL, new_flags))) { + .SUCCESS => {}, + .INVAL => |err| return errnoBug(err), + else => |err| return posix.unexpectedErrno(err), + } + }; + + return fds; +} diff --git a/lib/std/Io/Threaded/test.zig b/lib/std/Io/Threaded/test.zig index 774949d6762477bc4f223751a57a0ab936d3302a..34aa4a8c689d67ceef1d1ca8e985b5487da9e2f1 100644 --- a/lib/std/Io/Threaded/test.zig +++ b/lib/std/Io/Threaded/test.zig @@ -187,7 +187,7 @@ test "cancel blocked read from pipe" { .bInheritHandle = std.os.windows.FALSE, }), else => { - const pipe = try std.posix.pipe(); + const pipe = try std.Io.Threaded.pipe2(.{}); read_end = .{ .handle = pipe[0] }; write_end = .{ .handle = pipe[1] }; }, diff --git a/lib/std/os/linux/IoUring/test.zig b/lib/std/os/linux/IoUring/test.zig index 251ba14a8b1a04875b39614bd687f46b81f30f29..899a6dae6402f0e38598d85a6d38868c05d4ba83 100644 --- a/lib/std/os/linux/IoUring/test.zig +++ b/lib/std/os/linux/IoUring/test.zig @@ -280,7 +280,7 @@ test "splice/read" { var buffer_read = [_]u8{98} ** 20; try file_src.writeStreamingAll(io, &buffer_write); - const fds = try posix.pipe(); + const fds = try std.Io.Threaded.pipe2(.{}); const pipe_offset: u64 = std.math.maxInt(u64); const sqe_splice_to_pipe = try ring.splice(0x11111111, fd_src, 0, fds[1], pipe_offset, buffer_write.len); diff --git a/lib/std/posix.zig b/lib/std/posix.zig index 33cccb64f4f85626a1bc9a97bf75fc23ea0c1519..fed55f34cfb748744853c300380223b125286332 100644 --- a/lib/std/posix.zig +++ b/lib/std/posix.zig @@ -2105,80 +2105,6 @@ pub fn msync(memory: []align(page_size_min) u8, flags: i32) MSyncError!void { } } -pub const PipeError = error{ - SystemFdQuotaExceeded, - ProcessFdQuotaExceeded, -} || UnexpectedError; - -/// Creates a unidirectional data channel that can be used for interprocess communication. -pub fn pipe() PipeError![2]fd_t { - var fds: [2]fd_t = undefined; - switch (errno(system.pipe(&fds))) { - .SUCCESS => return fds, - .INVAL => unreachable, // Invalid parameters to pipe() - .FAULT => unreachable, // Invalid fds pointer - .NFILE => return error.SystemFdQuotaExceeded, - .MFILE => return error.ProcessFdQuotaExceeded, - else => |err| return unexpectedErrno(err), - } -} - -pub fn pipe2(flags: O) PipeError![2]fd_t { - if (@TypeOf(system.pipe2) != void) { - var fds: [2]fd_t = undefined; - switch (errno(system.pipe2(&fds, flags))) { - .SUCCESS => return fds, - .INVAL => unreachable, // Invalid flags - .FAULT => unreachable, // Invalid fds pointer - .NFILE => return error.SystemFdQuotaExceeded, - .MFILE => return error.ProcessFdQuotaExceeded, - else => |err| return unexpectedErrno(err), - } - } - - const fds: [2]fd_t = try pipe(); - errdefer { - close(fds[0]); - close(fds[1]); - } - - // https://github.com/ziglang/zig/issues/18882 - if (@as(u32, @bitCast(flags)) == 0) - return fds; - - // CLOEXEC is special, it's a file descriptor flag and must be set using - // F.SETFD. - if (flags.CLOEXEC) { - for (fds) |fd| { - switch (errno(system.fcntl(fd, F.SETFD, @as(u32, FD_CLOEXEC)))) { - .SUCCESS => {}, - .INVAL => unreachable, // Invalid flags - .BADF => unreachable, // Always a race condition - else => |err| return unexpectedErrno(err), - } - } - } - - const new_flags: u32 = f: { - var new_flags = flags; - new_flags.CLOEXEC = false; - break :f @bitCast(new_flags); - }; - // Set every other flag affecting the file status using F.SETFL. - if (new_flags != 0) { - for (fds) |fd| { - switch (errno(system.fcntl(fd, F.SETFL, new_flags))) { - .SUCCESS => {}, - .INVAL => unreachable, // Invalid flags - .BADF => unreachable, // Always a race condition - else => |err| return unexpectedErrno(err), - } - } - } - - return fds; -} - pub const SysCtlError = error{ PermissionDenied, SystemResources, diff --git a/lib/std/posix/test.zig b/lib/std/posix/test.zig index d79dc547c315a1189e7c5855b203d29bb42aedf5..f385a82663ff95be31d1e361eb7580fb857abb46 100644 --- a/lib/std/posix/test.zig +++ b/lib/std/posix/test.zig @@ -131,7 +131,7 @@ test "pipe" { if (native_os == .windows or native_os == .wasi) return error.SkipZigTest; - const fds = try posix.pipe(); + const fds = try std.Io.Threaded.pipe2(.{}); try expect((try posix.write(fds[1], "hello")) == 5); var buf: [16]u8 = undefined; try expect((try posix.read(fds[0], buf[0..])) == 5); -- 2.54.0 From 854c076ff7560de9b3f05ee15e1e30a90d738839 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 3 Jan 2026 20:10:13 -0800 Subject: [PATCH 57/60] std.Io.Threaded: improve posix spawning * avoid unreachable when the OS does something unexpected * make waiting for the fork/exec error report cancelable --- lib/std/Io/Threaded.zig | 188 +++++++++++++++++++++++----------- lib/std/posix.zig | 112 -------------------- lib/std/posix/test.zig | 31 ------ test/standalone/posix/cwd.zig | 6 +- 4 files changed, 129 insertions(+), 208 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index e654afaacd59613542e94509dedccca9bd8888dc..6efd50aa0a1b8f0feaa042ae91a552b7ecc1f34f 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -1456,8 +1456,8 @@ pub fn io(t: *Threaded) Io { .processReplacePath = processReplacePath, .processSpawn = processSpawn, .processSpawnPath = processSpawnPath, - .childWait = childWait, // TODO audit for cancelation and unreachable - .childKill = childKill, // TODO audit for cancelation and unreachable + .childWait = childWait, + .childKill = childKill, .progressParentFile = progressParentFile, @@ -11853,38 +11853,7 @@ fn processSetCurrentDir(userdata: ?*anyopaque, dir: Dir) process.SetCurrentDirEr }; } - if (dir.handle == posix.AT.FDCWD) return; - - const syscall: Syscall = try .start(); - while (true) { - switch (posix.errno(posix.system.fchdir(dir.handle))) { - .SUCCESS => return syscall.finish(), - .INTR => { - try syscall.checkCancel(); - continue; - }, - .ACCES => { - syscall.finish(); - return error.AccessDenied; - }, - .BADF => |err| { - syscall.finish(); - return errnoBug(err); - }, - .NOTDIR => { - syscall.finish(); - return error.NotDir; - }, - .IO => { - syscall.finish(); - return error.FileSystem; - }, - else => |err| { - syscall.finish(); - return posix.unexpectedErrno(err); - }, - } - } + return fchdir(dir.handle); } pub const PosixAddress = extern union { @@ -12960,57 +12929,64 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp }; if (pid_result == 0) { - // We are the child. + defer comptime unreachable; // We are the child. if (Thread.current) |current_thread| current_thread.cancel_protection = .blocked; + const ep1 = err_pipe[1]; - setUpChildIo(options.stdin, stdin_pipe[0], posix.STDIN_FILENO, dev_null_fd) catch |err| forkBail(err_pipe[1], err); - setUpChildIo(options.stdout, stdout_pipe[1], posix.STDOUT_FILENO, dev_null_fd) catch |err| forkBail(err_pipe[1], err); - setUpChildIo(options.stderr, stderr_pipe[1], posix.STDERR_FILENO, dev_null_fd) catch |err| forkBail(err_pipe[1], err); + setUpChildIo(options.stdin, stdin_pipe[0], posix.STDIN_FILENO, dev_null_fd) catch |err| forkBail(ep1, err); + setUpChildIo(options.stdout, stdout_pipe[1], posix.STDOUT_FILENO, dev_null_fd) catch |err| forkBail(ep1, err); + setUpChildIo(options.stderr, stderr_pipe[1], posix.STDERR_FILENO, dev_null_fd) catch |err| forkBail(ep1, err); if (options.cwd_dir) |cwd| { - posix.fchdir(cwd.handle) catch |err| forkBail(err_pipe[1], err); + fchdir(cwd.handle) catch |err| forkBail(ep1, err); } else if (options.cwd) |cwd| { - posix.chdir(cwd) catch |err| forkBail(err_pipe[1], err); + chdir(cwd) catch |err| forkBail(ep1, err); } // Must happen after fchdir above, the cwd file descriptor might be // equal to prog_fileno and be clobbered by this dup2 call. - if (prog_pipe[1] != -1) posix.dup2(prog_pipe[1], prog_fileno) catch |err| forkBail(err_pipe[1], err); + if (prog_pipe[1] != -1) dup2(prog_pipe[1], prog_fileno) catch |err| forkBail(ep1, err); if (options.gid) |gid| { - posix.setregid(gid, gid) catch |err| forkBail(err_pipe[1], err); + switch (posix.errno(posix.system.setregid(gid, gid))) { + .SUCCESS => {}, + .AGAIN => forkBail(ep1, error.ResourceLimitReached), + .INVAL => forkBail(ep1, error.InvalidUserId), + .PERM => forkBail(ep1, error.PermissionDenied), + else => forkBail(ep1, error.Unexpected), + } } if (options.uid) |uid| { switch (posix.errno(posix.system.setreuid(uid, uid))) { .SUCCESS => {}, - .AGAIN => forkBail(err_pipe[1], error.ResourceLimitReached), - .INVAL => forkBail(err_pipe[1], error.InvalidUserId), - .PERM => forkBail(err_pipe[1], error.PermissionDenied), - else => forkBail(err_pipe[1], error.Unexpected), + .AGAIN => forkBail(ep1, error.ResourceLimitReached), + .INVAL => forkBail(ep1, error.InvalidUserId), + .PERM => forkBail(ep1, error.PermissionDenied), + else => forkBail(ep1, error.Unexpected), } } if (options.pgid) |pid| { switch (posix.errno(posix.system.setpgid(0, pid))) { .SUCCESS => {}, - .ACCES => forkBail(err_pipe[1], error.ProcessAlreadyExec), - .INVAL => forkBail(err_pipe[1], error.InvalidProcessGroupId), - .PERM => forkBail(err_pipe[1], error.PermissionDenied), - else => forkBail(err_pipe[1], error.Unexpected), + .ACCES => forkBail(ep1, error.ProcessAlreadyExec), + .INVAL => forkBail(ep1, error.InvalidProcessGroupId), + .PERM => forkBail(ep1, error.PermissionDenied), + else => forkBail(ep1, error.Unexpected), } } if (options.start_suspended) { switch (posix.errno(posix.system.kill(posix.system.getpid(), .STOP))) { .SUCCESS => {}, - .PERM => forkBail(err_pipe[1], error.PermissionDenied), - else => forkBail(err_pipe[1], error.Unexpected), + .PERM => forkBail(ep1, error.PermissionDenied), + else => forkBail(ep1, error.Unexpected), } } const err = posixExecv(options.expand_arg0, argv_buf.ptr[0].?, argv_buf.ptr, envp, PATH); - forkBail(err_pipe[1], err); + forkBail(ep1, err); } const pid: posix.pid_t = @intCast(pid_result); // We are the parent. @@ -13050,9 +13026,10 @@ fn getDevNullFd(t: *Threaded) !posix.fd_t { defer t.mutex.unlock(); if (t.null_file.fd != -1) return t.null_file.fd; } + const mode: u32 = 0; const syscall: Syscall = try .start(); while (true) { - const rc = open_sym("/dev/null", .{ .ACCMODE = .RDWR }, 0); + const rc = open_sym("/dev/null", .{ .ACCMODE = .RDWR }, mode); switch (posix.errno(rc)) { .SUCCESS => { syscall.finish(); @@ -13089,10 +13066,15 @@ fn processSpawnPosix(userdata: ?*anyopaque, options: process.SpawnOptions) proce defer posix.close(spawned.err_fd); // Wait for the child to report any errors in or before `execvpe`. - if (readIntFd(t, spawned.err_fd)) |child_err_int| { + if (readIntFd(spawned.err_fd)) |child_err_int| { const child_err: process.SpawnError = @errorCast(@errorFromInt(child_err_int)); return child_err; } else |read_err| switch (read_err) { + error.Canceled => { + // We don't want to wait for the error to be reported, but we do + // need to return the child so that it can be cleaned up. + recancelInner(); + }, error.EndOfStream => { // Write end closed by CLOEXEC at the time of the `execvpe` call, // indicating success. @@ -13159,7 +13141,7 @@ fn childKillWindows(t: *Threaded, child: *process.Child, exit_code: windows.UINT fn childWaitWindows(child: *process.Child) process.Child.WaitError!process.Child.Term { const handle = child.id.?; - var syscall: Syscall = try .start(); + const syscall: Syscall = try .start(); while (true) switch (windows.kernel32.WaitForSingleObjectEx(handle, windows.INFINITE, windows.FALSE)) { windows.WAIT_OBJECT_0 => break syscall.finish(), windows.WAIT_ABANDONED, windows.WAIT_TIMEOUT => { @@ -13393,21 +13375,25 @@ fn writeIntFd(fd: posix.fd_t, value: ErrInt) !void { } } -fn readIntFd(t: *Threaded, fd: posix.fd_t) !ErrInt { - _ = t; // TODO cancelation +fn readIntFd(fd: posix.fd_t) !ErrInt { var buffer: [8]u8 = undefined; var i: usize = 0; + const syscall: Syscall = try .start(); while (true) { const rc = posix.system.read(fd, buffer[i..].ptr, buffer.len - i); switch (posix.errno(rc)) { .SUCCESS => { + syscall.finish(); const n: usize = @intCast(rc); if (n == 0) break; i += n; continue; }, - .INTR => continue, - else => |err| return posix.unexpectedErrno(err), + .INTR => { + try syscall.checkCancel(); + continue; + }, + else => |err| return syscall.unexpectedErrno(err), } } if (buffer.len - i != 0) return error.EndOfStream; @@ -13423,10 +13409,10 @@ fn destroyPipe(pipe: [2]posix.fd_t) void { fn setUpChildIo(stdio: process.SpawnOptions.StdIo, pipe_fd: i32, std_fileno: i32, dev_null_fd: i32) !void { switch (stdio) { - .pipe => try posix.dup2(pipe_fd, std_fileno), + .pipe => try dup2(pipe_fd, std_fileno), .close => posix.close(std_fileno), .inherit => {}, - .ignore => try posix.dup2(dev_null_fd, std_fileno), + .ignore => try dup2(dev_null_fd, std_fileno), .file => @panic("TODO implement setUpChildIo when file is used"), } } @@ -15289,3 +15275,81 @@ pub fn pipe2(flags: posix.O) PipeError![2]posix.fd_t { return fds; } + +pub const DupError = error{ + ProcessFdQuotaExceeded, + SystemResources, +} || Io.UnexpectedError || Io.Cancelable; + +pub fn dup2(old_fd: posix.fd_t, new_fd: posix.fd_t) DupError!void { + const syscall: Syscall = try .start(); + while (true) switch (posix.errno(posix.system.dup2(old_fd, new_fd))) { + .SUCCESS => return syscall.finish(), + .BUSY, .INTR => { + try syscall.checkCancel(); + continue; + }, + .INVAL => |err| return syscall.errnoBug(err), // invalid parameters + .BADF => |err| return syscall.errnoBug(err), // use after free + .MFILE => return syscall.fail(error.ProcessFdQuotaExceeded), + .NOMEM => return syscall.fail(error.SystemResources), + else => |err| return syscall.unexpectedErrno(err), + }; +} + +pub const FchdirError = error{ + AccessDenied, + NotDir, + FileSystem, +} || Io.Cancelable || Io.UnexpectedError; + +pub fn fchdir(fd: posix.fd_t) FchdirError!void { + if (fd == posix.AT.FDCWD) return; + const syscall: Syscall = try .start(); + while (true) switch (posix.errno(posix.system.fchdir(fd))) { + .SUCCESS => return syscall.finish(), + .INTR => { + try syscall.checkCancel(); + continue; + }, + .ACCES => return syscall.fail(error.AccessDenied), + .NOTDIR => return syscall.fail(error.NotDir), + .IO => return syscall.fail(error.FileSystem), + .BADF => |err| return syscall.errnoBug(err), + else => |err| return syscall.unexpectedErrno(err), + }; +} + +pub const ChdirError = error{ + AccessDenied, + FileSystem, + SymLinkLoop, + NameTooLong, + FileNotFound, + SystemResources, + NotDir, + BadPathName, +} || Io.Cancelable || Io.UnexpectedError; + +pub fn chdir(dir_path: []const u8) ChdirError!void { + var path_buffer: [posix.PATH_MAX]u8 = undefined; + const dir_path_posix = try pathToPosix(dir_path, &path_buffer); + const syscall: Syscall = try .start(); + while (true) switch (posix.errno(posix.system.chdir(dir_path_posix))) { + .SUCCESS => return syscall.finish(), + .INTR => { + try syscall.checkCancel(); + continue; + }, + .ACCES => return syscall.fail(error.AccessDenied), + .IO => return syscall.fail(error.FileSystem), + .LOOP => return syscall.fail(error.SymLinkLoop), + .NAMETOOLONG => return syscall.fail(error.NameTooLong), + .NOENT => return syscall.fail(error.FileNotFound), + .NOMEM => return syscall.fail(error.SystemResources), + .NOTDIR => return syscall.fail(error.NotDir), + .ILSEQ => return syscall.fail(error.BadPathName), + .FAULT => |err| return syscall.errnoBug(err), + else => |err| return syscall.unexpectedErrno(err), + }; +} diff --git a/lib/std/posix.zig b/lib/std/posix.zig index fed55f34cfb748744853c300380223b125286332..736dcf824f50a4b89ab63d15079702725828f828 100644 --- a/lib/std/posix.zig +++ b/lib/std/posix.zig @@ -772,29 +772,6 @@ pub fn openatZ(dir_fd: fd_t, file_path: [*:0]const u8, flags: O, mode: mode_t) O } } -pub fn dup(old_fd: fd_t) !fd_t { - const rc = system.dup(old_fd); - return switch (errno(rc)) { - .SUCCESS => return @intCast(rc), - .MFILE => error.ProcessFdQuotaExceeded, - .BADF => unreachable, // invalid file descriptor - else => |err| return unexpectedErrno(err), - }; -} - -pub fn dup2(old_fd: fd_t, new_fd: fd_t) !void { - while (true) { - switch (errno(system.dup2(old_fd, new_fd))) { - .SUCCESS => return, - .BUSY, .INTR => continue, - .MFILE => return error.ProcessFdQuotaExceeded, - .INVAL => unreachable, // invalid parameters passed to dup2 - .BADF => unreachable, // invalid file descriptor - else => |err| return unexpectedErrno(err), - } - } -} - pub fn getppid() pid_t { return system.getppid(); } @@ -832,85 +809,6 @@ pub fn getcwd(out_buffer: []u8) GetCwdError![]u8 { } } -/// Same as `mkdir` but the parameter is null-terminated. -/// On Windows, `dir_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). -/// On WASI, `dir_path` should be encoded as valid UTF-8. -/// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding. -pub const ChangeCurDirError = error{ - AccessDenied, - FileSystem, - SymLinkLoop, - NameTooLong, - FileNotFound, - SystemResources, - NotDir, - /// WASI: file paths must be valid UTF-8. - /// Windows: file paths provided by the user must be valid WTF-8. - /// https://wtf-8.codeberg.page/ - BadPathName, -} || UnexpectedError; - -/// Changes the current working directory of the calling process. -/// On Windows, `dir_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). -/// On WASI, `dir_path` should be encoded as valid UTF-8. -/// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding. -pub fn chdir(dir_path: []const u8) ChangeCurDirError!void { - if (native_os == .wasi and !builtin.link_libc) { - @compileError("unsupported OS"); - } else if (native_os == .windows) { - @compileError("unsupported OS"); - } else { - const dir_path_c = try toPosixPath(dir_path); - return chdirZ(&dir_path_c); - } -} - -/// Same as `chdir` except the parameter is null-terminated. -/// On Windows, `dir_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). -/// On WASI, `dir_path` should be encoded as valid UTF-8. -/// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding. -pub fn chdirZ(dir_path: [*:0]const u8) ChangeCurDirError!void { - if (native_os == .windows) { - @compileError("unsupported OS"); - } else if (native_os == .wasi and !builtin.link_libc) { - @compileError("unsupported OS"); - } - switch (errno(system.chdir(dir_path))) { - .SUCCESS => return, - .ACCES => return error.AccessDenied, - .FAULT => unreachable, - .IO => return error.FileSystem, - .LOOP => return error.SymLinkLoop, - .NAMETOOLONG => return error.NameTooLong, - .NOENT => return error.FileNotFound, - .NOMEM => return error.SystemResources, - .NOTDIR => return error.NotDir, - .ILSEQ => return error.BadPathName, - else => |err| return unexpectedErrno(err), - } -} - -pub const FchdirError = error{ - AccessDenied, - NotDir, - FileSystem, -} || UnexpectedError; - -pub fn fchdir(dirfd: fd_t) FchdirError!void { - if (dirfd == AT.FDCWD) return; - while (true) { - switch (errno(system.fchdir(dirfd))) { - .SUCCESS => return, - .ACCES => return error.AccessDenied, - .BADF => unreachable, - .NOTDIR => return error.NotDir, - .INTR => continue, - .IO => return error.FileSystem, - else => |err| return unexpectedErrno(err), - } - } -} - pub const SetEidError = error{ InvalidUserId, PermissionDenied, @@ -956,16 +854,6 @@ pub fn setegid(uid: uid_t) SetEidError!void { } } -pub fn setregid(rgid: gid_t, egid: gid_t) SetIdError!void { - switch (errno(system.setregid(rgid, egid))) { - .SUCCESS => return, - .AGAIN => return error.ResourceLimitReached, - .INVAL => return error.InvalidUserId, - .PERM => return error.PermissionDenied, - else => |err| return unexpectedErrno(err), - } -} - pub fn getuid() uid_t { return system.getuid(); } diff --git a/lib/std/posix/test.zig b/lib/std/posix/test.zig index f385a82663ff95be31d1e361eb7580fb857abb46..905b3438e4636898fb9b6e4db795346d27901d04 100644 --- a/lib/std/posix/test.zig +++ b/lib/std/posix/test.zig @@ -433,37 +433,6 @@ test "sigset add/del" { } } -test "dup & dup2" { - switch (native_os) { - .linux, .illumos => {}, - else => return error.SkipZigTest, - } - - const io = testing.io; - - var tmp = tmpDir(.{}); - defer tmp.cleanup(); - - { - var file = try tmp.dir.createFile(io, "os_dup_test", .{}); - defer file.close(io); - - var duped = Io.File{ .handle = try posix.dup(file.handle) }; - defer duped.close(io); - try duped.writeStreamingAll(io, "dup"); - - // Tests aren't run in parallel so using the next fd shouldn't be an issue. - const new_fd = duped.handle + 1; - try posix.dup2(file.handle, new_fd); - var dup2ed = Io.File{ .handle = new_fd }; - defer dup2ed.close(io); - try dup2ed.writeStreamingAll(io, "dup2"); - } - - var buffer: [8]u8 = undefined; - try expectEqualStrings("dupdup2", try tmp.dir.readFile(io, "os_dup_test", &buffer)); -} - test "getpid" { if (native_os == .wasi) return error.SkipZigTest; if (native_os == .windows) return error.SkipZigTest; diff --git a/test/standalone/posix/cwd.zig b/test/standalone/posix/cwd.zig index fd5ceae1ec6472fe8aaa41a67c5fb22fae297025..d43713247595bf1a2206a6a84e5f37b52bfc6af3 100644 --- a/test/standalone/posix/cwd.zig +++ b/test/standalone/posix/cwd.zig @@ -31,7 +31,7 @@ fn test_chdir_self() !void { const old_cwd = try std.posix.getcwd(old_cwd_buf[0..]); // Try changing to the current directory - try std.posix.chdir(old_cwd); + try std.Io.Threaded.chdir(old_cwd); try expect_cwd(old_cwd); } @@ -42,7 +42,7 @@ fn test_chdir_absolute() !void { const parent = std.fs.path.dirname(old_cwd) orelse unreachable; // old_cwd should be absolute // Try changing to the parent via a full path - try std.posix.chdir(parent); + try std.Io.Threaded.chdir(parent); try expect_cwd(parent); } @@ -63,7 +63,7 @@ fn test_chdir_relative(gpa: Allocator, io: Io) !void { defer gpa.free(expected_path); // change current working directory to new test directory - try std.posix.chdir(relative_dir_name); + try std.Io.Threaded.chdir(relative_dir_name); var new_cwd_buf: [path_max]u8 = undefined; const new_cwd = try std.posix.getcwd(new_cwd_buf[0..]); -- 2.54.0 From 9eb3b54eb5442b13d268a7965998c6ffc5004d6a Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 3 Jan 2026 20:23:55 -0800 Subject: [PATCH 58/60] std.Io.Threaded: revert making reading fork child status cancelable this caused the build runner to crash sometimes, not sure why yet --- lib/std/Io/Threaded.zig | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 6efd50aa0a1b8f0feaa042ae91a552b7ecc1f34f..08217be4becaccfd84d4e195b230a45fd5b979cf 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -13070,11 +13070,6 @@ fn processSpawnPosix(userdata: ?*anyopaque, options: process.SpawnOptions) proce const child_err: process.SpawnError = @errorCast(@errorFromInt(child_err_int)); return child_err; } else |read_err| switch (read_err) { - error.Canceled => { - // We don't want to wait for the error to be reported, but we do - // need to return the child so that it can be cleaned up. - recancelInner(); - }, error.EndOfStream => { // Write end closed by CLOEXEC at the time of the `execvpe` call, // indicating success. @@ -13378,22 +13373,17 @@ fn writeIntFd(fd: posix.fd_t, value: ErrInt) !void { fn readIntFd(fd: posix.fd_t) !ErrInt { var buffer: [8]u8 = undefined; var i: usize = 0; - const syscall: Syscall = try .start(); while (true) { const rc = posix.system.read(fd, buffer[i..].ptr, buffer.len - i); switch (posix.errno(rc)) { .SUCCESS => { - syscall.finish(); const n: usize = @intCast(rc); if (n == 0) break; i += n; continue; }, - .INTR => { - try syscall.checkCancel(); - continue; - }, - else => |err| return syscall.unexpectedErrno(err), + .INTR => continue, + else => |err| return posix.unexpectedErrno(err), } } if (buffer.len - i != 0) return error.EndOfStream; -- 2.54.0 From 420a9aed4c27af2501b6e85973b7762a7e38b9e4 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 3 Jan 2026 21:18:33 -0800 Subject: [PATCH 59/60] build: bump freebsd max_rss solves error: memory usage peaked at 1.10GB (1102852096 bytes), exceeding the declared upper bound of 1.06GB (1060217241 bytes) --- build.zig | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/build.zig b/build.zig index ed2c0f7581f32e0584fc5984e3b9781e1bcb7f98..a52dee1de981e8d374af0aa5e93fd072727f33b0 100644 --- a/build.zig +++ b/build.zig @@ -470,10 +470,7 @@ pub fn build(b: *std.Build) !void { .skip_llvm = skip_llvm, .skip_libc = skip_libc, .max_rss = switch (b.graph.host.result.os.tag) { - .freebsd => switch (b.graph.host.result.cpu.arch) { - .x86_64 => 1_060_217_241, - else => 1_100_000_000, - }, + .freebsd => 2_000_000_000, .linux => switch (b.graph.host.result.cpu.arch) { .aarch64 => 659_809_075, .loongarch64 => 598_902_374, -- 2.54.0 From ef1ddbe2f03a165c86af4e2dd36178b1d8661ebe Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 4 Jan 2026 00:26:27 -0800 Subject: [PATCH 60/60] std.heap.DebugAllocator: disable already flaky test tracked by #22731 --- lib/std/heap/debug_allocator.zig | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/std/heap/debug_allocator.zig b/lib/std/heap/debug_allocator.zig index 24f1554544c635dac055b465e38f873530afe430..0b57d934fc770fed35869169f6280fcb4d17ec74 100644 --- a/lib/std/heap/debug_allocator.zig +++ b/lib/std/heap/debug_allocator.zig @@ -1272,9 +1272,12 @@ test "shrink large object to large object" { } test "shrink large object to large object with larger alignment" { - if (!builtin.link_libc and builtin.os.tag == .wasi) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/22731 + if (builtin.os.tag == .wasi) { + // https://github.com/ziglang/zig/issues/22731 + return error.SkipZigTest; + } - var gpa = DebugAllocator(test_config){}; + var gpa: DebugAllocator(test_config) = .{}; defer std.testing.expect(gpa.deinit() == .ok) catch @panic("leak"); const allocator = gpa.allocator(); -- 2.54.0