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/build.zig b/build.zig index 338709c7c4193ad3c3c4874104fdcf15205eda45..a52dee1de981e8d374af0aa5e93fd072727f33b0 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"); @@ -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, @@ -829,30 +826,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) { - .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, - .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; 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/lib/compiler/aro/aro/Compilation.zig b/lib/compiler/aro/aro/Compilation.zig index aec780af02abd7f2c9611e7f08b0db14f8f36756..6f3a1a44da119e3e1b27750a551efb852df21d33 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, + environ_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, environ_map), .cwd = cwd, }; errdefer comp.deinit(); diff --git a/lib/compiler/aro/aro/Driver.zig b/lib/compiler/aro/aro/Driver.zig index 340a35bdde97b4995461bc139866a146246663db..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/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/build_runner.zig b/lib/compiler/build_runner.zig index f5315d6496213795d6570d3654b9d4db8cc7361e..82fc696618893810090a42a104de7a3e35611560 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,9 +37,12 @@ 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, .{}); + var threaded: std.Io.Threaded = .init(gpa, .{ + .environ = init.environ, + .argv0 = .init(init.args), + }); defer threaded.deinit(); const io = threaded.io(); @@ -81,9 +84,10 @@ pub fn main() !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 process.getEnvMap(arena), + .environ_map = try init.environ.createMap(arena), .global_cache_root = global_cache_directory, .zig_lib_directory = zig_lib_directory, .host = .{ @@ -126,13 +130,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.environ_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.environ_map)) |str| { if (std.meta.stringToEnum(MultilineErrors, str)) |style| { multiline_errors = style; } @@ -429,8 +433,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.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), @@ -540,7 +544,7 @@ pub fn main() !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/compiler/libc.zig b/lib/compiler/libc.zig index eb4614c95db3552f2e36342c4ce1ea7161347f31..8ca53fefc905c01527c7157bee4c0d10575e582e 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 environ_map = init.environ_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, + environ_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, + .environ_map = environ_map, }) catch |err| { fatal("unable to detect native libc: {t}", .{err}); }; 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/lib/compiler/reduce.zig b/lib/compiler/reduce.zig index 922031d00873a8a18cbe2d766c1b5f3b35baa98e..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.Child.run(arena, io, .{ .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/compile.zig b/lib/compiler/resinator/compile.zig index 4f75fca18a4287ae96003db378f68f8c83f40c7d..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) !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,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 = 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 e286a5f4b9d8047d5a77935567b24d71b9aee69c..d1aaa24264d2d57ed9072390ac44cc0477e6d756 100644 --- a/lib/compiler/resinator/main.zig +++ b/lib/compiler/resinator/main.zig @@ -19,12 +19,18 @@ 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(); - var threaded: std.Io.Threaded = .init(gpa, .{}); + var environ_map = try init.environ.createMap(gpa); + defer environ_map.deinit(); + + var threaded: std.Io.Threaded = .init(gpa, .{ + .environ = init.environ, + .argv0 = .init(init.args), + }); defer threaded.deinit(); const io = threaded.io(); @@ -32,7 +38,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); @@ -145,8 +151,8 @@ pub fn main() !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, &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, @@ -280,7 +286,7 @@ pub fn main() !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, &environ_map), .default_language_id = options.default_language_id, .default_code_page = default_code_page, .disjoint_code_page = has_disjoint_code_page, @@ -289,7 +295,7 @@ pub fn main() !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) { + }, &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 @@ -536,13 +542,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, + environ_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, + environ_map, + ) catch |err| switch (err) { error.OutOfMemory => |e| return e, else => |e| { switch (e) { @@ -569,6 +586,7 @@ fn getIncludePaths( auto_includes_option: cli.Options.AutoIncludes, zig_lib_dir: []const u8, target_machine_type: std.coff.IMAGE.FILE.MACHINE, + environ_map: *const std.process.Environ.Map, ) ![]const []const u8 { if (auto_includes_option == .none) return &[_][]const u8{}; @@ -615,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; @@ -641,7 +659,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, + 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 a67721a7e1c62863ef36f0b725333386d4789847..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) !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 = std.process.getEnvVarOwned(arena, "INCLUDE") catch ""; + 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/std-docs.zig b/lib/compiler/std-docs.zig index e2b58b86683f2018cf7f6d3638a231690f515da6..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/test_runner.zig b/lib/compiler/test_runner.zig index cd3c27412b055fb53bcabe3942f4fdff7d0065e7..5885f5cf12655871f0baf154514160d09ec48879 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()) { @@ -38,11 +38,10 @@ pub fn main() void { } if (need_simple) { - return mainSimple() catch @panic("test failure\n"); + return mainSimple() catch @panic("test failure"); } - 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; @@ -66,13 +65,13 @@ pub fn main() 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); @@ -132,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(); @@ -217,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"); @@ -236,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/compiler/translate-c/main.zig b/lib/compiler/translate-c/main.zig index ee50df422adbafc194b85930cb21d2b77485a7b6..ce8f4f55c3b6d4060df197f382e8cfa746fbd3d2 100644 --- a/lib/compiler/translate-c/main.zig +++ b/lib/compiler/translate-c/main.zig @@ -9,21 +9,13 @@ const Translator = @import("Translator.zig"); const fast_exit = @import("builtin").mode != .Debug; -var general_purpose_allocator: std.heap.GeneralPurposeAllocator(.{}) = .init; +pub fn main(init: std.process.Init) u8 { + const gpa = init.gpa; + const arena = init.arena.allocator(); + const io = init.io; + const environ_map = init.environ_map; -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(); - - 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; @@ -34,8 +26,8 @@ pub fn main() 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(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); @@ -50,7 +42,7 @@ pub fn main() 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(), 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); @@ -123,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/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 }); } 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.zig b/lib/std/Build.zig index dc3489bba3104a312568a95eb15bac7d3eec3dd3..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.EnvMap; 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, @@ -190,7 +189,7 @@ pub const RunError = error{ ExitCodeFailure, ProcessTerminated, ExecNotSupported, -} || std.process.Child.SpawnError; +} || std.process.SpawnError; pub const PkgConfigError = error{ PkgConfigCrashed, @@ -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, @@ -1738,8 +1737,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 { @@ -1755,7 +1753,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; @@ -1773,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| { @@ -1804,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; @@ -1830,24 +1828,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(argv, b.allocator); - 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, &graph.environ_map, argv); - try Step.handleVerbose2(b, null, child.env_map, argv); - try child.spawn(io); + var child = try std.process.spawn(io, .{ + .argv = argv, + .environ_map = &graph.environ_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 { @@ -1857,14 +1857,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; }, @@ -1875,21 +1879,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, 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, - }); - 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/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.zig b/lib/std/Build/Step.zig index 243dee8604da4eca431012923409fdc1b48e2303..28141cebe536778ee7503521952404e7d25a0e58 100644 --- a/lib/std/Build/Step.zig +++ b/lib/std/Build/Step.zig @@ -348,19 +348,21 @@ pub fn captureChildProcess( gpa: Allocator, progress_node: std.Progress.Node, argv: []const []const u8, -) !std.process.Child.RunResult { - const arena = s.owner.allocator; - const io = s.owner.graph.io; +) !std.process.RunResult { + 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, .{ + const result = std.process.run(arena, io, .{ .argv = argv, + .environ_map = &graph.environ_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); @@ -442,35 +444,34 @@ 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(argv, arena); - child.env_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 }); - const zp = try gpa.create(ZigProcess); + defer if (!watch) gpa.destroy(zp); + + zp.child = std.process.spawn(io, .{ + .argv = argv, + .environ_map = &b.graph.environ_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) zp.child.kill(io); + 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) 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) { - zp.poller.deinit(); - gpa.destroy(zp); - }; + defer if (!watch) zp.poller.deinit(); const result = try zigProcessUpdate(s, zp, watch, web_server, gpa); @@ -486,7 +487,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) { @@ -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.environ_map, + } else null, argv); std.debug.print("{s}\n", .{text}); } } @@ -714,12 +719,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", .{}); }, } @@ -728,15 +736,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 +782,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/Compile.zig b/lib/std/Build/Step/Compile.zig index 0454e5b79dbc2582cac7188b9a2593decb37dfa2..2403436c73818693a142a42b82bf67acaa5c69a3 100644 --- a/lib/std/Build/Step/Compile.zig +++ b/lib/std/Build/Step/Compile.zig @@ -741,13 +741,13 @@ 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, "--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, @@ -1846,8 +1846,8 @@ 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 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(); 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/Options.zig b/lib/std/Build/Step/Options.zig index 610d417aeabf53cc1881e0d57563224f6b29cda2..9bb91606208fed0af89168ad1d452cb9c84ffe71 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,9 +547,10 @@ test Options { .io = io, .gpa = arena.allocator(), .manifest_dir = Io.Dir.cwd(), + .cwd = cwd, }, .zig_exe = "test", - .env_map = std.process.EnvMap.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 258fadd06ccc46c6bcadcf86fec737fe57da93b9..1084c7fbfd89478853a7b4c607df1b538c18ae7c 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; @@ -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, @@ -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, }; }; @@ -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"); } } @@ -581,43 +581,51 @@ pub fn getEnvMap(run: *Run) *EnvMap { } fn getEnvMapInternal(run: *Run) *EnvMap { - const arena = run.step.owner.allocator; - 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 graph = run.step.owner.graph; + const arena = graph.arena; + return run.environ_map orelse { + const cloned_map = arena.create(EnvMap) catch @panic("OOM"); + 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 b = run.step.owner; - const env_map = run.getEnvMap(); - env_map.put(b.dupe(key), b.dupe(value)) catch @panic("unhandled error"); + const environ_map = run.getEnvMap(); + // This data structure already dupes keys and values. + environ_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. 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 { - const new_check: StdIo.Check = .{ .expect_term = .{ .Exited = code } }; + const new_check: StdIo.Check = .{ .expect_term = .{ .exited = code } }; run.addCheck(new_check); } @@ -749,28 +757,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.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 // 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 { @@ -791,32 +801,10 @@ fn make(step: *Step, options: Step.MakeOptions) !void { var man = b.graph.cache.obtain(); 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]); + if (run.environ_map) |environ_map| { + for (environ_map.keys(), environ_map.values()) |key, value| { + man.hash.addBytes(key); + man.hash.addBytes(value); } } @@ -1181,40 +1169,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, }; } @@ -1241,7 +1229,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, @@ -1251,13 +1239,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: { @@ -1293,8 +1281,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); @@ -1391,9 +1379,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}", .{ @@ -1525,8 +1513,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| { @@ -1540,7 +1528,7 @@ fn runCommand( } const EvalGenericResult = struct { - term: std.process.Child.Term, + term: process.Child.Term, stdout: ?[]const u8, stderr: ?[]const u8, }; @@ -1548,13 +1536,12 @@ 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, ) !?EvalGenericResult { const b = run.step.owner; - const arena = b.allocator; const graph = b.graph; const io = graph.io; @@ -1563,75 +1550,76 @@ fn spawnChildAndCollect( assert(run.stdio == .zig_test); } - var child = std.process.Child.init(argv, arena); - 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) { - .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, argv); + run.step.result_failed_command = try Step.allocPrintCmd(options.gpa, child_cwd, .{ + .child = environ_map, + .parent = &graph.environ_map, + }, argv); + + var spawn_options: process.SpawnOptions = .{ + .argv = argv, + .cwd = child_cwd, + .environ_map = environ_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); 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, &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 }; } } -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.remove("NO_COLOR"); + try environ_map.put("CLICOLOR_FORCE", "1"); + _ = environ_map.swapRemove("NO_COLOR"); }, .disable => { - try env_map.put("NO_COLOR", "1"); - env_map.remove("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, @@ -1655,7 +1643,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 { @@ -1679,14 +1667,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, @@ -1694,11 +1682,9 @@ fn evalZigTest( ); }; - try child.waitForSpawn(); - switch (try pollZigTest( run, - child, + &child, options, fuzz_context, &poller, @@ -1760,7 +1746,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)}); @@ -1815,7 +1801,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), @@ -2173,15 +2159,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| { @@ -2331,10 +2315,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/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 { 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 a2b35e35229aa716799bfe46a1c53dc2b452f2c7..cb201e991d78a6256426535d0d86cb3517b998bd 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())); } @@ -528,13 +522,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,11 +566,14 @@ fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.Optim "--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, + .environ_map = &graph.environ_map, + .stdin = .pipe, + .stdout = .pipe, + .stderr = .pipe, + }); + defer child.kill(io); var poller = Io.poll(gpa, enum { stdout, stderr }, .{ .stdout = child.stdout.?, @@ -636,19 +633,26 @@ 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}", - .{ code, try Build.Step.allocPrintCmd(arena, null, argv.items) }, + .{ code, try Build.Step.allocPrintCmd(arena, null, null, argv.items) }, ); 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, argv.items)}, + .{try Build.Step.allocPrintCmd(arena, null, null, argv.items)}, ); return error.WasmCompilationFailed; }, @@ -658,14 +662,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..a1e38135bd4b2b92d842f78025ce888ba8f97484 100644 --- a/lib/std/Io.zig +++ b/lib/std/Io.zig @@ -717,6 +717,14 @@ 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, + + progressParentFile: *const fn (?*anyopaque) std.Progress.ParentFileError!File, now: *const fn (?*anyopaque, Clock) Clock.Error!Timestamp, sleep: *const fn (?*anyopaque, Timeout) SleepError!void, 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..08217be4becaccfd84d4e195b230a45fd5b979cf 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; @@ -63,24 +64,41 @@ stderr_writer_initialized: bool = false, argv0: Argv0, environ: Environ, +null_file: NullFile = .{}, + 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: 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, /// Protected by `mutex`. Tracks the problem, if any, that occurred when /// trying to scan environment variables. /// @@ -89,21 +107,50 @@ 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, }; pub const String = switch (native_os) { - .openbsd, .haiku => 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, }, - else => 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, _, @@ -1105,6 +1152,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); @@ -1137,7 +1185,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: @@ -1167,8 +1216,8 @@ pub fn init( .old_sig_pipe = undefined, .have_signal_handler = false, .argv0 = options.argv0, - .environ = options.environ, .worker_threads = .init(null), + .environ = .{ .process_environ = options.environ }, }; if (posix.Sigaction != void) { @@ -1235,6 +1284,7 @@ pub fn deinit(t: *Threaded) void { 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; } @@ -1402,6 +1452,14 @@ pub fn io(t: *Threaded) Io { .tryLockStderr = tryLockStderr, .unlockStderr = unlockStderr, .processSetCurrentDir = processSetCurrentDir, + .processReplace = processReplace, + .processReplacePath = processReplacePath, + .processSpawn = processSpawn, + .processSpawnPath = processSpawnPath, + .childWait = childWait, + .childKill = childKill, + + .progressParentFile = progressParentFile, .now = now, .sleep = sleep, @@ -1540,6 +1598,14 @@ pub fn ioBasic(t: *Threaded) Io { .tryLockStderr = tryLockStderr, .unlockStderr = unlockStderr, .processSetCurrentDir = processSetCurrentDir, + .processReplace = processReplace, + .processReplacePath = processReplacePath, + .processSpawn = processSpawn, + .processSpawnPath = processSpawnPath, + .childWait = childWait, + .childKill = childKill, + + .progressParentFile = progressParentFile, .now = now, .sleep = sleep, @@ -1603,6 +1669,18 @@ 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) { + .linux => @hasField(std.os.linux.SYS, "wait4"), + .dragonfly, .freebsd, .netbsd, .openbsd, .illumos, .serenity, .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => true, + 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; @@ -2249,7 +2327,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) { @@ -2309,9 +2387,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, }; @@ -2349,7 +2427,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, @@ -2988,7 +3066,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, @@ -3537,7 +3615,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); } @@ -3620,7 +3698,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; @@ -3642,7 +3723,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; @@ -3935,7 +4019,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, @@ -5083,7 +5167,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, @@ -5360,7 +5444,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; @@ -5389,7 +5473,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; @@ -5614,7 +5698,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 @@ -5624,13 +5708,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(); }; @@ -5638,8 +5722,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, @@ -7892,7 +7976,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, @@ -7933,7 +8017,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) { @@ -11693,14 +11777,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); } @@ -11731,10 +11815,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; @@ -11769,38 +11853,7 @@ fn processSetCurrentDir(userdata: ?*anyopaque, dir: Dir) std.process.SetCurrentD }; } - 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 { @@ -12577,6 +12630,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(); @@ -12585,6 +12677,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; @@ -12652,9 +12747,9 @@ 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) { + } else { + 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) {} const key = line[0..line_i]; @@ -12667,29 +12762,1989 @@ 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; + } + } + } +} + +fn processReplace(userdata: ?*anyopaque, options: process.ReplaceOptions) process.ReplaceError { + 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; + @panic("TODO processReplacePath"); +} + +fn processSpawnPath(userdata: ?*anyopaque, dir: Dir, options: process.SpawnOptions) process.SpawnError!process.Child { + if (!process.can_spawn) return error.OperationUnsupported; + _ = userdata; + _ = dir; + _ = options; + @panic("TODO processSpawnPath"); +} + +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; +} + +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 + // 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 pipe2(pipe_flags) else undefined; + errdefer if (options.stdin == .pipe) { + destroyPipe(stdin_pipe); + }; + + 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 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) 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 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.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; + }; + + // 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 pipe2(.{ .CLOEXEC = true }); + errdefer destroyPipe(err_pipe); + + t.scanEnviron(); // for PATH + const PATH = t.environ.string.PATH orelse default_PATH; + + 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) { + 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(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| { + fchdir(cwd.handle) catch |err| forkBail(ep1, err); + } else if (options.cwd) |cwd| { + 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) dup2(prog_pipe[1], prog_fileno) catch |err| forkBail(ep1, err); + + if (options.gid) |gid| { + 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(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(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(ep1, error.PermissionDenied), + else => forkBail(ep1, error.Unexpected), + } + } + + const err = posixExecv(options.expand_arg0, argv_buf.ptr[0].?, argv_buf.ptr, envp, PATH); + forkBail(ep1, err); + } + + 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 + + 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]); + + 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 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 mode: u32 = 0; + const syscall: Syscall = try .start(); + while (true) { + const rc = open_sym("/dev/null", .{ .ACCMODE = .RDWR }, mode); + 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); + defer posix.close(spawned.err_fd); + + // Wait for the child to report any errors in or before `execvpe`. + 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.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 = spawned.pid, + .thread_handle = {}, + .stdin = spawned.stdin, + .stdout = spawned.stdout, + .stderr = spawned.stderr, + .request_resource_usage_statistics = options.request_resource_usage_statistics, + }; +} + +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(child), + else => return childWaitPosix(child), + } +} + +fn childKill(userdata: ?*anyopaque, child: *process.Child) void { + if (native_os == .wasi) unreachable; + const t: *Threaded = @ptrCast(@alignCast(userdata)); + if (is_windows) { + childKillWindows(t, child, 1) catch childCleanupWindows(child); } else { - for (t.environ.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]; + childKillPosix(child) catch {}; + childCleanupPosix(child); + } +} - var end_i: usize = line_i; - while (line[end_i] != 0) : (end_i += 1) {} - const value = line[line_i + 1 .. end_i :0]; +fn childKillWindows(t: *Threaded, child: *process.Child, exit_code: windows.UINT) !void { + _ = 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); +} - 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; +fn childWaitWindows(child: *process.Child) process.Child.WaitError!process.Child.Term { + const handle = child.id.?; + + 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 => { + try syscall.checkCancel(); + continue; + }, + windows.WAIT_FAILED => { + syscall.finish(); + switch (windows.GetLastError()) { + else => |err| return windows.unexpectedError(err), } + }, + else => return syscall.fail(error.Unexpected), + }; + + const term: process.Child.Term = x: { + var exit_code: windows.DWORD = undefined; + if (windows.kernel32.GetExitCodeProcess(handle, &exit_code) == 0) { + break :x .{ .unknown = 0 }; + } else { + break :x .{ .exited = @as(u8, @truncate(exit_code)) }; } + }; + + 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(child: *process.Child) process.Child.WaitError!process.Child.Term { + defer childCleanupPosix(child); + + const pid = child.id.?; + + 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; + const syscall: Syscall = try .start(); + while (true) switch (posix.errno(posix.system.wait4(pid, &status, 0, ru_ptr))) { + .SUCCESS => { + syscall.finish(); + if (ru_ptr) |p| child.resource_usage_statistics.rusage = p.*; + return statusToTerm(@bitCast(status)); + }, + .INTR => { + try syscall.checkCancel(); + continue; + }, + .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; + const syscall: Syscall = try .start(); + while (true) switch (linux.errno(linux.waitid(.PID, pid, &info, linux.W.EXITED, ru_ptr))) { + .SUCCESS => { + 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); + return switch (code) { + .EXITED => .{ .exited = @truncate(status) }, + .KILLED, .DUMPED => .{ .signal = @enumFromInt(status) }, + .TRAPPED, .STOPPED => .{ .stopped = status }, + _, .CONTINUED => .{ .unknown = status }, + }; + }, + .INTR => { + try syscall.checkCancel(); + continue; + }, + .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 => { + syscall.finish(); + return statusToTerm(@bitCast(status)); + }, + .INTR => { + try syscall.checkCancel(); + continue; + }, + .CHILD => |err| return syscall.errnoBug(err), // Double-free. + else => |err| return syscall.unexpectedErrno(err), + }; +} + +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(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, + .INVAL => |err| return errnoBug(err), + .SRCH => |err| return errnoBug(err), + else => |err| return posix.unexpectedErrno(err), + }; + + 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 { + 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; } + child.id = 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` 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); + } +} + +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(fd: posix.fd_t) !ErrInt { + 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) break; + i += n; + continue; + }, + .INTR => continue, + else => |err| return posix.unexpectedErrno(err), + } + } + if (buffer.len - i != 0) return error.EndOfStream; + 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 dup2(pipe_fd, std_fileno), + .close => posix.close(std_fileno), + .inherit => {}, + .ignore => try dup2(dev_null_fd, std_fileno), + .file => @panic("TODO implement setUpChildIo when file is used"), + } +} + +fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) process.SpawnError!process.Child { + const t: *Threaded = @ptrCast(@alignCast(userdata)); + + var saAttr: windows.SECURITY_ATTRIBUTES = .{ + .nLength = @sizeOf(windows.SECURITY_ATTRIBUTES), + .bInheritHandle = windows.TRUE, + .lpSecurityDescriptor = null, + }; + + const any_ignore = + options.stdin == .ignore or + options.stdout == .ignore or + options.stderr == .ignore; + + 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; + switch (options.stdin) { + .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; + }, + .file => @panic("TODO implement passing file stdio in processSpawnWindows"), + } + 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 (options.stdout) { + .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; + }, + .file => @panic("TODO implement passing file stdio in processSpawnWindows"), + } + 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 (options.stderr) { + .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; + }, + .file => @panic("TODO implement passing file stdio in processSpawnWindows"), + } + errdefer if (options.stderr == .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; + + 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 (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]; + const app_name_is_absolute = Dir.path.isAbsolute(app_name_wtf8); + + // 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(arena, dir); + } else if (options.cwd) |cwd| { + cwd_path_w_needs_free = true; + break :x try std.unicode.wtf8ToWtf16LeAllocZ(arena, cwd); + } else { + break :x &[_:0]u16{}; // empty for cwd + } + }; + + // 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(arena, app_dirname_wtf8); + } + break :x null; + }; + const app_name_w = try std.unicode.wtf8ToWtf16LeAllocZ(arena, app_basename_wtf8); + + const flags: windows.CreateProcessFlags = .{ + .create_suspended = options.start_suspended, + .create_unicode_environment = true, + .create_no_window = options.create_no_window, + }; + + run: { + // 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 + // 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(arena, options.argv); + + var app_buf: std.ArrayList(u16) = .empty; + try app_buf.appendSlice(arena, app_name_w); + + var dir_buf: std.ArrayList(u16) = .empty; + + if (cwd_path_w.len > 0) { + try dir_buf.appendSlice(arena, cwd_path_w); + } + if (app_dirname_w) |app_dir| { + if (dir_buf.items.len > 0) try dir_buf.append(arena, Dir.path.sep); + try dir_buf.appendSlice(arena, app_dir); + } + + windowsCreateProcessPathExt( + 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, + 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 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) { + return original_err; + } + + var it = std.mem.tokenizeScalar(u16, PATH, ';'); + while (it.next()) |search_path| { + dir_buf.clearRetainingCapacity(); + try dir_buf.appendSlice(arena, search_path); + + if (windowsCreateProcessPathExt( + 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. + 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 (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.?); + + 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, + }; +} + +fn getNulHandle(t: *Threaded) !windows.HANDLE { + { + t.mutex.lock(); + defer t.mutex.unlock(); + if (t.null_file.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.null_file.handle) |prev_handle| { + windows.CloseHandle(fresh_handle); + return prev_handle; + } else { + t.null_file.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. +/// Note: If the dir is the cwd, dir_buf should be empty (len = 0). +fn windowsCreateProcessPathExt( + arena: Allocator, + dir_buf: *std.ArrayList(u16), + app_buf: *std.ArrayList(u16), + pathext: [:0]const u16, + cmd_line_cache: *WindowsCommandLineCache, + envp_ptr: ?[*:0]const 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. + var dir = dir: { + // needs to be null-terminated + 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); + break :dir dirOpenDirWindows(.cwd(), prefixed_path.span(), .{ + .iterate = true, + }) 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); + + // Add wildcard and null-terminator + 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 + // 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(arena, Dir.path.sep), + }; + 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: { + 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(arena, Dir.path.sep), + }; + 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) { + .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, + env_ptr: ?[*:0]const u16, + cwd_ptr: ?[*:0]u16, + flags: windows.CreateProcessFlags, + lpStartupInfo: *windows.STARTUPINFOW, + lpProcessInformation: *windows.PROCESS_INFORMATION, +) !void { + 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 syscall.fail(error.InvalidExe), + + else => |err| { + syscall.finish(); + return windows.unexpectedError(err); + }, + } + } +} + +/// 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); +} + +fn posixExecv( + 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 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 posixExecvPath. + 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 = posixExecvPath(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. +pub fn posixExecvPath( + path: [*:0]const u8, + 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))) { + .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), + }, + } +} + +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); + +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, + } }; +} + +pub fn environString(t: *Threaded, comptime name: []const u8) ?[:0]const u8 { + t.scanEnviron(); + return @field(t.environ.string, name); } test { @@ -13151,3 +15206,140 @@ 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; +} + +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/Io/Threaded/test.zig b/lib/std/Io/Threaded/test.zig index 74bba51b90a01a2ab8e55d0bda2775dfec1cf9ef..34aa4a8c689d67ceef1d1ca8e985b5487da9e2f1 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(); @@ -155,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(); @@ -169,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/Progress.zig b/lib/std/Progress.zig index f0baca2784f0ae912488d1ed4a0a1dad28bf1406..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.parseEnvVarInt("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/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/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/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/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..007e815e374d61d860544e531d0a9e663157447d 100644 --- a/lib/std/debug/ElfFile.zig +++ b/lib/std/debug/ElfFile.zig @@ -67,15 +67,16 @@ pub const DebugInfoSearchPaths = struct { }; pub fn native(exe_path: []const u8) DebugInfoSearchPaths { - return .{ + 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 (std.posix.getenv("DEBUGINFOD_CACHE_PATH")) |p| { + if (t.environString("DEBUGINFOD_CACHE_PATH")) |p| { break :p .{ p, "" }; } - if (std.posix.getenv("XDG_CACHE_HOME")) |cache_path| { + if (t.environString("XDG_CACHE_HOME")) |cache_path| { break :p .{ cache_path, "/debuginfod_client" }; } - if (std.posix.getenv("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 be76f3a8c24fa92e256588359350d65767b031aa..ffcb4dfd26fdb9b1a80d030221d83b87283ef499 100644 --- a/lib/std/debug/SelfInfo/Elf.zig +++ b/lib/std/debug/SelfInfo/Elf.zig @@ -322,6 +322,7 @@ 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; 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/fs/path.zig b/lib/std/fs/path.zig index a8465f52d4148d10aea014b10a330b3d0578f371..cd189cc12246df7437c9a8d21d386afadebc980f 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 `environ_map` is used. +pub fn relative( + gpa: Allocator, + cwd: []const u8, + environ_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, environ_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 `environ_map` parameter. `null` is +/// treated equivalent to the environment variable missing. +pub fn relativeWindows( + gpa: Allocator, + cwd: []const u8, + environ_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, environ_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, environ_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, environ_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, + environ_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 (environ_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/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/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/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(); diff --git a/lib/std/http/Client.zig b/lib/std/http/Client.zig index 7f8c9827ef03ab3ba70477a0ec0d226c260df9f1..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) !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,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, environ_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, environ_map, &.{ "https_proxy", "HTTPS_PROXY", "all_proxy", "ALL_PROXY", }); } } -fn createProxyFromEnvVar(arena: Allocator, env_var_names: []const []const u8) !?*Proxy { +fn createProxyFromEnvVar( + arena: Allocator, + environ_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 = environ_map.get(name) orelse continue; if (content.len == 0) continue; - break content; } else return null; 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/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..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 { @@ -3616,14 +3623,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; @@ -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/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/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/lib/std/os/windows.zig b/lib/std/os/windows.zig index b47533a3ec9b24738d421146e79ecb238a91dfd7..c041293e2f8f7ad1d3233aa71f7b52067223e63d 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 @@ -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, @@ -4235,7 +4224,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 +4240,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 +4342,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 +4480,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 +4489,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 @@ -4660,10 +4528,10 @@ 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) { - 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/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/posix.zig b/lib/std/posix.zig index 8bcf7e82ee13d140d322d4667b8d743a40d39524..736dcf824f50a4b89ab63d15079702725828f828 100644 --- a/lib/std/posix.zig +++ b/lib/std/posix.zig @@ -772,214 +772,10 @@ 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 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), - }, - } -} - -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, -) 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)); -} - pub const GetCwdError = error{ NameTooLong, CurrentWorkingDirectoryUnlinked, @@ -1013,198 +809,6 @@ 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, - 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, @@ -1231,16 +835,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, @@ -1260,34 +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 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(); } @@ -1899,53 +1465,12 @@ 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. 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); @@ -2468,80 +1993,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 6fdb980b1bb93ab2db7d1a404cb996c7cf3c24ac..905b3438e4636898fb9b6e4db795346d27901d04 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); @@ -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); @@ -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; @@ -438,42 +433,11 @@ 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; - try expect(posix.getpid() != 0); + try expect(posix.system.getpid() != 0); } test "getppid" { @@ -532,7 +496,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" }); diff --git a/lib/std/process.zig b/lib/std/process.zig index 865376d907a71a9ff0ce2e1cb0ab1d91c4ce8121..d8db82cc0582821c4c2b9a6243ca32149905491a 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,40 @@ 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 if possible. + /// Threadsafe. + gpa: Allocator, + /// An appropriate default Io implementation based on the target + /// configuration. Debug mode will set up leak checking if possible. + io: Io, + /// Environment variables, initialized with `gpa`. Not threadsafe. + environ_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 +105,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,71 +260,278 @@ pub fn getBaseAddress() usize { } } -/// Tells whether calling the `execv` or `execve` functions will be a compile error. -pub const can_execv = switch (native_os) { +/// Tells whether the target operating system supports replacing the current +/// 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, }; -/// Tells whether spawning child processes is supported (e.g. via Child) +/// Tells whether spawning child processes is supported. 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 = 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, +} || Allocator.Error || Io.Dir.PathNameError || Io.Cancelable || Io.UnexpectedError; + +pub const ReplaceOptions = struct { + argv: []const []const u8, + 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, +}; + +/// 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. +/// +/// 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. -/// 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, +/// 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 '/'. +/// +/// 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); +} + +pub const ArgExpansion = enum { expand, no_expand }; + +/// File name extensions supported natively by `CreateProcess()` on Windows. +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, + /// 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 SpawnOptions = struct { 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); - 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"); - } + + /// 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. + 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 + /// 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, }; +}; - return posix.execvpeZ_expandArg0(.no_expand, argv_buf.ptr[0].?, argv_buf.ptr, envp); +/// 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 { + 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. + 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 + /// 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 { + 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, .{ + .argv = options.argv, + .cwd = options.cwd, + .cwd_dir = options.cwd_dir, + .environ_map = options.environ_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; + 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{ @@ -1903,215 +664,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..8e6caf2d651d407a2baaab7b276f0f187381f068 --- /dev/null +++ b/lib/std/process/Args.zig @@ -0,0 +1,976 @@ +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.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, +}; + +/// 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(gpa) }; + } + if (native_os == .windows) { + return .{ .inner = try .init(gpa, a.vector) }; + } + + 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(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 + // 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 gpa.alloc(u8, wtf8_len + 1); + errdefer gpa.free(buffer); + + return .{ + .allocator = gpa, + .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); +} + +pub const ToSliceError = Iterator.Windows.InitError || Iterator.Wasi.InitError; + +/// 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. +/// +/// 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; + } +} + +test "Iterator.Windows" { + 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\")" + , &.{ + \\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 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); + + // 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 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 IteratorGeneral(.{}).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 `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 IteratorGeneral(comptime options: IteratorGeneralOptions) 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 IteratorGeneral(.{ .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..17e15f208da5fbb7155ada08fe2a9006127be258 100644 --- a/lib/std/process/Child.zig +++ b/lib/std/process/Child.zig @@ -5,116 +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 EnvMap = std.process.EnvMap; -const maxInt = std.math.maxInt; const assert = std.debug.assert; const Allocator = std.mem.Allocator; const ArrayList = std.ArrayList; 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, - -/// 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). -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, @@ -164,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(argv: []const []const u8, allocator: Allocator) Child { - return .{ - .allocator = allocator, - .argv = argv, - .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, - .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.?, @@ -427,1473 +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(allocator: Allocator, io: Io, args: struct { - argv: []const []const u8, - 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); - 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); - var stderr: ArrayList(u8) = .empty; - defer stderr.deinit(allocator); - - try child.spawn(io); - errdefer { - _ = child.kill(io) catch {}; - } - try child.collectOutput(allocator, &stdout, &stderr, args.max_output_bytes); - - return .{ - .stdout = try stdout.toOwnedSlice(allocator), - .stderr = try stderr.toOwnedSlice(allocator), - .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; - if (self.env_map) |env_map| { - break :m (try process.createEnvironFromMap(arena, env_map, .{ - .zig_progress_fd = prog_fd, - })).ptr; - } else if (builtin.link_libc) { - break :m (try process.createEnvironFromExisting(arena, std.c.environ, .{ - .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), .{ - .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"); - } - }; - - // 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 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), - }; - 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/process/Environ.zig b/lib/std/process/Environ.zig new file mode 100644 index 0000000000000000000000000000000000000000..5255e1af16ab86f4e1e29158cbfda3c8c8830c66 --- /dev/null +++ b/lib/std/process/Environ.zig @@ -0,0 +1,822 @@ +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.testing; +const unicode = std.unicode; +const posix = std.posix; +const mem = std.mem; + +/// Unmodified, unprocessed data provided by the operating system. +block: Block, + +pub const empty: Environ = .{ + .block = switch (Block) { + void => {}, + else => &.{}, + }, +}; + +/// 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 => void, + .wasi => switch (builtin.link_libc) { + false => void, + true => [:null]const ?[*:0]const u8, + }, + .freestanding, .other => void, + else => [:null]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 @truncate(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: *const Map) [][]const u8 { + return m.array_hash_map.keys(); + } + + pub fn values(m: *const 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.array_hash_map.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 createBlockPosix( + map: *const Map, + arena: Allocator, + options: CreateBlockPosixOptions, + ) 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; + } + + /// 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 + 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; + } + 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 + /// 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 == .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 { + 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]; + + 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; + } +} + +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 + /// 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, +}; + +/// On Windows, if `key` is not valid [WTF-8](https://wtf-8.codeberg.page/), +/// then `error.InvalidWtf8` is returned. +/// +/// 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); +} + +/// 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; +} + +/// 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 getWindows(environ, key_w) != null; + } else { + return getPosix(environ, key) != null; + } +} + +/// 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 = getWindows(environ, key_w) orelse return false; + return value.len != 0; + } else { + const value = getPosix(environ, key) orelse return false; + return value.len != 0; + } +} + +/// This function is unavailable on WASI without libc due to the memory +/// allocation requirement. +/// +/// 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; + + return mem.sliceTo(line + line_i + 1, 0); + } + return null; +} + +/// 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. +/// +/// See also: +/// * `createMap` +/// * `containsConstant` +/// * `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 (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 = 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; +} + +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, +}; + +/// 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 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. + zig_progress_fd: ?i32 = null, +}; + +/// Creates a null-delimited environment variable block in the format expected +/// by POSIX, from a different one. +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; + + 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.block.len; + 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.createBlockPosix(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 + } + } +} + +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 = 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); + + 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()); + + 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{ + 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).?); + } +} + +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); + 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 = .{ .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.expectEqualDeep(map.keys(), map2.keys()); + try testing.expectEqualDeep(map.values(), map2.values()); +} + +test createMapWide { + if (builtin.cpu.arch.endian() == .big) return error.SkipZigTest; // TODO + + 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()); +} diff --git a/lib/std/start.zig b/lib/std/start.zig index c6a9f06724e965482cc67a8021aebff67f59d265..8d2e2a9df2df2ff95459d6a9d5cfd695d24aa1d7 100644 --- a/lib/std/start.zig +++ b/lib/std/start.zig @@ -1,128 +1,76 @@ // 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"; -// 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 (is_wasm) { + @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(&startWasi, .{ .name = wasm_start_sym }); } + } 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 }); + } 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 +90,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 { +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) { - .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,7 +472,10 @@ fn WinStartup() callconv(.withStackAlign(.c, 1)) noreturn { std.debug.maybeEnableSegfaultHandler(); - std.os.windows.ntdll.RtlExitUserProcess(callMain()); + 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_w, {})); } fn wWinMainCRTStartup() callconv(.withStackAlign(.c, 1)) noreturn { @@ -556,7 +507,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)); @@ -631,6 +582,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 => { @@ -665,92 +617,137 @@ 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; - +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(); + 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 { 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); - 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]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 = !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. +}; +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 (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 + comptime unreachable; + + defer if (use_debug_allocator) { + _ = debug_allocator.deinit(); // Leaks do not affect return code. + }; + + 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, .{ + .argv0 = .init(.{ .vector = args }), + .environ = .{ .block = environ }, + }); + defer threaded.deinit(); + + var environ_map = std.process.Environ.createMap(.{ .block = environ }, gpa) catch |err| + std.process.fatal("failed to parse environment variables: {t}", .{err}); + defer environ_map.deinit(); + + return wrapMain(root.main(.{ + .minimal = .{ + .args = .{ .vector = args }, + .environ = .{ .block = environ }, + }, + .arena = &arena_allocator, + .gpa = gpa, + .io = threaded.io(), + .environ_map = &environ_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..c5d71dcb602a06e4f4aeef18dc604e01eb056ba5 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, @@ -176,10 +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, + /// 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, + } = 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 @@ -193,6 +201,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..4f0f47b11f3dfe5b39bcbbef50b562f7a3f87189 100644 --- a/lib/std/zig.zig +++ b/lib/std/zig.zig @@ -739,28 +739,39 @@ 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, + + // C toolchain integration + NIX_CFLAGS_COMPILE, + NIX_CFLAGS_LINK, + NIX_LDFLAGS, + C_INCLUDE_PATH, + CPLUS_INCLUDE_PATH, + LIBRARY_PATH, CC, + + // Terminal integration NO_COLOR, CLICOLOR_FORCE, + + // Debug info integration XDG_CACHE_HOME, LOCALAPPDATA, HOME, - pub fn isSet(comptime ev: EnvVar) bool { - return std.process.hasNonEmptyEnvVarConstant(@tagName(ev)); - } + // Windows SDK integration + PROGRAMDATA, + + // Homebrew integration + HOMEBREW_PREFIX, - 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 isSet(ev: EnvVar, map: *const std.process.Environ.Map) bool { + return map.contains(@tagName(ev)); } - 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/LibCDirs.zig b/lib/std/zig/LibCDirs.zig index e05ccde5895c7eae0426a64ff1b5c176490065df..ca43fe127f8f51c7446c93793fd6b94119d1cf3e 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, + environ_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, + .environ_map = environ_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, + .environ_map = environ_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 f2489f9ee741ac30a063ca81170e2a4976d314d9..02b3df54dce74ee89913d717747f3740d08c8d37 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,6 +168,7 @@ pub fn render(self: LibCInstallation, out: *std.Io.Writer) !void { pub const FindNativeOptions = struct { target: *const std.Target, + environ_map: *const Environ.Map, /// If enabled, will print human-friendly errors to stderr. verbose: bool = false, @@ -191,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.environ_map) catch |err| switch (err) { error.NotFound => return error.WindowsSdkNotFound, error.PathTooLong => return error.WindowsSdkNotFound, error.OutOfMemory => return error.OutOfMemory, @@ -206,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 => {}, } @@ -238,20 +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 = std.process.getEnvMap(gpa) catch |err| switch (err) { - error.Unexpected => unreachable, // WASI-only - else => |e| return e, - }; - 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; }; @@ -260,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); + try appendCcExe(&argv, skip_cc_env_var, &environ_map); try argv.appendSlice(&.{ "-E", "-Wp,-v", @@ -268,10 +267,10 @@ 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, + .argv = argv.items, + .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! @@ -289,7 +288,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; }, @@ -336,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, @@ -345,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, @@ -447,6 +446,7 @@ fn findNativeCrtDirWindows( fn findNativeCrtDirPosix(self: *LibCInstallation, gpa: Allocator, io: Io, args: FindNativeOptions) FindError!void { self.crt_dir = try ccPrintFileName(gpa, io, .{ + .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,28 +551,26 @@ fn findNativeMsvcLibDir( } pub const CCPrintFileNameOptions = struct { + environ_map: *const Environ.Map, search_basename: []const u8, want_dirname: enum { full_path, only_dir }, verbose: bool = false, }; /// 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 = std.process.getEnvMap(gpa) catch |err| switch (err) { - error.Unexpected => unreachable, // WASI-only - else => |e| return e, - }; - 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; }; @@ -582,13 +580,13 @@ 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, &environ_map); 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, + .argv = argv.items, + .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! @@ -603,7 +601,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; }, @@ -619,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); }, } } @@ -668,14 +666,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, + environ_map: *const 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(environ_map) orelse { args.appendAssumeCapacity(default_cc_exe); return; }; diff --git a/lib/std/zig/WindowsSdk.zig b/lib/std/zig/WindowsSdk.zig index 1b172e4358eb195549526a727b7d9046189c2d55..d6e7e35130c5da5f50405f06d1afd73005fcf358 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, + environ_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, environ_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, + 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. 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(environ_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, + 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); + 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; @@ -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, + 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(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(environ_map) orelse return error.PathNotFound; const visualstudio_folder_path = try Dir.path.join(gpa, &.{ local_app_data_path, "Microsoft\\VisualStudio\\", }); @@ -951,16 +964,15 @@ 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, + environ_map: *const 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 (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); @@ -1030,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, + environ_map: *const Environ.Map, + ) error{ OutOfMemory, MsvcLibDirNotFound }![]const u8 { + 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) 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) 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 bf66f912ae360e1798a8981a21c4a68ccc2181f0..f8003cd62061b4cfeb936533448e9a4b9f20dbb4 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, + environ_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(environ_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(environ_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(environ_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; } @@ -124,7 +121,7 @@ pub fn detect(arena: Allocator, io: Io, native_target: *const std.Target) !Nativ } // Check for homebrew paths - if (std.posix.getenv("HOMEBREW_PREFIX")) |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" })); } @@ -180,23 +177,21 @@ pub fn detect(arena: Allocator, io: Io, native_target: *const std.Target) !Nativ // 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.posix.getenv("C_INCLUDE_PATH")) |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.posix.getenv("CPLUS_INCLUDE_PATH")) |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.posix.getenv("LIBRARY_PATH")) |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/lib/std/zig/system/darwin.zig b/lib/std/zig/system/darwin.zig index b493ccf0ec378bd24fd33fbea534b00f07c53fb0..b31dc484ceb3ce3a87a049610114a547ef5cdf2f 100644 --- a/lib/std/zig/system/darwin.zig +++ b/lib/std/zig/system/darwin.zig @@ -17,7 +17,7 @@ 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, .{ .argv = &.{ "xcode-select", "--print-path" }, }) catch return false; defer { @@ -25,7 +25,7 @@ pub fn isSdkInstalled(gpa: Allocator, io: Io) bool { 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, }; } @@ -47,13 +47,13 @@ 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.run(gpa, io, .{ .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..82f45ee9f0189bdc1d26b292624e4c2ab42726ea 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -54,6 +54,7 @@ gpa: Allocator, /// threads at once. arena: Allocator, io: Io, +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, @@ -761,11 +762,12 @@ pub const Directories = struct { .wasi => void, else => []const u8, }, + environ_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: { @@ -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, environ_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: *const 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 @@ -2123,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() }); @@ -2306,6 +2312,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 +5510,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 +5713,7 @@ pub fn translateC( translated_basename: []const u8, owner_mod: *Package.Module, prog_node: std.Progress.Node, + environ_map: *const std.process.Environ.Map, ) !CImportResult { dev.check(.translate_c_command); @@ -5774,7 +5783,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, 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}); @@ -5861,7 +5870,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 +5879,7 @@ pub fn cImport( translated_basename, owner_mod, prog_node, + comp.environ_map, ); }; @@ -6249,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); @@ -6281,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, @@ -6337,17 +6348,24 @@ 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| { + .exited => |code| { if (code != 0) { std.process.exit(code); } @@ -6357,21 +6375,21 @@ 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; - - 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))); - 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| { + .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}); @@ -6381,6 +6399,10 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr 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", .{}); @@ -6741,15 +6763,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,16 +6804,20 @@ 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) { - .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", .{}); @@ -7959,6 +7986,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 }); @@ -8096,6 +8124,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/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/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 9481201c3bfdab5691ff5805733ca71a7b342f89..fa04e7de58a1fda94f9311fc1bb21a7266558e3f 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; } @@ -101,31 +102,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, 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(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(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.getPosix()) |cache_root| { + if (std.zig.EnvVar.XDG_CACHE_HOME.get(environ_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(environ_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; @@ -144,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); @@ -164,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; } @@ -184,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/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/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/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/link/Lld.zig b/src/link/Lld.zig index b2a0f6e396399307fddbd2e04ea7255461163f56..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,13 +1677,13 @@ 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; }; 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..8bc3b583910ca58db23dbfdae9fb9f8a46e2632e 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,16 +50,17 @@ 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; 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; @@ -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,34 +182,48 @@ 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]; + if (args.len <= 1) { + std.log.info("{s}", .{usage}); + fatal("expected command argument", .{}); + } + + var environ_map = init.environ.createMap(arena) catch |err| fatal("failed to parse environment: {t}", .{err}); + + Compilation.setMainThread(); + + var threaded: Io.Threaded = .init(gpa, .{ + .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, args); + 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, args); + return mainArgs(gpa, arena, io, args, &environ_map); } -fn mainArgs(gpa: Allocator, arena: Allocator, args: []const [:0]const u8) !void { - const tr = tracy.trace(@src()); - defer tr.end(); - - Compilation.setMainThread(); - - if (args.len <= 1) { - std.log.info("{s}", .{usage}); - fatal("expected command argument", .{}); - } - - if (process.can_execv and std.posix.getenvZ("ZIG_IS_DETECTING_LIBC_PATHS") != null) { +fn mainArgs( + gpa: Allocator, + arena: Allocator, + io: Io, + args: []const [:0]const u8, + environ_map: *process.Environ.Map, +) !void { + 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 @@ -217,58 +231,51 @@ 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: EnvVar = .ZIG_IS_TRYING_TO_NOT_CALL_ITSELF; + 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 " ++ + "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 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.execve(arena, args[1..], &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.execve(arena, modified_args, &env_map); + return process.replace(io, .{ .argv = modified_args, .environ_map = environ_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 }, environ_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 }, environ_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 }, environ_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, environ_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, environ_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, environ_map); } else if (mem.eql(u8, cmd, "dlltool") or mem.eql(u8, cmd, "ranlib") or mem.eql(u8, cmd, "lib") or @@ -278,7 +285,7 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const [:0]const u8) !void 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, environ_map); } else if (mem.eql(u8, cmd, "clang") or mem.eql(u8, cmd, "-cc1") or mem.eql(u8, cmd, "-cc1as")) { @@ -292,16 +299,16 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const [:0]const u8) !void 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, environ_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, environ_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, 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, .{ + return jitCmd(gpa, arena, io, cmd_args, environ_map, .{ .cmd_name = "resinator", .root_src_path = "resinator/main.zig", .depend_on_aro = true, @@ -312,20 +319,20 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const [:0]const u8) !void 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, environ_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, environ_map); } else if (mem.eql(u8, cmd, "libc")) { - return jitCmd(gpa, arena, io, cmd_args, .{ + 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, .{ + return jitCmd(gpa, arena, io, cmd_args, environ_map, .{ .cmd_name = "std", .root_src_path = "std-docs.zig", .prepend_zig_lib_dir_path = true, @@ -355,10 +362,11 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const [:0]const u8) !void args, if (native_os == .wasi) wasi_preopens, &host, + environ_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, environ_map, .{ .cmd_name = "reduce", .root_src_path = "reduce.zig", }); @@ -803,6 +811,7 @@ fn buildOutputType( io: Io, all_args: []const []const u8, arg_mode: ArgMode, + environ_map: *process.Environ.Map, ) !void { var provided_name: ?[]const u8 = null; var root_src_file: ?[]const u8 = null; @@ -815,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(); + EnvVar.ZIG_VERBOSE_LINK.isSet(environ_map); var verbose_cc = (native_os != .wasi or builtin.link_libc) and - EnvVar.ZIG_VERBOSE_CC.isSet(); + EnvVar.ZIG_VERBOSE_CC.isSet(environ_map); var verbose_air = false; var verbose_intern_pool = false; var verbose_generic_instances = false; @@ -889,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 = 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(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; @@ -988,7 +997,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(environ_map), .native_system_include_paths = &.{}, }; defer create_module.link_inputs.deinit(gpa); @@ -997,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()) + var color: Color = if (native_os == .wasi or EnvVar.NO_COLOR.isSet(environ_map)) .off - else if (EnvVar.CLICOLOR_FORCE.isSet()) + else if (EnvVar.CLICOLOR_FORCE.isSet(environ_map)) .on else .auto; @@ -3097,6 +3106,7 @@ fn buildOutputType( }, if (native_os == .wasi) wasi_preopens, self_exe_path, + environ_map, ); defer dirs.deinit(io); @@ -3108,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); + 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}); @@ -3585,6 +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 = environ_map, }) catch |err| switch (err) { error.CreateFail => switch (create_diag) { .cross_libc_unavailable => { @@ -3648,6 +3659,7 @@ fn buildOutputType( arg_mode, all_args, runtime_args_start, + environ_map, ); return cleanExit(io); }, @@ -3674,6 +3686,7 @@ fn buildOutputType( arg_mode, all_args, runtime_args_start, + environ_map, ); return cleanExit(io); }, @@ -3686,7 +3699,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, environ_map); } updateModule(comp, color, root_prog_node) catch |err| switch (err) { @@ -3754,6 +3767,7 @@ fn buildOutputType( all_args, runtime_args_start, create_module.resolved_options.link_libc, + environ_map, ); } @@ -3809,6 +3823,7 @@ fn createModule( index: usize, parent: ?*Package.Module, color: std.zig.Color, + environ_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 +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) 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}); @@ -4015,6 +4030,7 @@ fn createModule( create_module.libc_installation = LibCInstallation.findNative(arena, io, .{ .verbose = true, .target = target, + .environ_map = environ_map, }) catch |err| { fatal("unable to find native libc installation: {t}", .{err}); }; @@ -4119,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); + 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); } @@ -4128,9 +4144,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 +4157,7 @@ fn serve( arg_mode: ArgMode, all_args: []const []const u8, runtime_args_start: ?usize, + environ_map: *process.Environ.Map, ) !void { const gpa = comp.gpa; const io = comp.io; @@ -4190,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); + try cmdTranslateC(comp, arena, &output, file_system_inputs, main_progress_node, environ_map); defer output.deinit(gpa); if (file_system_inputs.items.len != 0) { @@ -4390,6 +4405,7 @@ fn runOrTest( all_args: []const []const u8, runtime_args_start: ?usize, link_libc: bool, + environ_map: *process.Environ.Map, ) !void { const raw_emit_bin = comp.emit_bin orelse return; const exe_path = switch (comp.cache_use) { @@ -4426,77 +4442,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); + 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_execv and arg_mode == .run) { - // execv releases the locks; no need to destroy the Compilation here. + 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.execve(gpa, argv.items, &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); 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, + .environ_map = environ_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,43 +4588,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.spawn(io, .{ + .argv = argv.items, + .stdin = .inherit, + .stdout = .inherit, + .stderr = .inherit, + }); + return child.id.?; } const UpdateModuleError = Compilation.UpdateError || error{ @@ -4627,6 +4626,7 @@ fn cmdTranslateC( fancy_output: ?*Compilation.CImportResult, file_system_inputs: ?*std.ArrayList(u8), prog_node: std.Progress.Node, + environ_map: *process.Environ.Map, ) !void { dev.check(.translate_c_command); @@ -4660,6 +4660,7 @@ fn cmdTranslateC( translated_basename, comp.root_mod, prog_node, + environ_map, ); if (result.errors.errorMessageCount() != 0) { @@ -4707,10 +4708,11 @@ pub fn translateC( arena: Allocator, io: Io, argv: []const []const u8, + environ_map: *const process.Environ.Map, prog_node: std.Progress.Node, capture: ?*[]u8, ) !void { - try jitCmd(gpa, arena, io, argv, .{ + try jitCmd(gpa, arena, io, argv, environ_map, .{ .cmd_name = "translate-c", .root_src_path = "translate-c/main.zig", .depend_on_aro = true, @@ -4867,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) !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 = 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(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(); + EnvVar.ZIG_VERBOSE_LINK.isSet(environ_map); var verbose_cc = (native_os != .wasi or builtin.link_libc) and - EnvVar.ZIG_VERBOSE_CC.isSet(); + EnvVar.ZIG_VERBOSE_CC.isSet(environ_map); var verbose_air = false; var verbose_intern_pool = false; var verbose_generic_instances = false; @@ -5078,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(); + EnvVar.ZIG_BTRFS_WORKAROUND.isSet(environ_map); const root_prog_node = std.Progress.start(io, .{ .disable_printing = (color == .off), .root_name = "Compile Build Script", @@ -5138,6 +5140,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) } }, {}, self_exe_path, + environ_map, ); defer dirs.deinit(io); @@ -5240,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); + try http_client.initDefaultProxies(arena, environ_map); } try job_queue.all_fetches.ensureUnusedCapacity(gpa, 1); @@ -5394,6 +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 = 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)}), @@ -5415,81 +5419,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}); + }, } } } @@ -5512,6 +5516,7 @@ fn jitCmd( arena: Allocator, io: Io, args: []const []const u8, + environ_map: *const process.Environ.Map, options: JitCmdOptions, ) !void { dev.check(.jit_command); @@ -5533,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()) + 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 = 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(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( @@ -5550,6 +5555,7 @@ fn jitCmd( .global, if (native_os == .wasi) wasi_preopens, self_exe_path, + environ_map, ); defer dirs.deinit(io); @@ -5623,6 +5629,7 @@ fn jitCmd( .self_exe_path = self_exe_path, .thread_limit = thread_limit, .cache_mode = .whole, + .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)}), @@ -5668,32 +5675,34 @@ fn jitCmd( child_argv.appendSliceAssumeCapacity(args); - if (process.can_execv and options.capture == null) { - if (EnvVar.ZIG_DEBUG_CMD.isSet()) { + if (process.can_replace and options.capture == null) { + 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.execv(gpa, child_argv.items); + 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 }); } 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, &.{}); @@ -5701,9 +5710,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); @@ -5711,6 +5719,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}); @@ -5826,7 +5838,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 { @@ -6902,14 +6914,15 @@ fn cmdFetch( arena: Allocator, io: Io, args: []const []const u8, + 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(); + EnvVar.ZIG_BTRFS_WORKAROUND.isSet(environ_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(environ_map); var debug_hash: bool = false; var save: union(enum) { no, @@ -6955,7 +6968,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, environ_map); var root_prog_node = std.Progress.start(io, .{ .root_name = "Fetch", @@ -6963,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); + 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 3540a58d188605726c41338ccffeb4a6bc382ca1..ac0578852e5f9d43c925ee4894904cd78062e826 100644 --- a/src/print_env.zig +++ b/src/print_env.zig @@ -19,9 +19,10 @@ pub fn cmdEnv( else => void, }, host: *const std.Target, + environ_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(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], @@ -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, + environ_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(environ_map), .{}); } try env.end(); try root.end(); 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/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; 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/child_process/main.zig b/test/standalone/child_process/main.zig index 98d38bdee3677184a554fcbe01961b0e930e9865..2776c3e95c88d5158b9db122d6b9669871ba23e9 100644 --- a/test/standalone/child_process/main.zig +++ b/test/standalone/child_process/main.zig @@ -1,15 +1,21 @@ 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); + const process_cwd_path = try std.process.getCwdAlloc(gpa); + defer gpa.free(process_cwd_path); + + var environ_map = try init.environ.createMap(gpa); + defer environ_map.deinit(); + + 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: { @@ -17,19 +23,24 @@ pub fn main() !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, &environ_map, cwd_path, child_path), true }; }; 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(); - 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(io, .{ + .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); @@ -44,7 +55,7 @@ pub fn main() !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 }); @@ -57,7 +68,7 @@ pub fn main() !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/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 ba2de2777fe2c05fb3d6c61e366a127543337915..5900e76b574b1dd5d1628bc38e51133423b785cd 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.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 49eefa3b48e9b79b5794c17a1334602465a07090..e0e408a3a491446cc17ed72c64d958bd88b62ec0 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.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 134d53d2fc75ae6919ac091125e7c9c92968a133..6b0dad536e4661244c70a5ecfb5c41035aec8f17 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.minimal.args.iterateAllocator(init.gpa); defer args.deinit(); _ = args.next() orelse unreachable; // skip binary name 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/empty_env/main.zig b/test/standalone/empty_env/main.zig index 1dc435d9fa90d3e9b046d79224617d887a2ac31d..170a46de9e28856fc4d257df5f721bef8db9da1c 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.expectEqual(0, init.environ_map.count()); } 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 b85105642e1da6a5530246195658f04189e027a2..09167f285fe9960fbedb8f9fcb179bc354ca15f4 100644 --- a/test/standalone/env_vars/main.zig +++ b/test/standalone/env_vars/main.zig @@ -2,174 +2,150 @@ 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(); + const allocator = init.gpa; + const arena = init.arena.allocator(); + const environ = init.minimal.environ; - var arena_state = std.heap.ArenaAllocator.init(allocator); - defer arena_state.deinit(); - const arena = arena_state.allocator(); - - // 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")); - if (builtin.os.tag == .windows) { - try std.testing.expectEqualSlices(u8, "123", try std.process.getEnvVarOwned(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, "КИРиллИЦА")); - if (builtin.os.tag == .windows) { - try std.testing.expectEqualSlices(u8, "non-ascii አማርኛ \u{10FFFF}", try std.process.getEnvVarOwned(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, "123", try environ.getAlloc(arena, "FOO")); + 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, "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, "123", try environ.getAlloc(arena, "foo")); } - } - - // parseEnvVarInt - { - 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)); + try std.testing.expectEqualSlices(u8, "ABC=123", try environ.getAlloc(arena, "EQUALS")); + 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.expectEqual(123, try std.process.parseEnvVarInt("foo", u32, 10)); + try std.testing.expectEqualSlices(u8, "non-ascii አማርኛ \u{10FFFF}", try environ.getAlloc(arena, "кирИЛЛица")); } - 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)); + try std.testing.expectEqualSlices(u8, "", try environ.getAlloc(arena, "NO_VALUE")); + try std.testing.expectError(error.EnvironmentVariableMissing, environ.getAlloc(arena, "NOT_SET")); 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)); + 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")); } } - // EnvMap + // Environ.Map { - var env_map = try std.process.getEnvMap(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/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/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..d43713247595bf1a2206a6a84e5f37b52bfc6af3 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 @@ -39,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); } @@ -50,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); } @@ -71,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..]); diff --git a/test/standalone/posix/getenv.zig b/test/standalone/posix/getenv.zig index 8b28d09ae76d0d91cf60a41a6f0a4d6a94083378..62c348e085b93dfee378b84953096879cbc575cc 100644 --- a/test/standalone/posix/getenv.zig +++ b/test/standalone/posix/getenv.zig @@ -1,32 +1,28 @@ -// test getting environment variables +//! test getting environment variables const std = @import("std"); const builtin = @import("builtin"); -pub fn main() !void { - if (builtin.target.os.tag == .windows) { - return; // Windows env strings are WTF-16, so not supported by Zig's std.posix.getenv() - } +pub fn main(init: std.process.Init.Minimal) !void { + if (builtin.target.os.tag == .windows) return; + if (builtin.target.os.tag == .wasi and !builtin.link_libc) return; - 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 - } + 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/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]; diff --git a/test/standalone/run_output_caching/main.zig b/test/standalone/run_output_caching/main.zig index 9786101d325ebf6098dc4416e47257279febdf05..8a9855f5ecef4709e02b20a6b302370562895224 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.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 7efa8e051a29d0f0e380fc092827859090fa4b5b..8490c1c5e9e9241a88035f2c2d8502bf7a370a3c 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.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 d725207320811408d453104f022b4c6409b67681..32610ebcde6d07a4d1ef14b1dca4fab3dd3bd90b 100644 --- a/test/standalone/self_exe_symlink/create-symlink.zig +++ b/test/standalone/self_exe_symlink/create-symlink.zig @@ -1,21 +1,19 @@ 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.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 = 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(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, 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/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 0135ac4b50943e5fc4c2fe136ea611fc20bc70cf..bc894d3881fd59658dc17ca65afc3dd934226560 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.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/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/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, }; 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 28749259f7d832ed5de43ed2ed552adb7ac1a8ad..b82ec8b458e7e589b9d5c5d09d3c84458fe14713 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.args.iterateAllocator(gpa); defer it.deinit(); _ = it.next() orelse unreachable; // skip binary name const child_exe_path_orig = it.next() orelse unreachable; @@ -84,13 +79,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; @@ -98,8 +93,8 @@ 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, .{ - .env_map = env, + const result = try std.process.run(gpa, io, .{ + .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 e0d1abe80640bcab15592c416ca973efc5584231..fde627a5fec983005acace78a0c92773ded362bc 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.args.iterateAllocator(gpa); defer it.deinit(); _ = it.next() orelse unreachable; // skip binary name const child_exe_path_orig = it.next() orelse unreachable; @@ -109,7 +105,7 @@ pub fn main() anyerror!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"); @@ -130,13 +126,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; @@ -144,8 +140,8 @@ 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, .{ - .env_map = env, + const result = try std.process.run(gpa, io, .{ + .environ_map = env, .argv = argv, }); defer gpa.free(result.stdout); 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 ed4069dc61e6a5121936b34e9628fd21831da3bc..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); @@ -25,10 +21,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,12 +92,12 @@ fn checkRelative( expected_stdout: []const u8, argv: []const []const u8, cwd: ?[]const u8, - env_map: ?*const std.process.EnvMap, + environ_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, + .environ_map = environ_map, }); defer allocator.free(result.stdout); defer allocator.free(result.stderr); diff --git a/test/standalone/windows_spawn/main.zig b/test/standalone/windows_spawn/main.zig index c9522bf4de301022d4b8e39d0f5e71fb97040ba2..4a64ca48d6260475b927cea0f0f1cf3252f0dbcb 100644 --- a/test/standalone/windows_spawn/main.zig +++ b/test/standalone/windows_spawn/main.zig @@ -5,16 +5,12 @@ 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; + const process_cwd_path = try std.process.getCwdAlloc(init.arena.allocator()); - 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.args.iterateAllocator(gpa); defer it.deinit(); _ = it.next() orelse unreachable; // skip binary name const hello_exe_cache_path = it.next() orelse unreachable; @@ -28,7 +24,7 @@ pub fn main() anyerror!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 @@ -212,7 +208,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/test/tests.zig b/test/tests.zig index aa3c018a627ce2558374a0e787372b4d2cf0b194..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 }); @@ -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); } 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 3a67210a592b0daaf471f1853897d3635602b61e..7f4b8fbda803714765b1b616b1e60677ea0911d5 100644 --- a/tools/doctest.zig +++ b/tools/doctest.zig @@ -29,21 +29,17 @@ 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 environ_map = init.environ_map; + const cwd_path = try std.process.getCwdAlloc(arena); - const arena = arena_instance.allocator(); + try environ_map.put("CLICOLOR_FORCE", "1"); - 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; @@ -105,12 +101,13 @@ pub fn main() !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, 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, tmp_dir_path, zig_lib_dir) + try Dir.path.relative(arena, cwd_path, environ_map, tmp_dir_path, zig_lib_dir) else null, + environ_map, ); try out_file_writer.end(); @@ -129,10 +126,8 @@ fn printOutput( input_path: []const u8, /// Relative to `tmp_dir_path`. opt_zig_lib_dir: ?[]const u8, + environ_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; @@ -201,14 +196,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, + .environ_map = environ_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); @@ -226,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) { @@ -257,26 +252,26 @@ 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, + .environ_map = environ_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; } 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", .{}); }; @@ -345,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); @@ -376,14 +371,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, + .environ_map = environ_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); @@ -432,14 +427,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, + .environ_map = environ_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); @@ -508,14 +503,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, + .environ_map = environ_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); @@ -536,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"); }, @@ -595,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 }); @@ -1128,24 +1123,29 @@ fn in(slice: []const u8, number: u8) bool { fn run( allocator: Allocator, io: Io, - env_map: *process.EnvMap, + environ_map: *const 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, + .environ_map = environ_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); 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); diff --git a/tools/dump-cov.zig b/tools/dump-cov.zig index 1a8ebb324e57becbb8a60a42333086e2ce1d529c..4a54d9f5199299a75dd8fcae46a7d3506ee53d50 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.allocator(); + const io = init.io; + 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 c55a569e9fdbac6ceb1ee05f42ce7e84a6f7ab32..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, @@ -66,14 +62,12 @@ 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(); +pub fn main(init: std.process.Init) !void { + const io = init.io; + const arena = init.arena.allocator(); + const args = try init.minimal.args.toSlice(arena); - const args = try std.process.argsAlloc(allocator); - - 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..] }; @@ -85,23 +79,19 @@ pub fn main() anyerror!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}", .{ @@ -117,7 +107,7 @@ pub fn main() anyerror!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); } } @@ -164,7 +154,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 95880fe3424b1be2949ba05de16a83918c8b2ee1..3e83761923e55de8fc9f54eb019d7b7f91007d5c 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.allocator(); + const io = init.io; + const args = try init.minimal.args.toSlice(arena); - var threaded: Io.Threaded = .init(gpa, .{}); - defer threaded.deinit(); - const io = threaded.io(); - - const args = try std.process.argsAlloc(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..bd36484e1e9b697deba71a6d32169c0bfde880e2 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.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_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..6041a550da6f3ac04cf366cf9d1913c3cbbed5da 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.minimal.args.toSlice(init.arena.allocator()); + 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..70d410f806b35999af5950f67cf8faecfb951fa7 100644 --- a/tools/generate_linux_syscalls.zig +++ b/tools/generate_linux_syscalls.zig @@ -170,16 +170,11 @@ 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(); +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(); - - 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; @@ -217,8 +212,7 @@ pub fn main() !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} \\ \\ diff --git a/tools/incr-check.zig b/tools/incr-check.zig index 05a6afc8b846818deb52877fe19e1c02f838541e..2dfb23447a13f5744124b306159d018507f3b6d2 100644 --- a/tools/incr-check.zig +++ b/tools/incr-check.zig @@ -27,18 +27,12 @@ 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.allocator(); + const io = init.io; + const environ_map = init.environ_map; + const cwd_path = try std.process.getCwdAlloc(arena); var opt_zig_exe: ?[]const u8 = null; var opt_input_file_name: ?[]const u8 = null; @@ -52,7 +46,7 @@ pub fn main() !void { var debug_log_args: std.ArrayList([]const u8) = .empty; - var arg_it = try std.process.argsWithAllocator(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] == '-') { @@ -119,9 +113,9 @@ pub fn main() !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, environ_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, environ_map, tmp_dir_path, lib_dir) else null; @@ -179,18 +173,10 @@ pub fn main() !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| - try Dir.path.relative(arena, 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; @@ -209,6 +195,17 @@ pub fn main() !void { try cc_child_args.append(arena, "-o"); } + var child = try 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, @@ -227,11 +224,6 @@ pub fn main() !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.?, @@ -536,7 +528,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, @@ -564,7 +556,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) { @@ -572,9 +564,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}); }, } @@ -622,7 +617,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, @@ -631,13 +626,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}); } @@ -651,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 }); }; @@ -917,8 +912,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/process_headers.zig b/tools/process_headers.zig index cbd94c6292fffecd177572ee6962328bb169b7a2..deb1f01b5ebde66a342846d105c8fd426c52cedf 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 environ_map = init.environ_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, environ_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 }); } diff --git a/tools/update-linux-headers.zig b/tools/update-linux-headers.zig index 5091b18dbe74e88eeec1516beb8e67091672b413..e05ba082031cbee62f0aab9638f127bfbc2ae0ff 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 environ_map = init.environ_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, 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_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 3852a85193f059e74fb7befb118d68cee5e2b10b..3041ee6acc5129a3bfbe9b8d1295a94f69a529e7 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(); + 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 @@ -1994,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, }); @@ -2004,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];