authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-01-04 09:28:33+01:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-01-04 09:28:33+01:00
logde25a6ffee7c4b055b235f4b1f0116530592e81b
treed2b1f439a15ad31594a1bb971ff2ec4f9d21e0f5
parent86a9a9048e28efc8b49d42db12db123822c3450b
parentef1ddbe2f03a165c86af4e2dd36178b1d8661ebe

Merge pull request 'std: delete `os.environ`, `os.argv`, add new parameter to `main`, move process API to `std.Io`' (#30644) from juice into master

Reviewed-on: https://codeberg.org/ziglang/zig/pulls/30644

342 files changed, 6772 insertions(+), 7357 deletions(-)

CMakeLists.txt-1
...@@ -513,7 +513,6 @@ set(ZIG_STAGE2_SOURCES...@@ -513,7 +513,6 @@ set(ZIG_STAGE2_SOURCES
513 src/Builtin.zig513 src/Builtin.zig
514 src/Compilation.zig514 src/Compilation.zig
515 src/Compilation/Config.zig515 src/Compilation/Config.zig
516 src/DarwinPosixSpawn.zig
517 src/InternPool.zig516 src/InternPool.zig
518 src/Package.zig517 src/Package.zig
519 src/Package/Fetch.zig518 src/Package/Fetch.zig
build.zig+3-29
...@@ -261,7 +261,7 @@ pub fn build(b: *std.Build) !void {...@@ -261,7 +261,7 @@ pub fn build(b: *std.Build) !void {
261 "--git-dir", ".git", // affected by the -C argument261 "--git-dir", ".git", // affected by the -C argument
262 "describe", "--match", "*.*.*", //262 "describe", "--match", "*.*.*", //
263 "--tags", "--abbrev=9",263 "--tags", "--abbrev=9",
264 }, &code, .Ignore) catch {264 }, &code, .ignore) catch {
265 break :v version_string;265 break :v version_string;
266 };266 };
267 const git_describe = mem.trim(u8, git_describe_untrimmed, " \n\r");267 const git_describe = mem.trim(u8, git_describe_untrimmed, " \n\r");
...@@ -470,10 +470,7 @@ pub fn build(b: *std.Build) !void {...@@ -470,10 +470,7 @@ pub fn build(b: *std.Build) !void {
470 .skip_llvm = skip_llvm,470 .skip_llvm = skip_llvm,
471 .skip_libc = skip_libc,471 .skip_libc = skip_libc,
472 .max_rss = switch (b.graph.host.result.os.tag) {472 .max_rss = switch (b.graph.host.result.os.tag) {
473 .freebsd => switch (b.graph.host.result.cpu.arch) {473 .freebsd => 2_000_000_000,
474 .x86_64 => 1_060_217_241,
475 else => 1_100_000_000,
476 },
477 .linux => switch (b.graph.host.result.cpu.arch) {474 .linux => switch (b.graph.host.result.cpu.arch) {
478 .aarch64 => 659_809_075,475 .aarch64 => 659_809_075,
479 .loongarch64 => 598_902_374,476 .loongarch64 => 598_902_374,
...@@ -829,30 +826,7 @@ fn addCompilerMod(b: *std.Build, options: AddCompilerModOptions) *std.Build.Modu...@@ -829,30 +826,7 @@ fn addCompilerMod(b: *std.Build, options: AddCompilerModOptions) *std.Build.Modu
829fn addCompilerStep(b: *std.Build, options: AddCompilerModOptions) *std.Build.Step.Compile {826fn addCompilerStep(b: *std.Build, options: AddCompilerModOptions) *std.Build.Step.Compile {
830 const exe = b.addExecutable(.{827 const exe = b.addExecutable(.{
831 .name = "zig",828 .name = "zig",
832 .max_rss = switch (b.graph.host.result.os.tag) {829 .max_rss = 7_000_000_000,
833 .freebsd => switch (b.graph.host.result.cpu.arch) {
834 .x86_64 => 6_044_158_771,
835 else => 6_100_000_000,
836 },
837 .linux => switch (b.graph.host.result.cpu.arch) {
838 .aarch64 => 6_240_805_683,
839 .loongarch64 => 5_024_158_515,
840 .powerpc64le => 5_224_914_534,
841 .riscv64 => 6_996_309_196,
842 .s390x => 4_997_174_476,
843 .x86_64 => 6_664_025_702,
844 else => 7_000_000_000,
845 },
846 .macos => switch (b.graph.host.result.cpu.arch) {
847 .aarch64 => 6_639_145_779,
848 else => 6_700_000_000,
849 },
850 .windows => switch (b.graph.host.result.cpu.arch) {
851 .x86_64 => 5_770_394_009,
852 else => 5_800_000_000,
853 },
854 else => 7_000_000_000,
855 },
856 .root_module = addCompilerMod(b, options),830 .root_module = addCompilerMod(b, options),
857 });831 });
858 exe.stack_size = stack_size;832 exe.stack_size = stack_size;
doc/langref.html.in+1-2
...@@ -7027,8 +7027,7 @@ WebAssembly.instantiate(typedArray, {...@@ -7027,8 +7027,7 @@ WebAssembly.instantiate(typedArray, {
7027The result is 3{#end_shell_samp#}7027The result is 3{#end_shell_samp#}
7028 {#header_close#}7028 {#header_close#}
7029 {#header_open|WASI#}7029 {#header_open|WASI#}
7030 <p>Zig's support for WebAssembly System Interface (WASI) is under active development.7030 <p>Zig standard library has first-class support for WebAssembly System Interface.</p>
7031 Example of using the standard library and reading command line arguments:</p>
7032 {#code|wasi_args.zig#}7031 {#code|wasi_args.zig#}
70337032
7034 {#shell_samp#}$ wasmtime wasi_args.wasm 123 hello7033 {#shell_samp#}$ wasmtime wasi_args.wasm 123 hello
doc/langref/hello.zig+2-12
...@@ -1,17 +1,7 @@...@@ -1,17 +1,7 @@
1const std = @import("std");1const std = @import("std");
22
3// See https://github.com/ziglang/zig/issues/245103pub fn main(init: std.process.Init) !void {
4// for the plan to simplify this code.4 try std.Io.File.stdout().writeStreamingAll(init.io, "Hello, World!\n");
5pub fn main() !void {
6 var debug_allocator: std.heap.DebugAllocator(.{}) = .init;
7 defer _ = debug_allocator.deinit();
8 const gpa = debug_allocator.allocator();
9
10 var threaded: std.Io.Threaded = .init(gpa, .{});
11 defer threaded.deinit();
12 const io = threaded.io();
13
14 try std.Io.File.stdout().writeStreamingAll(io, "Hello, World!\n");
15}5}
166
17// exe=succeed7// exe=succeed
doc/langref/wasi_args.zig+4-8
...@@ -1,13 +1,9 @@...@@ -1,13 +1,9 @@
1const std = @import("std");1const std = @import("std");
22
3pub fn main() !void {3pub fn main(init: std.process.Init) !void {
4 var general_purpose_allocator: std.heap.GeneralPurposeAllocator(.{}) = .init;4 const args = try init.minimal.args.toSlice(init.arena.allocator());
5 const gpa = general_purpose_allocator.allocator();5 for (0.., args) |i, arg| {
6 const args = try std.process.argsAlloc(gpa);6 std.debug.print("{d}: {s}\n", .{ i, arg });
7 defer std.process.argsFree(gpa, args);
8
9 for (args, 0..) |arg, i| {
10 std.debug.print("{}: {s}\n", .{ i, arg });
11 }7 }
12}8}
139
doc/langref/wasi_preopens.zig+3-11
...@@ -1,18 +1,10 @@...@@ -1,18 +1,10 @@
1const std = @import("std");1const std = @import("std");
2const fs = std.fs;
32
4pub fn main() !void {3pub fn main(init: std.process.Init) !void {
5 var general_purpose_allocator: std.heap.GeneralPurposeAllocator(.{}) = .init;4 const preopens = try std.fs.wasi.preopensAlloc(init.arena.allocator());
6 const gpa = general_purpose_allocator.allocator();
7
8 var arena_instance = std.heap.ArenaAllocator.init(gpa);
9 defer arena_instance.deinit();
10 const arena = arena_instance.allocator();
11
12 const preopens = try fs.wasi.preopensAlloc(arena);
135
14 for (preopens.names, 0..) |preopen, i| {6 for (preopens.names, 0..) |preopen, i| {
15 std.debug.print("{}: {s}\n", .{ i, preopen });7 std.debug.print("{d}: {s}\n", .{ i, preopen });
16 }8 }
17}9}
1810
lib/compiler/aro/aro/Compilation.zig+11-10
...@@ -74,9 +74,7 @@ pub const Environment = struct {...@@ -74,9 +74,7 @@ pub const Environment = struct {
74 pub const default: @This() = .{ .provided = 0 };74 pub const default: @This() = .{ .provided = 0 };
75 };75 };
7676
77 /// Load all of the environment variables using the std.process API. Do not use if using Aro as a shared library on Linux without libc77 pub fn loadAll(allocator: std.mem.Allocator, environ_map: *const std.process.Environ.Map) !Environment {
78 /// See https://github.com/ziglang/zig/issues/4524
79 pub fn loadAll(allocator: std.mem.Allocator) !Environment {
80 var env: Environment = .{};78 var env: Environment = .{};
81 errdefer env.deinit(allocator);79 errdefer env.deinit(allocator);
8280
...@@ -85,11 +83,7 @@ pub const Environment = struct {...@@ -85,11 +83,7 @@ pub const Environment = struct {
8583
86 var env_var_buf: [field.name.len]u8 = undefined;84 var env_var_buf: [field.name.len]u8 = undefined;
87 const env_var_name = std.ascii.upperString(&env_var_buf, field.name);85 const env_var_name = std.ascii.upperString(&env_var_buf, field.name);
88 const val: ?[]const u8 = std.process.getEnvVarOwned(allocator, env_var_name) catch |err| switch (err) {86 const val: ?[]const u8 = if (environ_map.get(env_var_name)) |v| try allocator.dupe(u8, v) else null;
89 error.OutOfMemory => |e| return e,
90 error.EnvironmentVariableNotFound => null,
91 error.InvalidWtf8 => null,
92 };
93 @field(env, field.name) = val;87 @field(env, field.name) = val;
94 }88 }
95 return env;89 return env;
...@@ -193,13 +187,20 @@ pub fn init(gpa: Allocator, arena: Allocator, io: Io, diagnostics: *Diagnostics,...@@ -193,13 +187,20 @@ pub fn init(gpa: Allocator, arena: Allocator, io: Io, diagnostics: *Diagnostics,
193187
194/// Initialize Compilation with default environment,188/// Initialize Compilation with default environment,
195/// pragma handlers and emulation mode set to target.189/// pragma handlers and emulation mode set to target.
196pub fn initDefault(gpa: Allocator, arena: Allocator, io: Io, diagnostics: *Diagnostics, cwd: Io.Dir) !Compilation {190pub fn initDefault(
191 gpa: Allocator,
192 arena: Allocator,
193 io: Io,
194 diagnostics: *Diagnostics,
195 cwd: Io.Dir,
196 environ_map: *const std.process.Environ.Map,
197) !Compilation {
197 var comp: Compilation = .{198 var comp: Compilation = .{
198 .gpa = gpa,199 .gpa = gpa,
199 .arena = arena,200 .arena = arena,
200 .io = io,201 .io = io,
201 .diagnostics = diagnostics,202 .diagnostics = diagnostics,
202 .environment = try Environment.loadAll(gpa),203 .environment = try Environment.loadAll(gpa, environ_map),
203 .cwd = cwd,204 .cwd = cwd,
204 };205 };
205 errdefer comp.deinit();206 errdefer comp.deinit();
lib/compiler/aro/aro/Driver.zig+24-16
...@@ -1250,21 +1250,25 @@ fn getOutFileName(d: *Driver, source: Source, buf: *[std.fs.max_name_bytes]u8) !...@@ -1250,21 +1250,25 @@ fn getOutFileName(d: *Driver, source: Source, buf: *[std.fs.max_name_bytes]u8) !
1250}1250}
12511251
1252fn invokeAssembler(d: *Driver, tc: *Toolchain, input_path: []const u8, output_path: []const u8) !void {1252fn invokeAssembler(d: *Driver, tc: *Toolchain, input_path: []const u8, output_path: []const u8) !void {
1253 const io = d.comp.io;
1253 var assembler_path_buf: [std.fs.max_path_bytes]u8 = undefined;1254 var assembler_path_buf: [std.fs.max_path_bytes]u8 = undefined;
1254 const assembler_path = try tc.getAssemblerPath(&assembler_path_buf);1255 const assembler_path = try tc.getAssemblerPath(&assembler_path_buf);
1255 const argv = [_][]const u8{ assembler_path, input_path, "-o", output_path };1256 const argv = [_][]const u8{ assembler_path, input_path, "-o", output_path };
12561257
1257 var child = std.process.Child.init(&argv, d.comp.gpa);1258 var child = std.process.spawn(io, .{
1258 // TODO handle better1259 .argv = &argv,
1259 child.stdin_behavior = .Inherit;1260 // TODO handle better
1260 child.stdout_behavior = .Inherit;1261 .stdin = .inherit,
1261 child.stderr_behavior = .Inherit;1262 .stdout = .inherit,
12621263 .stderr = .inherit,
1263 const term = child.spawnAndWait() catch |er| {1264 }) catch |er| {
1264 return d.fatal("unable to spawn linker: {s}", .{errorDescription(er)});1265 return d.fatal("unable to spawn linker: {s}", .{errorDescription(er)});
1265 };1266 };
1267 const term = child.wait(io) catch |er| {
1268 return d.fatal("unable to wait linker: {s}", .{errorDescription(er)});
1269 };
1266 switch (term) {1270 switch (term) {
1267 .Exited => |code| if (code != 0) {1271 .exited => |code| if (code != 0) {
1268 const e = d.fatal("assembler exited with an error code", .{});1272 const e = d.fatal("assembler exited with an error code", .{});
1269 return e;1273 return e;
1270 },1274 },
...@@ -1490,6 +1494,7 @@ fn dumpLinkerArgs(w: *std.Io.Writer, items: []const []const u8) !void {...@@ -1490,6 +1494,7 @@ fn dumpLinkerArgs(w: *std.Io.Writer, items: []const []const u8) !void {
1490/// **MAY call `exit` if `fast_exit` is set.**1494/// **MAY call `exit` if `fast_exit` is set.**
1491pub fn invokeLinker(d: *Driver, tc: *Toolchain, comptime fast_exit: bool) Compilation.Error!void {1495pub fn invokeLinker(d: *Driver, tc: *Toolchain, comptime fast_exit: bool) Compilation.Error!void {
1492 const gpa = d.comp.gpa;1496 const gpa = d.comp.gpa;
1497 const io = d.comp.io;
1493 var argv: std.ArrayList([]const u8) = .empty;1498 var argv: std.ArrayList([]const u8) = .empty;
1494 defer argv.deinit(gpa);1499 defer argv.deinit(gpa);
14951500
...@@ -1506,17 +1511,20 @@ pub fn invokeLinker(d: *Driver, tc: *Toolchain, comptime fast_exit: bool) Compil...@@ -1506,17 +1511,20 @@ pub fn invokeLinker(d: *Driver, tc: *Toolchain, comptime fast_exit: bool) Compil
1506 return d.fatal("unable to dump linker args: {s}", .{errorDescription(stdout.err.?)});1511 return d.fatal("unable to dump linker args: {s}", .{errorDescription(stdout.err.?)});
1507 };1512 };
1508 }1513 }
1509 var child = std.process.Child.init(argv.items, d.comp.gpa);1514 var child = std.process.spawn(io, .{
1510 // TODO handle better1515 .argv = argv.items,
1511 child.stdin_behavior = .Inherit;1516 // TODO handle better
1512 child.stdout_behavior = .Inherit;1517 .stdin = .inherit,
1513 child.stderr_behavior = .Inherit;1518 .stdout = .inherit,
15141519 .stderr = .inherit,
1515 const term = child.spawnAndWait() catch |er| {1520 }) catch |er| {
1516 return d.fatal("unable to spawn linker: {s}", .{errorDescription(er)});1521 return d.fatal("unable to spawn linker: {s}", .{errorDescription(er)});
1517 };1522 };
1523 const term = child.wait(io) catch |er| {
1524 return d.fatal("unable to wait linker: {s}", .{errorDescription(er)});
1525 };
1518 switch (term) {1526 switch (term) {
1519 .Exited => |code| if (code != 0) {1527 .exited => |code| if (code != 0) {
1520 const e = d.fatal("linker exited with an error code", .{});1528 const e = d.fatal("linker exited with an error code", .{});
1521 if (fast_exit) d.exitWithCleanup(code);1529 if (fast_exit) d.exitWithCleanup(code);
1522 return e;1530 return e;
lib/compiler/aro/main.zig+2-2
...@@ -18,7 +18,7 @@ var debug_allocator: std.heap.DebugAllocator(.{...@@ -18,7 +18,7 @@ var debug_allocator: std.heap.DebugAllocator(.{
18 .canary = @truncate(0xc647026dc6875134),18 .canary = @truncate(0xc647026dc6875134),
19}) = .{};19}) = .{};
2020
21pub fn main() u8 {21pub fn main(init: std.process.Init.Minimal) u8 {
22 const gpa = if (@import("builtin").link_libc)22 const gpa = if (@import("builtin").link_libc)
23 std.heap.c_allocator23 std.heap.c_allocator
24 else24 else
...@@ -37,7 +37,7 @@ pub fn main() u8 {...@@ -37,7 +37,7 @@ pub fn main() u8 {
3737
38 const fast_exit = @import("builtin").mode != .Debug;38 const fast_exit = @import("builtin").mode != .Debug;
3939
40 const args = process.argsAlloc(arena) catch {40 const args = init.args.toSlice(arena) catch {
41 std.debug.print("out of memory\n", .{});41 std.debug.print("out of memory\n", .{});
42 if (fast_exit) process.exit(1);42 if (fast_exit) process.exit(1);
43 return 1;43 return 1;
lib/compiler/build_runner.zig+13-9
...@@ -24,7 +24,7 @@ pub const std_options: std.Options = .{...@@ -24,7 +24,7 @@ pub const std_options: std.Options = .{
24 .crypto_fork_safety = false,24 .crypto_fork_safety = false,
25};25};
2626
27pub fn main() !void {27pub fn main(init: process.Init.Minimal) !void {
28 // The build runner is often short-lived, but thanks to `--watch` and `--webui`, that's not28 // The build runner is often short-lived, but thanks to `--watch` and `--webui`, that's not
29 // always the case. So, we do need a true gpa for some things.29 // always the case. So, we do need a true gpa for some things.
30 var debug_gpa_state: std.heap.DebugAllocator(.{}) = .init;30 var debug_gpa_state: std.heap.DebugAllocator(.{}) = .init;
...@@ -37,9 +37,12 @@ pub fn main() !void {...@@ -37,9 +37,12 @@ pub fn main() !void {
37 var thread_safe_arena: std.heap.ThreadSafeAllocator = .{ .child_allocator = single_threaded_arena.allocator() };37 var thread_safe_arena: std.heap.ThreadSafeAllocator = .{ .child_allocator = single_threaded_arena.allocator() };
38 const arena = thread_safe_arena.allocator();38 const arena = thread_safe_arena.allocator();
3939
40 const args = try process.argsAlloc(arena);40 const args = try init.args.toSlice(arena);
4141
42 var threaded: std.Io.Threaded = .init(gpa, .{});42 var threaded: std.Io.Threaded = .init(gpa, .{
43 .environ = init.environ,
44 .argv0 = .init(init.args),
45 });
43 defer threaded.deinit();46 defer threaded.deinit();
44 const io = threaded.io();47 const io = threaded.io();
4548
...@@ -81,9 +84,10 @@ pub fn main() !void {...@@ -81,9 +84,10 @@ pub fn main() !void {
81 .io = io,84 .io = io,
82 .gpa = arena,85 .gpa = arena,
83 .manifest_dir = try local_cache_directory.handle.createDirPathOpen(io, "h", .{}),86 .manifest_dir = try local_cache_directory.handle.createDirPathOpen(io, "h", .{}),
87 .cwd = try process.getCwdAlloc(single_threaded_arena.allocator()),
84 },88 },
85 .zig_exe = zig_exe,89 .zig_exe = zig_exe,
86 .env_map = try process.getEnvMap(arena),90 .environ_map = try init.environ.createMap(arena),
87 .global_cache_root = global_cache_directory,91 .global_cache_root = global_cache_directory,
88 .zig_lib_directory = zig_lib_directory,92 .zig_lib_directory = zig_lib_directory,
89 .host = .{93 .host = .{
...@@ -126,13 +130,13 @@ pub fn main() !void {...@@ -126,13 +130,13 @@ pub fn main() !void {
126 var debounce_interval_ms: u16 = 50;130 var debounce_interval_ms: u16 = 50;
127 var webui_listen: ?Io.net.IpAddress = null;131 var webui_listen: ?Io.net.IpAddress = null;
128132
129 if (try std.zig.EnvVar.ZIG_BUILD_ERROR_STYLE.get(arena)) |str| {133 if (std.zig.EnvVar.ZIG_BUILD_ERROR_STYLE.get(&graph.environ_map)) |str| {
130 if (std.meta.stringToEnum(ErrorStyle, str)) |style| {134 if (std.meta.stringToEnum(ErrorStyle, str)) |style| {
131 error_style = style;135 error_style = style;
132 }136 }
133 }137 }
134138
135 if (try std.zig.EnvVar.ZIG_BUILD_MULTILINE_ERRORS.get(arena)) |str| {139 if (std.zig.EnvVar.ZIG_BUILD_MULTILINE_ERRORS.get(&graph.environ_map)) |str| {
136 if (std.meta.stringToEnum(MultilineErrors, str)) |style| {140 if (std.meta.stringToEnum(MultilineErrors, str)) |style| {
137 multiline_errors = style;141 multiline_errors = style;
138 }142 }
...@@ -429,8 +433,8 @@ pub fn main() !void {...@@ -429,8 +433,8 @@ pub fn main() !void {
429 }433 }
430 }434 }
431435
432 const NO_COLOR = std.zig.EnvVar.NO_COLOR.isSet();436 const NO_COLOR = std.zig.EnvVar.NO_COLOR.isSet(&graph.environ_map);
433 const CLICOLOR_FORCE = std.zig.EnvVar.CLICOLOR_FORCE.isSet();437 const CLICOLOR_FORCE = std.zig.EnvVar.CLICOLOR_FORCE.isSet(&graph.environ_map);
434438
435 graph.stderr_mode = switch (color) {439 graph.stderr_mode = switch (color) {
436 .auto => try .detect(io, .stderr(), NO_COLOR, CLICOLOR_FORCE),440 .auto => try .detect(io, .stderr(), NO_COLOR, CLICOLOR_FORCE),
...@@ -540,7 +544,7 @@ pub fn main() !void {...@@ -540,7 +544,7 @@ pub fn main() !void {
540 var w: Watch = w: {544 var w: Watch = w: {
541 if (!watch) break :w undefined;545 if (!watch) break :w undefined;
542 if (!Watch.have_impl) fatal("--watch not yet implemented for {t}", .{builtin.os.tag});546 if (!Watch.have_impl) fatal("--watch not yet implemented for {t}", .{builtin.os.tag});
543 break :w try .init();547 break :w try .init(graph.cache.cwd);
544 };548 };
545549
546 const now = Io.Clock.Timestamp.now(io, .awake) catch |err| fatal("failed to collect timestamp: {t}", .{err});550 const now = Io.Clock.Timestamp.now(io, .awake) catch |err| fatal("failed to collect timestamp: {t}", .{err});
lib/compiler/libc.zig+12-13
...@@ -24,23 +24,19 @@ const usage_libc =...@@ -24,23 +24,19 @@ const usage_libc =
2424
25var stdout_buffer: [4096]u8 = undefined;25var stdout_buffer: [4096]u8 = undefined;
2626
27pub fn main() !void {27pub fn main(init: std.process.Init) !void {
28 var arena_instance = std.heap.ArenaAllocator.init(std.heap.page_allocator);28 const arena = init.arena.allocator();
29 defer arena_instance.deinit();29 const gpa = init.gpa;
30 const arena = arena_instance.allocator();30 const io = init.io;
31 const gpa = arena;31 const args = try init.minimal.args.toSlice(arena);
32 const environ_map = init.environ_map;
3233
33 var threaded: std.Io.Threaded = .init(gpa, .{});
34 defer threaded.deinit();
35 const io = threaded.io();
36
37 const args = try std.process.argsAlloc(arena);
38 const zig_lib_directory = args[1];34 const zig_lib_directory = args[1];
3935
40 var input_file: ?[]const u8 = null;36 var input_file: ?[]const u8 = null;
41 var target_arch_os_abi: []const u8 = "native";37 var target_arch_os_abi: []const u8 = "native";
42 var print_includes: bool = false;38 var print_includes: bool = false;
43 var stdout_writer = Io.File.stdout().writer(&stdout_buffer);39 var stdout_writer = Io.File.stdout().writer(io, &stdout_buffer);
44 const stdout = &stdout_writer.interface;40 const stdout = &stdout_writer.interface;
45 {41 {
46 var i: usize = 2;42 var i: usize = 2;
...@@ -77,7 +73,7 @@ pub fn main() !void {...@@ -77,7 +73,7 @@ pub fn main() !void {
77 const libc_installation: ?*LibCInstallation = libc: {73 const libc_installation: ?*LibCInstallation = libc: {
78 if (input_file) |libc_file| {74 if (input_file) |libc_file| {
79 const libc = try arena.create(LibCInstallation);75 const libc = try arena.create(LibCInstallation);
80 libc.* = LibCInstallation.parse(arena, libc_file, &target) catch |err| {76 libc.* = LibCInstallation.parse(arena, io, libc_file, &target) catch |err| {
81 fatal("unable to parse libc file at path {s}: {t}", .{ libc_file, err });77 fatal("unable to parse libc file at path {s}: {t}", .{ libc_file, err });
82 };78 };
83 break :libc libc;79 break :libc libc;
...@@ -90,11 +86,13 @@ pub fn main() !void {...@@ -90,11 +86,13 @@ pub fn main() !void {
9086
91 const libc_dirs = std.zig.LibCDirs.detect(87 const libc_dirs = std.zig.LibCDirs.detect(
92 arena,88 arena,
89 io,
93 zig_lib_directory,90 zig_lib_directory,
94 &target,91 &target,
95 is_native_abi,92 is_native_abi,
96 true,93 true,
97 libc_installation,94 libc_installation,
95 environ_map,
98 ) catch |err| {96 ) catch |err| {
99 const zig_target = try target.zigTriple(arena);97 const zig_target = try target.zigTriple(arena);
100 fatal("unable to detect libc for target {s}: {t}", .{ zig_target, err });98 fatal("unable to detect libc for target {s}: {t}", .{ zig_target, err });
...@@ -114,7 +112,7 @@ pub fn main() !void {...@@ -114,7 +112,7 @@ pub fn main() !void {
114 }112 }
115113
116 if (input_file) |libc_file| {114 if (input_file) |libc_file| {
117 var libc = LibCInstallation.parse(gpa, libc_file, &target) catch |err| {115 var libc = LibCInstallation.parse(gpa, io, libc_file, &target) catch |err| {
118 fatal("unable to parse libc file at path {s}: {t}", .{ libc_file, err });116 fatal("unable to parse libc file at path {s}: {t}", .{ libc_file, err });
119 };117 };
120 defer libc.deinit(gpa);118 defer libc.deinit(gpa);
...@@ -125,6 +123,7 @@ pub fn main() !void {...@@ -125,6 +123,7 @@ pub fn main() !void {
125 var libc = LibCInstallation.findNative(gpa, io, .{123 var libc = LibCInstallation.findNative(gpa, io, .{
126 .verbose = true,124 .verbose = true,
127 .target = &target,125 .target = &target,
126 .environ_map = environ_map,
128 }) catch |err| {127 }) catch |err| {
129 fatal("unable to detect native libc: {t}", .{err});128 fatal("unable to detect native libc: {t}", .{err});
130 };129 };
lib/compiler/objcopy.zig+4-14
...@@ -17,20 +17,10 @@ var stdout_buffer: [1024]u8 = undefined;...@@ -17,20 +17,10 @@ var stdout_buffer: [1024]u8 = undefined;
17var input_buffer: [1024]u8 = undefined;17var input_buffer: [1024]u8 = undefined;
18var output_buffer: [1024]u8 = undefined;18var output_buffer: [1024]u8 = undefined;
1919
20pub fn main() !void {20pub fn main(init: std.process.Init) !void {
21 var arena_instance = std.heap.ArenaAllocator.init(std.heap.page_allocator);21 const arena = init.arena.allocator();
22 defer arena_instance.deinit();22 const args = try init.minimal.args.toSlice(arena);
23 const arena = arena_instance.allocator();23 return cmdObjCopy(arena, init.io, args[1..]);
24
25 var general_purpose_allocator: std.heap.GeneralPurposeAllocator(.{}) = .init;
26 const gpa = general_purpose_allocator.allocator();
27
28 var threaded: std.Io.Threaded = .init(gpa, .{});
29 defer threaded.deinit();
30 const io = threaded.io();
31
32 const args = try std.process.argsAlloc(arena);
33 return cmdObjCopy(arena, io, args[1..]);
34}24}
3525
36fn cmdObjCopy(arena: Allocator, io: Io, args: []const []const u8) !void {26fn cmdObjCopy(arena: Allocator, io: Io, args: []const []const u8) !void {
lib/compiler/reduce.zig+17-24
...@@ -47,19 +47,11 @@ const Interestingness = enum { interesting, unknown, boring };...@@ -47,19 +47,11 @@ const Interestingness = enum { interesting, unknown, boring };
47// - reduce flags sent to the compiler47// - reduce flags sent to the compiler
48// - integrate with the build system?48// - integrate with the build system?
4949
50pub fn main() !void {50pub fn main(init: std.process.Init) !void {
51 var arena_instance = std.heap.ArenaAllocator.init(std.heap.page_allocator);51 const arena = init.arena.allocator();
52 defer arena_instance.deinit();52 const gpa = init.gpa;
53 const arena = arena_instance.allocator();53 const io = init.io;
5454 const args = try init.minimal.args.toSlice(arena);
55 var general_purpose_allocator: std.heap.GeneralPurposeAllocator(.{}) = .init;
56 const gpa = general_purpose_allocator.allocator();
57
58 var threaded: std.Io.Threaded = .init(gpa, .{});
59 defer threaded.deinit();
60 const io = threaded.io();
61
62 const args = try std.process.argsAlloc(arena);
6355
64 var opt_checker_path: ?[]const u8 = null;56 var opt_checker_path: ?[]const u8 = null;
65 var opt_root_source_file_path: ?[]const u8 = null;57 var opt_root_source_file_path: ?[]const u8 = null;
...@@ -73,8 +65,7 @@ pub fn main() !void {...@@ -73,8 +65,7 @@ pub fn main() !void {
73 const arg = args[i];65 const arg = args[i];
74 if (mem.startsWith(u8, arg, "-")) {66 if (mem.startsWith(u8, arg, "-")) {
75 if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) {67 if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) {
76 const stdout = Io.File.stdout();68 try Io.File.stdout().writeStreamingAll(io, usage);
77 try stdout.writeAll(usage);
78 return std.process.cleanExit(io);69 return std.process.cleanExit(io);
79 } else if (mem.eql(u8, arg, "--")) {70 } else if (mem.eql(u8, arg, "--")) {
80 argv = args[i + 1 ..];71 argv = args[i + 1 ..];
...@@ -131,12 +122,10 @@ pub fn main() !void {...@@ -131,12 +122,10 @@ pub fn main() !void {
131122
132 if (!skip_smoke_test) {123 if (!skip_smoke_test) {
133 std.debug.print("smoke testing the interestingness check...\n", .{});124 std.debug.print("smoke testing the interestingness check...\n", .{});
134 switch (try runCheck(arena, interestingness_argv.items)) {125 switch (try runCheck(arena, io, interestingness_argv.items)) {
135 .interesting => {},126 .interesting => {},
136 .boring, .unknown => |t| {127 .boring, .unknown => |t| {
137 fatal("interestingness check returned {s} for unmodified input\n", .{128 fatal("interestingness check returned {t} for unmodified input\n", .{t});
138 @tagName(t),
139 });
140 },129 },
141 }130 }
142 }131 }
...@@ -238,7 +227,7 @@ pub fn main() !void {...@@ -238,7 +227,7 @@ pub fn main() !void {
238 try Io.Dir.cwd().writeFile(io, .{ .sub_path = root_source_file_path, .data = rendered.written() });227 try Io.Dir.cwd().writeFile(io, .{ .sub_path = root_source_file_path, .data = rendered.written() });
239 // std.debug.print("trying this code:\n{s}\n", .{rendered.items});228 // std.debug.print("trying this code:\n{s}\n", .{rendered.items});
240229
241 const interestingness = try runCheck(arena, interestingness_argv.items);230 const interestingness = try runCheck(arena, io, interestingness_argv.items);
242 std.debug.print("{d} random transformations: {t}. {d}/{d}\n", .{231 std.debug.print("{d} random transformations: {t}. {d}/{d}\n", .{
243 subset_size, interestingness, start_index, transformations.items.len,232 subset_size, interestingness, start_index, transformations.items.len,
244 });233 });
...@@ -293,20 +282,24 @@ fn sortTransformations(transformations: []Walk.Transformation, rng: std.Random)...@@ -293,20 +282,24 @@ fn sortTransformations(transformations: []Walk.Transformation, rng: std.Random)
293282
294fn termToInteresting(term: std.process.Child.Term) Interestingness {283fn termToInteresting(term: std.process.Child.Term) Interestingness {
295 return switch (term) {284 return switch (term) {
296 .Exited => |code| switch (code) {285 .exited => |code| switch (code) {
297 0 => .interesting,286 0 => .interesting,
298 1 => .unknown,287 1 => .unknown,
299 else => .boring,288 else => .boring,
300 },289 },
301 else => b: {290 .signal => |sig| {
291 std.debug.print("interestingness check terminated with signal {t}\n", .{sig});
292 return .boring;
293 },
294 else => {
302 std.debug.print("interestingness check aborted unexpectedly\n", .{});295 std.debug.print("interestingness check aborted unexpectedly\n", .{});
303 break :b .boring;296 return .boring;
304 },297 },
305 };298 };
306}299}
307300
308fn runCheck(arena: Allocator, io: Io, argv: []const []const u8) !Interestingness {301fn runCheck(arena: Allocator, io: Io, argv: []const []const u8) !Interestingness {
309 const result = try std.process.Child.run(arena, io, .{ .argv = argv });302 const result = try std.process.run(arena, io, .{ .argv = argv });
310 if (result.stderr.len != 0)303 if (result.stderr.len != 0)
311 std.debug.print("{s}", .{result.stderr});304 std.debug.print("{s}", .{result.stderr});
312 return termToInteresting(result.term);305 return termToInteresting(result.term);
lib/compiler/resinator/compile.zig+2-3
...@@ -80,7 +80,7 @@ pub const Dependencies = struct {...@@ -80,7 +80,7 @@ pub const Dependencies = struct {
80 }80 }
81};81};
8282
83pub fn compile(allocator: Allocator, io: Io, source: []const u8, writer: *std.Io.Writer, options: CompileOptions) !void {83pub fn compile(allocator: Allocator, io: Io, source: []const u8, writer: *std.Io.Writer, options: CompileOptions, environ_map: *const std.process.Environ.Map) !void {
84 var lexer = lex.Lexer.init(source, .{84 var lexer = lex.Lexer.init(source, .{
85 .default_code_page = options.default_code_page,85 .default_code_page = options.default_code_page,
86 .source_mappings = options.source_mappings,86 .source_mappings = options.source_mappings,
...@@ -148,8 +148,7 @@ pub fn compile(allocator: Allocator, io: Io, source: []const u8, writer: *std.Io...@@ -148,8 +148,7 @@ pub fn compile(allocator: Allocator, io: Io, source: []const u8, writer: *std.Io
148 try search_dirs.append(allocator, .{ .dir = dir, .path = try allocator.dupe(u8, system_include_path) });148 try search_dirs.append(allocator, .{ .dir = dir, .path = try allocator.dupe(u8, system_include_path) });
149 }149 }
150 if (!options.ignore_include_env_var) {150 if (!options.ignore_include_env_var) {
151 const INCLUDE = std.process.getEnvVarOwned(allocator, "INCLUDE") catch "";151 const INCLUDE = environ_map.get("INCLUDE") orelse "";
152 defer allocator.free(INCLUDE);
153152
154 // The only precedence here is llvm-rc which also uses the platform-specific153 // The only precedence here is llvm-rc which also uses the platform-specific
155 // delimiter. There's no precedence set by `rc.exe` since it's Windows-only.154 // delimiter. There's no precedence set by `rc.exe` since it's Windows-only.
lib/compiler/resinator/main.zig+38-11
...@@ -19,12 +19,18 @@ const fmtResourceType = @import("res.zig").NameOrOrdinal.fmtResourceType;...@@ -19,12 +19,18 @@ const fmtResourceType = @import("res.zig").NameOrOrdinal.fmtResourceType;
19const aro = @import("aro");19const aro = @import("aro");
20const compiler_util = @import("../util.zig");20const compiler_util = @import("../util.zig");
2121
22pub fn main() !void {22pub fn main(init: std.process.Init.Minimal) !void {
23 var debug_allocator: std.heap.DebugAllocator(.{}) = .init;23 var debug_allocator: std.heap.DebugAllocator(.{}) = .init;
24 defer std.debug.assert(debug_allocator.deinit() == .ok);24 defer std.debug.assert(debug_allocator.deinit() == .ok);
25 const gpa = debug_allocator.allocator();25 const gpa = debug_allocator.allocator();
2626
27 var threaded: std.Io.Threaded = .init(gpa, .{});27 var environ_map = try init.environ.createMap(gpa);
28 defer environ_map.deinit();
29
30 var threaded: std.Io.Threaded = .init(gpa, .{
31 .environ = init.environ,
32 .argv0 = .init(init.args),
33 });
28 defer threaded.deinit();34 defer threaded.deinit();
29 const io = threaded.io();35 const io = threaded.io();
3036
...@@ -32,7 +38,7 @@ pub fn main() !void {...@@ -32,7 +38,7 @@ pub fn main() !void {
32 defer arena_state.deinit();38 defer arena_state.deinit();
33 const arena = arena_state.allocator();39 const arena = arena_state.allocator();
3440
35 const args = try std.process.argsAlloc(arena);41 const args = try init.args.toSlice(arena);
3642
37 if (args.len < 2) {43 if (args.len < 2) {
38 const stderr = try io.lockStderr(&.{}, null);44 const stderr = try io.lockStderr(&.{}, null);
...@@ -145,8 +151,8 @@ pub fn main() !void {...@@ -145,8 +151,8 @@ pub fn main() !void {
145 defer argv.deinit(aro_arena);151 defer argv.deinit(aro_arena);
146152
147 try argv.append(aro_arena, "arocc"); // dummy command name153 try argv.append(aro_arena, "arocc"); // dummy command name
148 const resolved_include_paths = try include_paths.get(&error_handler);154 const resolved_include_paths = try include_paths.get(&error_handler, &environ_map);
149 try preprocess.appendAroArgs(aro_arena, &argv, options, resolved_include_paths);155 try preprocess.appendAroArgs(aro_arena, &argv, options, resolved_include_paths, &environ_map);
150 try argv.append(aro_arena, switch (options.input_source) {156 try argv.append(aro_arena, switch (options.input_source) {
151 .stdio => "-",157 .stdio => "-",
152 .filename => |filename| filename,158 .filename => |filename| filename,
...@@ -280,7 +286,7 @@ pub fn main() !void {...@@ -280,7 +286,7 @@ pub fn main() !void {
280 .dependencies = maybe_dependencies,286 .dependencies = maybe_dependencies,
281 .ignore_include_env_var = options.ignore_include_env_var,287 .ignore_include_env_var = options.ignore_include_env_var,
282 .extra_include_paths = options.extra_include_paths.items,288 .extra_include_paths = options.extra_include_paths.items,
283 .system_include_paths = try include_paths.get(&error_handler),289 .system_include_paths = try include_paths.get(&error_handler, &environ_map),
284 .default_language_id = options.default_language_id,290 .default_language_id = options.default_language_id,
285 .default_code_page = default_code_page,291 .default_code_page = default_code_page,
286 .disjoint_code_page = has_disjoint_code_page,292 .disjoint_code_page = has_disjoint_code_page,
...@@ -289,7 +295,7 @@ pub fn main() !void {...@@ -289,7 +295,7 @@ pub fn main() !void {
289 .max_string_literal_codepoints = options.max_string_literal_codepoints,295 .max_string_literal_codepoints = options.max_string_literal_codepoints,
290 .silent_duplicate_control_ids = options.silent_duplicate_control_ids,296 .silent_duplicate_control_ids = options.silent_duplicate_control_ids,
291 .warn_instead_of_error_on_invalid_code_page = options.warn_instead_of_error_on_invalid_code_page,297 .warn_instead_of_error_on_invalid_code_page = options.warn_instead_of_error_on_invalid_code_page,
292 }) catch |err| switch (err) {298 }, &environ_map) catch |err| switch (err) {
293 error.ParseError, error.CompileError => {299 error.ParseError, error.CompileError => {
294 try error_handler.emitDiagnostics(gpa, Io.Dir.cwd(), final_input, &diagnostics, mapping_results.mappings);300 try error_handler.emitDiagnostics(gpa, Io.Dir.cwd(), final_input, &diagnostics, mapping_results.mappings);
295 // Delete the output file on error301 // Delete the output file on error
...@@ -536,13 +542,24 @@ const LazyIncludePaths = struct {...@@ -536,13 +542,24 @@ const LazyIncludePaths = struct {
536 target_machine_type: std.coff.IMAGE.FILE.MACHINE,542 target_machine_type: std.coff.IMAGE.FILE.MACHINE,
537 resolved_include_paths: ?[]const []const u8 = null,543 resolved_include_paths: ?[]const []const u8 = null,
538544
539 pub fn get(self: *LazyIncludePaths, error_handler: *ErrorHandler) ![]const []const u8 {545 pub fn get(
546 self: *LazyIncludePaths,
547 error_handler: *ErrorHandler,
548 environ_map: *const std.process.Environ.Map,
549 ) ![]const []const u8 {
540 const io = self.io;550 const io = self.io;
541551
542 if (self.resolved_include_paths) |include_paths|552 if (self.resolved_include_paths) |include_paths|
543 return include_paths;553 return include_paths;
544554
545 return getIncludePaths(self.arena, io, self.auto_includes_option, self.zig_lib_dir, self.target_machine_type) catch |err| switch (err) {555 return getIncludePaths(
556 self.arena,
557 io,
558 self.auto_includes_option,
559 self.zig_lib_dir,
560 self.target_machine_type,
561 environ_map,
562 ) catch |err| switch (err) {
546 error.OutOfMemory => |e| return e,563 error.OutOfMemory => |e| return e,
547 else => |e| {564 else => |e| {
548 switch (e) {565 switch (e) {
...@@ -569,6 +586,7 @@ fn getIncludePaths(...@@ -569,6 +586,7 @@ fn getIncludePaths(
569 auto_includes_option: cli.Options.AutoIncludes,586 auto_includes_option: cli.Options.AutoIncludes,
570 zig_lib_dir: []const u8,587 zig_lib_dir: []const u8,
571 target_machine_type: std.coff.IMAGE.FILE.MACHINE,588 target_machine_type: std.coff.IMAGE.FILE.MACHINE,
589 environ_map: *const std.process.Environ.Map,
572) ![]const []const u8 {590) ![]const []const u8 {
573 if (auto_includes_option == .none) return &[_][]const u8{};591 if (auto_includes_option == .none) return &[_][]const u8{};
574592
...@@ -615,7 +633,7 @@ fn getIncludePaths(...@@ -615,7 +633,7 @@ fn getIncludePaths(
615 };633 };
616 const target = std.zig.resolveTargetQueryOrFatal(io, target_query);634 const target = std.zig.resolveTargetQueryOrFatal(io, target_query);
617 const is_native_abi = target_query.isNativeAbi();635 const is_native_abi = target_query.isNativeAbi();
618 const detected_libc = std.zig.LibCDirs.detect(arena, io, zig_lib_dir, &target, is_native_abi, true, null) catch {636 const detected_libc = std.zig.LibCDirs.detect(arena, io, zig_lib_dir, &target, is_native_abi, true, null, environ_map) catch {
619 if (includes == .any) {637 if (includes == .any) {
620 // fall back to mingw638 // fall back to mingw
621 includes = .gnu;639 includes = .gnu;
...@@ -641,7 +659,16 @@ fn getIncludePaths(...@@ -641,7 +659,16 @@ fn getIncludePaths(
641 };659 };
642 const target = std.zig.resolveTargetQueryOrFatal(io, target_query);660 const target = std.zig.resolveTargetQueryOrFatal(io, target_query);
643 const is_native_abi = target_query.isNativeAbi();661 const is_native_abi = target_query.isNativeAbi();
644 const detected_libc = std.zig.LibCDirs.detect(arena, io, zig_lib_dir, &target, is_native_abi, true, null) catch |err| switch (err) {662 const detected_libc = std.zig.LibCDirs.detect(
663 arena,
664 io,
665 zig_lib_dir,
666 &target,
667 is_native_abi,
668 true,
669 null,
670 environ_map,
671 ) catch |err| switch (err) {
645 error.OutOfMemory => |e| return e,672 error.OutOfMemory => |e| return e,
646 else => return error.MingwIncludesNotFound,673 else => return error.MingwIncludesNotFound,
647 };674 };
lib/compiler/resinator/preprocess.zig+2-2
...@@ -86,7 +86,7 @@ fn hasAnyErrors(comp: *aro.Compilation) bool {...@@ -86,7 +86,7 @@ fn hasAnyErrors(comp: *aro.Compilation) bool {
8686
87/// `arena` is used for temporary -D argument strings and the INCLUDE environment variable.87/// `arena` is used for temporary -D argument strings and the INCLUDE environment variable.
88/// The arena should be kept alive at least as long as `argv`.88/// The arena should be kept alive at least as long as `argv`.
89pub fn appendAroArgs(arena: Allocator, argv: *std.ArrayList([]const u8), options: cli.Options, system_include_paths: []const []const u8) !void {89pub 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 {
90 try argv.appendSlice(arena, &.{90 try argv.appendSlice(arena, &.{
91 "-E",91 "-E",
92 "--comments",92 "--comments",
...@@ -109,7 +109,7 @@ pub fn appendAroArgs(arena: Allocator, argv: *std.ArrayList([]const u8), options...@@ -109,7 +109,7 @@ pub fn appendAroArgs(arena: Allocator, argv: *std.ArrayList([]const u8), options
109 }109 }
110110
111 if (!options.ignore_include_env_var) {111 if (!options.ignore_include_env_var) {
112 const INCLUDE = std.process.getEnvVarOwned(arena, "INCLUDE") catch "";112 const INCLUDE = environ_map.get("INCLUDE") orelse "";
113113
114 // The only precedence here is llvm-rc which also uses the platform-specific114 // The only precedence here is llvm-rc which also uses the platform-specific
115 // delimiter. There's no precedence set by `rc.exe` since it's Windows-only.115 // delimiter. There's no precedence set by `rc.exe` since it's Windows-only.
lib/compiler/std-docs.zig+36-33
...@@ -21,19 +21,12 @@ fn usage(io: Io) noreturn {...@@ -21,19 +21,12 @@ fn usage(io: Io) noreturn {
21 std.process.exit(1);21 std.process.exit(1);
22}22}
2323
24pub fn main() !void {24pub fn main(init: std.process.Init) !void {
25 var arena_instance = std.heap.ArenaAllocator.init(std.heap.page_allocator);25 const arena = init.arena.allocator();
26 defer arena_instance.deinit();26 const gpa = init.gpa;
27 const arena = arena_instance.allocator();27 const io = init.io;
28
29 var general_purpose_allocator: std.heap.GeneralPurposeAllocator(.{}) = .init;
30 const gpa = general_purpose_allocator.allocator();
3128
32 var threaded: Io.Threaded = .init(gpa, .{});29 var argv = try init.minimal.args.iterateAllocator(arena);
33 defer threaded.deinit();
34 const io = threaded.io();
35
36 var argv = try std.process.argsWithAllocator(arena);
37 defer argv.deinit();30 defer argv.deinit();
38 assert(argv.skip());31 assert(argv.skip());
39 const zig_lib_directory = argv.next().?;32 const zig_lib_directory = argv.next().?;
...@@ -72,7 +65,7 @@ pub fn main() !void {...@@ -72,7 +65,7 @@ pub fn main() !void {
72 const url_with_newline = try std.fmt.allocPrint(arena, "http://127.0.0.1:{d}/\n", .{port});65 const url_with_newline = try std.fmt.allocPrint(arena, "http://127.0.0.1:{d}/\n", .{port});
73 Io.File.stdout().writeStreamingAll(io, url_with_newline) catch {};66 Io.File.stdout().writeStreamingAll(io, url_with_newline) catch {};
74 if (should_open_browser) {67 if (should_open_browser) {
75 openBrowserTab(gpa, io, url_with_newline[0 .. url_with_newline.len - 1 :'\n']) catch |err| {68 openBrowserTab(io, url_with_newline[0 .. url_with_newline.len - 1 :'\n']) catch |err| {
76 std.log.err("unable to open browser: {t}", .{err});69 std.log.err("unable to open browser: {t}", .{err});
77 };70 };
78 }71 }
...@@ -324,11 +317,12 @@ fn buildWasmBinary(...@@ -324,11 +317,12 @@ fn buildWasmBinary(
324 "--listen=-", //317 "--listen=-", //
325 });318 });
326319
327 var child = std.process.Child.init(argv.items, gpa);320 var child = try std.process.spawn(io, .{
328 child.stdin_behavior = .Pipe;321 .argv = argv.items,
329 child.stdout_behavior = .Pipe;322 .stdin = .pipe,
330 child.stderr_behavior = .Pipe;323 .stdout = .pipe,
331 try child.spawn(io);324 .stderr = .pipe,
325 });
332326
333 var poller = Io.poll(gpa, enum { stdout, stderr }, .{327 var poller = Io.poll(gpa, enum { stdout, stderr }, .{
334 .stdout = child.stdout.?,328 .stdout = child.stdout.?,
...@@ -388,19 +382,26 @@ fn buildWasmBinary(...@@ -388,19 +382,26 @@ fn buildWasmBinary(
388 child.stdin = null;382 child.stdin = null;
389383
390 switch (try child.wait(io)) {384 switch (try child.wait(io)) {
391 .Exited => |code| {385 .exited => |code| {
392 if (code != 0) {386 if (code != 0) {
393 std.log.err(387 std.log.err(
394 "the following command exited with error code {d}:\n{s}",388 "the following command exited with error code {d}:\n{s}",
395 .{ code, try std.Build.Step.allocPrintCmd(arena, null, argv.items) },389 .{ code, try std.Build.Step.allocPrintCmd(arena, null, null, argv.items) },
396 );390 );
397 return error.WasmCompilationFailed;391 return error.WasmCompilationFailed;
398 }392 }
399 },393 },
400 .Signal, .Stopped, .Unknown => {394 .signal => |sig| {
395 std.log.err(
396 "the following command terminated with signal {t}:\n{s}",
397 .{ sig, try std.Build.Step.allocPrintCmd(arena, null, null, argv.items) },
398 );
399 return error.WasmCompilationFailed;
400 },
401 .stopped, .unknown => {
401 std.log.err(402 std.log.err(
402 "the following command terminated unexpectedly:\n{s}",403 "the following command terminated unexpectedly:\n{s}",
403 .{try std.Build.Step.allocPrintCmd(arena, null, argv.items)},404 .{try std.Build.Step.allocPrintCmd(arena, null, null, argv.items)},
404 );405 );
405 return error.WasmCompilationFailed;406 return error.WasmCompilationFailed;
406 },407 },
...@@ -410,14 +411,14 @@ fn buildWasmBinary(...@@ -410,14 +411,14 @@ fn buildWasmBinary(
410 try result_error_bundle.renderToStderr(io, .{}, .auto);411 try result_error_bundle.renderToStderr(io, .{}, .auto);
411 std.log.err("the following command failed with {d} compilation errors:\n{s}", .{412 std.log.err("the following command failed with {d} compilation errors:\n{s}", .{
412 result_error_bundle.errorMessageCount(),413 result_error_bundle.errorMessageCount(),
413 try std.Build.Step.allocPrintCmd(arena, null, argv.items),414 try std.Build.Step.allocPrintCmd(arena, null, null, argv.items),
414 });415 });
415 return error.WasmCompilationFailed;416 return error.WasmCompilationFailed;
416 }417 }
417418
418 return result orelse {419 return result orelse {
419 std.log.err("child process failed to report result\n{s}", .{420 std.log.err("child process failed to report result\n{s}", .{
420 try std.Build.Step.allocPrintCmd(arena, null, argv.items),421 try std.Build.Step.allocPrintCmd(arena, null, null, argv.items),
421 });422 });
422 return error.WasmCompilationFailed;423 return error.WasmCompilationFailed;
423 };424 };
...@@ -434,22 +435,24 @@ fn sendMessage(io: Io, file: Io.File, tag: std.zig.Client.Message.Tag) !void {...@@ -434,22 +435,24 @@ fn sendMessage(io: Io, file: Io.File, tag: std.zig.Client.Message.Tag) !void {
434 };435 };
435}436}
436437
437fn openBrowserTab(gpa: Allocator, io: Io, url: []const u8) !void {438fn openBrowserTab(io: Io, url: []const u8) !void {
438 // Until https://github.com/ziglang/zig/issues/19205 is implemented, we439 // Until https://github.com/ziglang/zig/issues/19205 is implemented, we
439 // spawn a thread for this child process.440 // spawn and then leak a concurrent task for this child process.
440 _ = try std.Thread.spawn(.{}, openBrowserTabThread, .{ gpa, io, url });441 const future = try io.concurrent(openBrowserTabTask, .{ io, url });
442 _ = future; // leak it
441}443}
442444
443fn openBrowserTabThread(gpa: Allocator, io: Io, url: []const u8) !void {445fn openBrowserTabTask(io: Io, url: []const u8) !void {
444 const main_exe = switch (builtin.os.tag) {446 const main_exe = switch (builtin.os.tag) {
445 .windows => "explorer",447 .windows => "explorer",
446 .macos => "open",448 .macos => "open",
447 else => "xdg-open",449 else => "xdg-open",
448 };450 };
449 var child = std.process.Child.init(&.{ main_exe, url }, gpa);451 var child = try std.process.spawn(io, .{
450 child.stdin_behavior = .Ignore;452 .argv = &.{ main_exe, url },
451 child.stdout_behavior = .Ignore;453 .stdin = .ignore,
452 child.stderr_behavior = .Ignore;454 .stdout = .ignore,
453 try child.spawn(io);455 .stderr = .ignore,
456 });
454 _ = try child.wait(io);457 _ = try child.wait(io);
455}458}
lib/compiler/test_runner.zig+11-10
...@@ -29,7 +29,7 @@ const need_simple = switch (builtin.zig_backend) {...@@ -29,7 +29,7 @@ const need_simple = switch (builtin.zig_backend) {
29 else => false,29 else => false,
30};30};
3131
32pub fn main() void {32pub fn main(init: std.process.Init.Minimal) void {
33 @disableInstrumentation();33 @disableInstrumentation();
3434
35 if (builtin.cpu.arch.isSpirV()) {35 if (builtin.cpu.arch.isSpirV()) {
...@@ -38,11 +38,10 @@ pub fn main() void {...@@ -38,11 +38,10 @@ pub fn main() void {
38 }38 }
3939
40 if (need_simple) {40 if (need_simple) {
41 return mainSimple() catch @panic("test failure\n");41 return mainSimple() catch @panic("test failure");
42 }42 }
4343
44 const args = std.process.argsAlloc(fba.allocator()) catch44 const args = init.args.toSlice(fba.allocator()) catch @panic("unable to parse command line args");
45 @panic("unable to parse command line args");
4645
47 var listen = false;46 var listen = false;
48 var opt_cache_dir: ?[]const u8 = null;47 var opt_cache_dir: ?[]const u8 = null;
...@@ -66,13 +65,13 @@ pub fn main() void {...@@ -66,13 +65,13 @@ pub fn main() void {
66 }65 }
6766
68 if (listen) {67 if (listen) {
69 return mainServer(args) catch @panic("internal test runner failure");68 return mainServer(init) catch @panic("internal test runner failure");
70 } else {69 } else {
71 return mainTerminal(args);70 return mainTerminal(init);
72 }71 }
73}72}
7473
75fn mainServer(args: []const [:0]const u8) !void {74fn mainServer(init: std.process.Init.Minimal) !void {
76 @disableInstrumentation();75 @disableInstrumentation();
77 var stdin_reader = Io.File.stdin().readerStreaming(runner_threaded_io, &stdin_buffer);76 var stdin_reader = Io.File.stdin().readerStreaming(runner_threaded_io, &stdin_buffer);
78 var stdout_writer = Io.File.stdout().writerStreaming(runner_threaded_io, &stdout_buffer);77 var stdout_writer = Io.File.stdout().writerStreaming(runner_threaded_io, &stdout_buffer);
...@@ -132,7 +131,8 @@ fn mainServer(args: []const [:0]const u8) !void {...@@ -132,7 +131,8 @@ fn mainServer(args: []const [:0]const u8) !void {
132 .run_test => {131 .run_test => {
133 testing.allocator_instance = .{};132 testing.allocator_instance = .{};
134 testing.io_instance = .init(testing.allocator, .{133 testing.io_instance = .init(testing.allocator, .{
135 .argv0 = if (@hasField(Io.Threaded.Argv0, "value")) .{ .value = args[0] } else .{},134 .argv0 = .init(init.args),
135 .environ = init.environ,
136 });136 });
137 log_err_count = 0;137 log_err_count = 0;
138 const index = try server.receiveBody_u32();138 const index = try server.receiveBody_u32();
...@@ -217,7 +217,7 @@ fn mainServer(args: []const [:0]const u8) !void {...@@ -217,7 +217,7 @@ fn mainServer(args: []const [:0]const u8) !void {
217 }217 }
218}218}
219219
220fn mainTerminal(args: []const [:0]const u8) void {220fn mainTerminal(init: std.process.Init.Minimal) void {
221 @disableInstrumentation();221 @disableInstrumentation();
222 if (builtin.fuzz) @panic("fuzz test requires server");222 if (builtin.fuzz) @panic("fuzz test requires server");
223223
...@@ -236,7 +236,8 @@ fn mainTerminal(args: []const [:0]const u8) void {...@@ -236,7 +236,8 @@ fn mainTerminal(args: []const [:0]const u8) void {
236 for (test_fn_list, 0..) |test_fn, i| {236 for (test_fn_list, 0..) |test_fn, i| {
237 testing.allocator_instance = .{};237 testing.allocator_instance = .{};
238 testing.io_instance = .init(testing.allocator, .{238 testing.io_instance = .init(testing.allocator, .{
239 .argv0 = if (@hasField(Io.Threaded.Argv0, "value")) .{ .value = args[0] } else .{},239 .argv0 = .init(init.args),
240 .environ = init.environ,
240 });241 });
241 defer {242 defer {
242 testing.io_instance.deinit();243 testing.io_instance.deinit();
lib/compiler/translate-c/main.zig+34-44
...@@ -9,21 +9,13 @@ const Translator = @import("Translator.zig");...@@ -9,21 +9,13 @@ const Translator = @import("Translator.zig");
99
10const fast_exit = @import("builtin").mode != .Debug;10const fast_exit = @import("builtin").mode != .Debug;
1111
12var general_purpose_allocator: std.heap.GeneralPurposeAllocator(.{}) = .init;12pub fn main(init: std.process.Init) u8 {
13 const gpa = init.gpa;
14 const arena = init.arena.allocator();
15 const io = init.io;
16 const environ_map = init.environ_map;
1317
14pub fn main() u8 {18 const args = init.minimal.args.toSlice(arena) catch {
15 const gpa = general_purpose_allocator.allocator();
16 defer _ = general_purpose_allocator.deinit();
17
18 var arena_instance = std.heap.ArenaAllocator.init(gpa);
19 defer arena_instance.deinit();
20 const arena = arena_instance.allocator();
21
22 var threaded: std.Io.Threaded = .init(gpa, .{});
23 defer threaded.deinit();
24 const io = threaded.io();
25
26 const args = process.argsAlloc(arena) catch {
27 std.debug.print("ran out of memory allocating arguments\n", .{});19 std.debug.print("ran out of memory allocating arguments\n", .{});
28 if (fast_exit) process.exit(1);20 if (fast_exit) process.exit(1);
29 return 1;21 return 1;
...@@ -34,8 +26,8 @@ pub fn main() u8 {...@@ -34,8 +26,8 @@ pub fn main() u8 {
34 zig_integration = true;26 zig_integration = true;
35 }27 }
3628
37 const NO_COLOR = std.zig.EnvVar.NO_COLOR.isSet();29 const NO_COLOR = std.zig.EnvVar.NO_COLOR.isSet(environ_map);
38 const CLICOLOR_FORCE = std.zig.EnvVar.CLICOLOR_FORCE.isSet();30 const CLICOLOR_FORCE = std.zig.EnvVar.CLICOLOR_FORCE.isSet(environ_map);
3931
40 var stderr_buf: [1024]u8 = undefined;32 var stderr_buf: [1024]u8 = undefined;
41 var stderr = Io.File.stderr().writer(io, &stderr_buf);33 var stderr = Io.File.stderr().writer(io, &stderr_buf);
...@@ -50,7 +42,7 @@ pub fn main() u8 {...@@ -50,7 +42,7 @@ pub fn main() u8 {
50 };42 };
51 defer diagnostics.deinit();43 defer diagnostics.deinit();
5244
53 var comp = aro.Compilation.initDefault(gpa, arena, io, &diagnostics, Io.Dir.cwd()) catch |err| switch (err) {45 var comp = aro.Compilation.initDefault(gpa, arena, io, &diagnostics, .cwd(), environ_map) catch |err| switch (err) {
54 error.OutOfMemory => {46 error.OutOfMemory => {
55 std.debug.print("ran out of memory initializing C compilation\n", .{});47 std.debug.print("ran out of memory initializing C compilation\n", .{});
56 if (fast_exit) process.exit(1);48 if (fast_exit) process.exit(1);
...@@ -123,43 +115,41 @@ pub const usage =...@@ -123,43 +115,41 @@ pub const usage =
123 \\115 \\
124;116;
125117
126fn translate(d: *aro.Driver, tc: *aro.Toolchain, args: [][:0]u8, zig_integration: bool) !void {118fn translate(d: *aro.Driver, tc: *aro.Toolchain, args: []const [:0]const u8, zig_integration: bool) !void {
127 const gpa = d.comp.gpa;119 const gpa = d.comp.gpa;
128 const io = d.comp.io;120 const io = d.comp.io;
129121
130 const aro_args = args: {122 var aro_args: std.ArrayList([:0]const u8) = .empty;
131 var i: usize = 0;123 defer aro_args.deinit(gpa);
132 for (args) |arg| {124
133 args[i] = arg;125 for (args, 0..) |arg, i| {
134 if (mem.eql(u8, arg, "--help")) {126 if (mem.eql(u8, arg, "--help")) {
135 var stdout_buf: [512]u8 = undefined;127 var stdout_buf: [512]u8 = undefined;
136 var stdout = Io.File.stdout().writer(io, &stdout_buf);128 var stdout = Io.File.stdout().writer(io, &stdout_buf);
137 try stdout.interface.print(usage, .{args[0]});129 try stdout.interface.print(usage, .{args[0]});
138 try stdout.interface.flush();130 try stdout.interface.flush();
139 return;131 return;
140 } else if (mem.eql(u8, arg, "--version")) {132 } else if (mem.eql(u8, arg, "--version")) {
141 var stdout_buf: [512]u8 = undefined;133 var stdout_buf: [512]u8 = undefined;
142 var stdout = Io.File.stdout().writer(io, &stdout_buf);134 var stdout = Io.File.stdout().writer(io, &stdout_buf);
143 // TODO add version135 // TODO add version
144 try stdout.interface.writeAll("0.0.0-dev\n");136 try stdout.interface.writeAll("0.0.0-dev\n");
145 try stdout.interface.flush();137 try stdout.interface.flush();
146 return;138 return;
147 } else if (mem.eql(u8, arg, "--zig-integration")) {139 } else if (mem.eql(u8, arg, "--zig-integration")) {
148 if (i != 1 or !zig_integration)140 if (i != 1 or !zig_integration)
149 return d.fatal("--zig-integration must be the first argument", .{});141 return d.fatal("--zig-integration must be the first argument", .{});
150 } else {142 } else {
151 i += 1;143 try aro_args.append(gpa, arg);
152 }
153 }144 }
154 break :args args[0..i];145 }
155 };
156 const user_macros = macros: {146 const user_macros = macros: {
157 var macro_buf: std.ArrayList(u8) = .empty;147 var macro_buf: std.ArrayList(u8) = .empty;
158 defer macro_buf.deinit(gpa);148 defer macro_buf.deinit(gpa);
159149
160 var discard_buf: [256]u8 = undefined;150 var discard_buf: [256]u8 = undefined;
161 var discarding: std.Io.Writer.Discarding = .init(&discard_buf);151 var discarding: std.Io.Writer.Discarding = .init(&discard_buf);
162 assert(!try d.parseArgs(&discarding.writer, &macro_buf, aro_args));152 assert(!try d.parseArgs(&discarding.writer, &macro_buf, aro_args.items));
163 if (macro_buf.items.len > std.math.maxInt(u32)) {153 if (macro_buf.items.len > std.math.maxInt(u32)) {
164 return d.fatal("user provided macro source exceeded max size", .{});154 return d.fatal("user provided macro source exceeded max size", .{});
165 }155 }
lib/compiler_rt.zig+17-1
...@@ -1,7 +1,23 @@...@@ -1,7 +1,23 @@
1const std = @import("std");
1const builtin = @import("builtin");2const builtin = @import("builtin");
2const common = @import("compiler_rt/common.zig");3const common = @import("compiler_rt/common.zig");
34
4pub const panic = common.panic;5/// Avoid dragging in the runtime safety mechanisms into this .o file, unless
6/// we're trying to test compiler-rt.
7pub const panic = if (common.test_safety)
8 std.debug.FullPanic(std.debug.defaultPanic)
9else
10 std.debug.no_panic;
11
12pub const std_options_debug_threaded_io: ?*std.Io.Threaded = if (builtin.is_test)
13 std.Io.Threaded.global_single_threaded
14else
15 null;
16
17pub const std_options_debug_io: std.Io = if (builtin.is_test)
18 std.Io.Threaded.global_single_threaded.ioBasic()
19else
20 unreachable;
521
6comptime {22comptime {
7 // Integer routines23 // Integer routines
lib/compiler_rt/absvdi2.zig-2
...@@ -1,8 +1,6 @@...@@ -1,8 +1,6 @@
1const common = @import("./common.zig");1const common = @import("./common.zig");
2const absv = @import("./absv.zig").absv;2const absv = @import("./absv.zig").absv;
33
4pub const panic = common.panic;
5
6comptime {4comptime {
7 @export(&__absvdi2, .{ .name = "__absvdi2", .linkage = common.linkage, .visibility = common.visibility });5 @export(&__absvdi2, .{ .name = "__absvdi2", .linkage = common.linkage, .visibility = common.visibility });
8}6}
lib/compiler_rt/absvsi2.zig-2
...@@ -1,8 +1,6 @@...@@ -1,8 +1,6 @@
1const common = @import("./common.zig");1const common = @import("./common.zig");
2const absv = @import("./absv.zig").absv;2const absv = @import("./absv.zig").absv;
33
4pub const panic = common.panic;
5
6comptime {4comptime {
7 @export(&__absvsi2, .{ .name = "__absvsi2", .linkage = common.linkage, .visibility = common.visibility });5 @export(&__absvsi2, .{ .name = "__absvsi2", .linkage = common.linkage, .visibility = common.visibility });
8}6}
lib/compiler_rt/absvti2.zig-2
...@@ -1,8 +1,6 @@...@@ -1,8 +1,6 @@
1const common = @import("./common.zig");1const common = @import("./common.zig");
2const absv = @import("./absv.zig").absv;2const absv = @import("./absv.zig").absv;
33
4pub const panic = common.panic;
5
6comptime {4comptime {
7 @export(&__absvti2, .{ .name = "__absvti2", .linkage = common.linkage, .visibility = common.visibility });5 @export(&__absvti2, .{ .name = "__absvti2", .linkage = common.linkage, .visibility = common.visibility });
8}6}
lib/compiler_rt/adddf3.zig-2
...@@ -1,8 +1,6 @@...@@ -1,8 +1,6 @@
1const common = @import("./common.zig");1const common = @import("./common.zig");
2const addf3 = @import("./addf3.zig").addf3;2const addf3 = @import("./addf3.zig").addf3;
33
4pub const panic = common.panic;
5
6comptime {4comptime {
7 if (common.want_aeabi) {5 if (common.want_aeabi) {
8 @export(&__aeabi_dadd, .{ .name = "__aeabi_dadd", .linkage = common.linkage, .visibility = common.visibility });6 @export(&__aeabi_dadd, .{ .name = "__aeabi_dadd", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/addhf3.zig-2
...@@ -1,8 +1,6 @@...@@ -1,8 +1,6 @@
1const common = @import("./common.zig");1const common = @import("./common.zig");
2const addf3 = @import("./addf3.zig").addf3;2const addf3 = @import("./addf3.zig").addf3;
33
4pub const panic = common.panic;
5
6comptime {4comptime {
7 @export(&__addhf3, .{ .name = "__addhf3", .linkage = common.linkage, .visibility = common.visibility });5 @export(&__addhf3, .{ .name = "__addhf3", .linkage = common.linkage, .visibility = common.visibility });
8}6}
lib/compiler_rt/addsf3.zig-2
...@@ -1,8 +1,6 @@...@@ -1,8 +1,6 @@
1const common = @import("./common.zig");1const common = @import("./common.zig");
2const addf3 = @import("./addf3.zig").addf3;2const addf3 = @import("./addf3.zig").addf3;
33
4pub const panic = common.panic;
5
6comptime {4comptime {
7 if (common.want_aeabi) {5 if (common.want_aeabi) {
8 @export(&__aeabi_fadd, .{ .name = "__aeabi_fadd", .linkage = common.linkage, .visibility = common.visibility });6 @export(&__aeabi_fadd, .{ .name = "__aeabi_fadd", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/addtf3.zig-2
...@@ -1,8 +1,6 @@...@@ -1,8 +1,6 @@
1const common = @import("./common.zig");1const common = @import("./common.zig");
2const addf3 = @import("./addf3.zig").addf3;2const addf3 = @import("./addf3.zig").addf3;
33
4pub const panic = common.panic;
5
6comptime {4comptime {
7 if (common.want_ppc_abi) {5 if (common.want_ppc_abi) {
8 @export(&__addtf3, .{ .name = "__addkf3", .linkage = common.linkage, .visibility = common.visibility });6 @export(&__addtf3, .{ .name = "__addkf3", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/addvdi3.zig-2
...@@ -1,8 +1,6 @@...@@ -1,8 +1,6 @@
1const common = @import("./common.zig");1const common = @import("./common.zig");
2const testing = @import("std").testing;2const testing = @import("std").testing;
33
4pub const panic = common.panic;
5
6comptime {4comptime {
7 @export(&__addvdi3, .{ .name = "__addvdi3", .linkage = common.linkage, .visibility = common.visibility });5 @export(&__addvdi3, .{ .name = "__addvdi3", .linkage = common.linkage, .visibility = common.visibility });
8}6}
lib/compiler_rt/addvsi3.zig-2
...@@ -1,8 +1,6 @@...@@ -1,8 +1,6 @@
1const common = @import("./common.zig");1const common = @import("./common.zig");
2const testing = @import("std").testing;2const testing = @import("std").testing;
33
4pub const panic = common.panic;
5
6comptime {4comptime {
7 @export(&__addvsi3, .{ .name = "__addvsi3", .linkage = common.linkage, .visibility = common.visibility });5 @export(&__addvsi3, .{ .name = "__addvsi3", .linkage = common.linkage, .visibility = common.visibility });
8}6}
lib/compiler_rt/addxf3.zig-2
...@@ -1,8 +1,6 @@...@@ -1,8 +1,6 @@
1const common = @import("./common.zig");1const common = @import("./common.zig");
2const addf3 = @import("./addf3.zig").addf3;2const addf3 = @import("./addf3.zig").addf3;
33
4pub const panic = common.panic;
5
6comptime {4comptime {
7 @export(&__addxf3, .{ .name = "__addxf3", .linkage = common.linkage, .visibility = common.visibility });5 @export(&__addxf3, .{ .name = "__addxf3", .linkage = common.linkage, .visibility = common.visibility });
8}6}
lib/compiler_rt/arm.zig-2
...@@ -6,8 +6,6 @@ const target = builtin.target;...@@ -6,8 +6,6 @@ const target = builtin.target;
6const arch = builtin.cpu.arch;6const arch = builtin.cpu.arch;
7const common = @import("common.zig");7const common = @import("common.zig");
88
9pub const panic = common.panic;
10
11comptime {9comptime {
12 if (!builtin.is_test) {10 if (!builtin.is_test) {
13 if (arch.isArm()) {11 if (arch.isArm()) {
lib/compiler_rt/atomics.zig-1
...@@ -5,7 +5,6 @@ const cpu = builtin.cpu;...@@ -5,7 +5,6 @@ const cpu = builtin.cpu;
5const arch = cpu.arch;5const arch = cpu.arch;
6const linkage = common.linkage;6const linkage = common.linkage;
7const visibility = common.visibility;7const visibility = common.visibility;
8pub const panic = common.panic;
98
10// This parameter is true iff the target architecture supports the bare minimum9// This parameter is true iff the target architecture supports the bare minimum
11// to implement the atomic load/store intrinsics.10// to implement the atomic load/store intrinsics.
lib/compiler_rt/aulldiv.zig-2
...@@ -5,8 +5,6 @@ const os = builtin.os.tag;...@@ -5,8 +5,6 @@ const os = builtin.os.tag;
5const abi = builtin.abi;5const abi = builtin.abi;
6const common = @import("common.zig");6const common = @import("common.zig");
77
8pub const panic = common.panic;
9
10comptime {8comptime {
11 if (common.want_windows_x86_msvc_abi) {9 if (common.want_windows_x86_msvc_abi) {
12 // Don't let LLVM apply the stdcall name mangling on those MSVC builtins10 // Don't let LLVM apply the stdcall name mangling on those MSVC builtins
lib/compiler_rt/aullrem.zig-2
...@@ -5,8 +5,6 @@ const os = builtin.os.tag;...@@ -5,8 +5,6 @@ const os = builtin.os.tag;
5const abi = builtin.abi;5const abi = builtin.abi;
6const common = @import("common.zig");6const common = @import("common.zig");
77
8pub const panic = common.panic;
9
10comptime {8comptime {
11 if (common.want_windows_x86_msvc_abi) {9 if (common.want_windows_x86_msvc_abi) {
12 // Don't let LLVM apply the stdcall name mangling on those MSVC builtins10 // Don't let LLVM apply the stdcall name mangling on those MSVC builtins
lib/compiler_rt/bitreverse.zig-2
...@@ -2,8 +2,6 @@ const std = @import("std");...@@ -2,8 +2,6 @@ const std = @import("std");
2const builtin = @import("builtin");2const builtin = @import("builtin");
3const common = @import("common.zig");3const common = @import("common.zig");
44
5pub const panic = common.panic;
6
7comptime {5comptime {
8 @export(&__bitreversesi2, .{ .name = "__bitreversesi2", .linkage = common.linkage, .visibility = common.visibility });6 @export(&__bitreversesi2, .{ .name = "__bitreversesi2", .linkage = common.linkage, .visibility = common.visibility });
9 @export(&__bitreversedi2, .{ .name = "__bitreversedi2", .linkage = common.linkage, .visibility = common.visibility });7 @export(&__bitreversedi2, .{ .name = "__bitreversedi2", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/bswap.zig-2
...@@ -2,8 +2,6 @@ const std = @import("std");...@@ -2,8 +2,6 @@ const std = @import("std");
2const builtin = @import("builtin");2const builtin = @import("builtin");
3const common = @import("common.zig");3const common = @import("common.zig");
44
5pub const panic = common.panic;
6
7comptime {5comptime {
8 @export(&__bswapsi2, .{ .name = "__bswapsi2", .linkage = common.linkage, .visibility = common.visibility });6 @export(&__bswapsi2, .{ .name = "__bswapsi2", .linkage = common.linkage, .visibility = common.visibility });
9 @export(&__bswapdi2, .{ .name = "__bswapdi2", .linkage = common.linkage, .visibility = common.visibility });7 @export(&__bswapdi2, .{ .name = "__bswapdi2", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/ceil.zig-2
...@@ -13,8 +13,6 @@ const mem = std.mem;...@@ -13,8 +13,6 @@ const mem = std.mem;
13const expect = std.testing.expect;13const expect = std.testing.expect;
14const common = @import("common.zig");14const common = @import("common.zig");
1515
16pub const panic = common.panic;
17
18comptime {16comptime {
19 @export(&__ceilh, .{ .name = "__ceilh", .linkage = common.linkage, .visibility = common.visibility });17 @export(&__ceilh, .{ .name = "__ceilh", .linkage = common.linkage, .visibility = common.visibility });
20 @export(&ceilf, .{ .name = "ceilf", .linkage = common.linkage, .visibility = common.visibility });18 @export(&ceilf, .{ .name = "ceilf", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/clear_cache.zig-1
...@@ -3,7 +3,6 @@ const builtin = @import("builtin");...@@ -3,7 +3,6 @@ const builtin = @import("builtin");
3const arch = builtin.cpu.arch;3const arch = builtin.cpu.arch;
4const os = builtin.os.tag;4const os = builtin.os.tag;
5const common = @import("common.zig");5const common = @import("common.zig");
6pub const panic = common.panic;
76
8// Ported from llvm-project d32170dbd5b0d54436537b6b75beaf44324e0c287// Ported from llvm-project d32170dbd5b0d54436537b6b75beaf44324e0c28
98
lib/compiler_rt/cmp.zig-2
...@@ -2,8 +2,6 @@ const std = @import("std");...@@ -2,8 +2,6 @@ const std = @import("std");
2const builtin = @import("builtin");2const builtin = @import("builtin");
3const common = @import("common.zig");3const common = @import("common.zig");
44
5pub const panic = common.panic;
6
7comptime {5comptime {
8 @export(&__cmpsi2, .{ .name = "__cmpsi2", .linkage = common.linkage, .visibility = common.visibility });6 @export(&__cmpsi2, .{ .name = "__cmpsi2", .linkage = common.linkage, .visibility = common.visibility });
9 @export(&__cmpdi2, .{ .name = "__cmpdi2", .linkage = common.linkage, .visibility = common.visibility });7 @export(&__cmpdi2, .{ .name = "__cmpdi2", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/cmpdf2.zig-2
...@@ -3,8 +3,6 @@...@@ -3,8 +3,6 @@
3const common = @import("./common.zig");3const common = @import("./common.zig");
4const comparef = @import("./comparef.zig");4const comparef = @import("./comparef.zig");
55
6pub const panic = common.panic;
7
8comptime {6comptime {
9 if (common.want_aeabi) {7 if (common.want_aeabi) {
10 @export(&__aeabi_dcmpeq, .{ .name = "__aeabi_dcmpeq", .linkage = common.linkage, .visibility = common.visibility });8 @export(&__aeabi_dcmpeq, .{ .name = "__aeabi_dcmpeq", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/cmphf2.zig-2
...@@ -3,8 +3,6 @@...@@ -3,8 +3,6 @@
3const common = @import("./common.zig");3const common = @import("./common.zig");
4const comparef = @import("./comparef.zig");4const comparef = @import("./comparef.zig");
55
6pub const panic = common.panic;
7
8comptime {6comptime {
9 @export(&__eqhf2, .{ .name = "__eqhf2", .linkage = common.linkage, .visibility = common.visibility });7 @export(&__eqhf2, .{ .name = "__eqhf2", .linkage = common.linkage, .visibility = common.visibility });
10 @export(&__nehf2, .{ .name = "__nehf2", .linkage = common.linkage, .visibility = common.visibility });8 @export(&__nehf2, .{ .name = "__nehf2", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/cmpsf2.zig-2
...@@ -3,8 +3,6 @@...@@ -3,8 +3,6 @@
3const common = @import("./common.zig");3const common = @import("./common.zig");
4const comparef = @import("./comparef.zig");4const comparef = @import("./comparef.zig");
55
6pub const panic = common.panic;
7
8comptime {6comptime {
9 if (common.want_aeabi) {7 if (common.want_aeabi) {
10 @export(&__aeabi_fcmpeq, .{ .name = "__aeabi_fcmpeq", .linkage = common.linkage, .visibility = common.visibility });8 @export(&__aeabi_fcmpeq, .{ .name = "__aeabi_fcmpeq", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/cmptf2.zig-2
...@@ -3,8 +3,6 @@...@@ -3,8 +3,6 @@
3const common = @import("./common.zig");3const common = @import("./common.zig");
4const comparef = @import("./comparef.zig");4const comparef = @import("./comparef.zig");
55
6pub const panic = common.panic;
7
8comptime {6comptime {
9 if (common.want_ppc_abi) {7 if (common.want_ppc_abi) {
10 @export(&__eqtf2, .{ .name = "__eqkf2", .linkage = common.linkage, .visibility = common.visibility });8 @export(&__eqtf2, .{ .name = "__eqkf2", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/cmpxf2.zig-2
...@@ -3,8 +3,6 @@...@@ -3,8 +3,6 @@
3const common = @import("./common.zig");3const common = @import("./common.zig");
4const comparef = @import("./comparef.zig");4const comparef = @import("./comparef.zig");
55
6pub const panic = common.panic;
7
8comptime {6comptime {
9 @export(&__eqxf2, .{ .name = "__eqxf2", .linkage = common.linkage, .visibility = common.visibility });7 @export(&__eqxf2, .{ .name = "__eqxf2", .linkage = common.linkage, .visibility = common.visibility });
10 @export(&__nexf2, .{ .name = "__nexf2", .linkage = common.linkage, .visibility = common.visibility });8 @export(&__nexf2, .{ .name = "__nexf2", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/common.zig-4
...@@ -126,10 +126,6 @@ pub const test_safety = switch (builtin.zig_backend) {...@@ -126,10 +126,6 @@ pub const test_safety = switch (builtin.zig_backend) {
126 else => builtin.is_test,126 else => builtin.is_test,
127};127};
128128
129// Avoid dragging in the runtime safety mechanisms into this .o file, unless
130// we're trying to test compiler-rt.
131pub const panic = if (test_safety) std.debug.FullPanic(std.debug.defaultPanic) else std.debug.no_panic;
132
133/// This seems to mostly correspond to `clang::TargetInfo::HasFloat16`.129/// This seems to mostly correspond to `clang::TargetInfo::HasFloat16`.
134pub fn F16T(comptime OtherType: type) type {130pub fn F16T(comptime OtherType: type) type {
135 return switch (builtin.cpu.arch) {131 return switch (builtin.cpu.arch) {
lib/compiler_rt/cos.zig-2
...@@ -4,8 +4,6 @@ const mem = std.mem;...@@ -4,8 +4,6 @@ const mem = std.mem;
4const expect = std.testing.expect;4const expect = std.testing.expect;
5const common = @import("common.zig");5const common = @import("common.zig");
66
7pub const panic = common.panic;
8
9const trig = @import("trig.zig");7const trig = @import("trig.zig");
10const rem_pio2 = @import("rem_pio2.zig").rem_pio2;8const rem_pio2 = @import("rem_pio2.zig").rem_pio2;
11const rem_pio2f = @import("rem_pio2f.zig").rem_pio2f;9const rem_pio2f = @import("rem_pio2f.zig").rem_pio2f;
lib/compiler_rt/count0bits.zig-2
...@@ -2,8 +2,6 @@ const std = @import("std");...@@ -2,8 +2,6 @@ const std = @import("std");
2const builtin = @import("builtin");2const builtin = @import("builtin");
3const common = @import("common.zig");3const common = @import("common.zig");
44
5pub const panic = common.panic;
6
7comptime {5comptime {
8 @export(&__clzsi2, .{ .name = "__clzsi2", .linkage = common.linkage, .visibility = common.visibility });6 @export(&__clzsi2, .{ .name = "__clzsi2", .linkage = common.linkage, .visibility = common.visibility });
9 @export(&__clzdi2, .{ .name = "__clzdi2", .linkage = common.linkage, .visibility = common.visibility });7 @export(&__clzdi2, .{ .name = "__clzdi2", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/divdf3.zig-2
...@@ -10,8 +10,6 @@ const common = @import("common.zig");...@@ -10,8 +10,6 @@ const common = @import("common.zig");
10const normalize = common.normalize;10const normalize = common.normalize;
11const wideMultiply = common.wideMultiply;11const wideMultiply = common.wideMultiply;
1212
13pub const panic = common.panic;
14
15comptime {13comptime {
16 if (common.want_aeabi) {14 if (common.want_aeabi) {
17 @export(&__aeabi_ddiv, .{ .name = "__aeabi_ddiv", .linkage = common.linkage, .visibility = common.visibility });15 @export(&__aeabi_ddiv, .{ .name = "__aeabi_ddiv", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/divsf3.zig-2
...@@ -9,8 +9,6 @@ const arch = builtin.cpu.arch;...@@ -9,8 +9,6 @@ const arch = builtin.cpu.arch;
9const common = @import("common.zig");9const common = @import("common.zig");
10const normalize = common.normalize;10const normalize = common.normalize;
1111
12pub const panic = common.panic;
13
14comptime {12comptime {
15 if (common.want_aeabi) {13 if (common.want_aeabi) {
16 @export(&__aeabi_fdiv, .{ .name = "__aeabi_fdiv", .linkage = common.linkage, .visibility = common.visibility });14 @export(&__aeabi_fdiv, .{ .name = "__aeabi_fdiv", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/divtf3.zig-2
...@@ -5,8 +5,6 @@ const common = @import("common.zig");...@@ -5,8 +5,6 @@ const common = @import("common.zig");
5const normalize = common.normalize;5const normalize = common.normalize;
6const wideMultiply = common.wideMultiply;6const wideMultiply = common.wideMultiply;
77
8pub const panic = common.panic;
9
10comptime {8comptime {
11 if (common.want_ppc_abi) {9 if (common.want_ppc_abi) {
12 @export(&__divtf3, .{ .name = "__divkf3", .linkage = common.linkage, .visibility = common.visibility });10 @export(&__divtf3, .{ .name = "__divkf3", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/divti3.zig-2
...@@ -4,8 +4,6 @@ const udivmod = @import("udivmod.zig").udivmod;...@@ -4,8 +4,6 @@ const udivmod = @import("udivmod.zig").udivmod;
4const arch = builtin.cpu.arch;4const arch = builtin.cpu.arch;
5const common = @import("common.zig");5const common = @import("common.zig");
66
7pub const panic = common.panic;
8
9comptime {7comptime {
10 if (common.want_windows_v2u64_abi) {8 if (common.want_windows_v2u64_abi) {
11 @export(&__divti3_windows_x86_64, .{ .name = "__divti3", .linkage = common.linkage, .visibility = common.visibility });9 @export(&__divti3_windows_x86_64, .{ .name = "__divti3", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/divxf3.zig-2
...@@ -6,8 +6,6 @@ const common = @import("common.zig");...@@ -6,8 +6,6 @@ const common = @import("common.zig");
6const normalize = common.normalize;6const normalize = common.normalize;
7const wideMultiply = common.wideMultiply;7const wideMultiply = common.wideMultiply;
88
9pub const panic = common.panic;
10
11comptime {9comptime {
12 @export(&__divxf3, .{ .name = "__divxf3", .linkage = common.linkage, .visibility = common.visibility });10 @export(&__divxf3, .{ .name = "__divxf3", .linkage = common.linkage, .visibility = common.visibility });
13}11}
lib/compiler_rt/emutls.zig-2
...@@ -15,8 +15,6 @@ const expect = std.testing.expect;...@@ -15,8 +15,6 @@ const expect = std.testing.expect;
15/// typedef unsigned int gcc_word __attribute__((mode(word)));15/// typedef unsigned int gcc_word __attribute__((mode(word)));
16const gcc_word = usize;16const gcc_word = usize;
1717
18pub const panic = common.panic;
19
20comptime {18comptime {
21 if (builtin.link_libc and (builtin.abi.isAndroid() or builtin.abi.isOpenHarmony() or builtin.os.tag == .openbsd)) {19 if (builtin.link_libc and (builtin.abi.isAndroid() or builtin.abi.isOpenHarmony() or builtin.os.tag == .openbsd)) {
22 @export(&__emutls_get_address, .{ .name = "__emutls_get_address", .linkage = common.linkage, .visibility = common.visibility });20 @export(&__emutls_get_address, .{ .name = "__emutls_get_address", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/exp.zig-2
...@@ -13,8 +13,6 @@ const expect = std.testing.expect;...@@ -13,8 +13,6 @@ const expect = std.testing.expect;
13const expectEqual = std.testing.expectEqual;13const expectEqual = std.testing.expectEqual;
14const common = @import("common.zig");14const common = @import("common.zig");
1515
16pub const panic = common.panic;
17
18comptime {16comptime {
19 @export(&__exph, .{ .name = "__exph", .linkage = common.linkage, .visibility = common.visibility });17 @export(&__exph, .{ .name = "__exph", .linkage = common.linkage, .visibility = common.visibility });
20 @export(&expf, .{ .name = "expf", .linkage = common.linkage, .visibility = common.visibility });18 @export(&expf, .{ .name = "expf", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/exp2.zig-2
...@@ -13,8 +13,6 @@ const expect = std.testing.expect;...@@ -13,8 +13,6 @@ const expect = std.testing.expect;
13const expectEqual = std.testing.expectEqual;13const expectEqual = std.testing.expectEqual;
14const common = @import("common.zig");14const common = @import("common.zig");
1515
16pub const panic = common.panic;
17
18comptime {16comptime {
19 @export(&__exp2h, .{ .name = "__exp2h", .linkage = common.linkage, .visibility = common.visibility });17 @export(&__exp2h, .{ .name = "__exp2h", .linkage = common.linkage, .visibility = common.visibility });
20 @export(&exp2f, .{ .name = "exp2f", .linkage = common.linkage, .visibility = common.visibility });18 @export(&exp2f, .{ .name = "exp2f", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/extenddftf2.zig-2
...@@ -1,8 +1,6 @@...@@ -1,8 +1,6 @@
1const common = @import("./common.zig");1const common = @import("./common.zig");
2const extendf = @import("./extendf.zig").extendf;2const extendf = @import("./extendf.zig").extendf;
33
4pub const panic = common.panic;
5
6comptime {4comptime {
7 if (common.want_ppc_abi) {5 if (common.want_ppc_abi) {
8 @export(&__extenddftf2, .{ .name = "__extenddfkf2", .linkage = common.linkage, .visibility = common.visibility });6 @export(&__extenddftf2, .{ .name = "__extenddfkf2", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/extenddfxf2.zig-2
...@@ -1,8 +1,6 @@...@@ -1,8 +1,6 @@
1const common = @import("./common.zig");1const common = @import("./common.zig");
2const extend_f80 = @import("./extendf.zig").extend_f80;2const extend_f80 = @import("./extendf.zig").extend_f80;
33
4pub const panic = common.panic;
5
6comptime {4comptime {
7 @export(&__extenddfxf2, .{ .name = "__extenddfxf2", .linkage = common.linkage, .visibility = common.visibility });5 @export(&__extenddfxf2, .{ .name = "__extenddfxf2", .linkage = common.linkage, .visibility = common.visibility });
8}6}
lib/compiler_rt/extendhfdf2.zig-2
...@@ -1,8 +1,6 @@...@@ -1,8 +1,6 @@
1const common = @import("./common.zig");1const common = @import("./common.zig");
2const extendf = @import("./extendf.zig").extendf;2const extendf = @import("./extendf.zig").extendf;
33
4pub const panic = common.panic;
5
6comptime {4comptime {
7 @export(&__extendhfdf2, .{ .name = "__extendhfdf2", .linkage = common.linkage, .visibility = common.visibility });5 @export(&__extendhfdf2, .{ .name = "__extendhfdf2", .linkage = common.linkage, .visibility = common.visibility });
8}6}
lib/compiler_rt/extendhfsf2.zig-2
...@@ -1,8 +1,6 @@...@@ -1,8 +1,6 @@
1const common = @import("./common.zig");1const common = @import("./common.zig");
2const extendf = @import("./extendf.zig").extendf;2const extendf = @import("./extendf.zig").extendf;
33
4pub const panic = common.panic;
5
6comptime {4comptime {
7 if (common.gnu_f16_abi) {5 if (common.gnu_f16_abi) {
8 @export(&__gnu_h2f_ieee, .{ .name = "__gnu_h2f_ieee", .linkage = common.linkage, .visibility = common.visibility });6 @export(&__gnu_h2f_ieee, .{ .name = "__gnu_h2f_ieee", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/extendhftf2.zig-2
...@@ -1,8 +1,6 @@...@@ -1,8 +1,6 @@
1const common = @import("./common.zig");1const common = @import("./common.zig");
2const extendf = @import("./extendf.zig").extendf;2const extendf = @import("./extendf.zig").extendf;
33
4pub const panic = common.panic;
5
6comptime {4comptime {
7 @export(&__extendhftf2, .{ .name = "__extendhftf2", .linkage = common.linkage, .visibility = common.visibility });5 @export(&__extendhftf2, .{ .name = "__extendhftf2", .linkage = common.linkage, .visibility = common.visibility });
8}6}
lib/compiler_rt/extendhfxf2.zig-2
...@@ -1,8 +1,6 @@...@@ -1,8 +1,6 @@
1const common = @import("./common.zig");1const common = @import("./common.zig");
2const extend_f80 = @import("./extendf.zig").extend_f80;2const extend_f80 = @import("./extendf.zig").extend_f80;
33
4pub const panic = common.panic;
5
6comptime {4comptime {
7 @export(&__extendhfxf2, .{ .name = "__extendhfxf2", .linkage = common.linkage, .visibility = common.visibility });5 @export(&__extendhfxf2, .{ .name = "__extendhfxf2", .linkage = common.linkage, .visibility = common.visibility });
8}6}
lib/compiler_rt/extendsfdf2.zig-2
...@@ -1,8 +1,6 @@...@@ -1,8 +1,6 @@
1const common = @import("./common.zig");1const common = @import("./common.zig");
2const extendf = @import("./extendf.zig").extendf;2const extendf = @import("./extendf.zig").extendf;
33
4pub const panic = common.panic;
5
6comptime {4comptime {
7 if (common.want_aeabi) {5 if (common.want_aeabi) {
8 @export(&__aeabi_f2d, .{ .name = "__aeabi_f2d", .linkage = common.linkage, .visibility = common.visibility });6 @export(&__aeabi_f2d, .{ .name = "__aeabi_f2d", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/extendsftf2.zig-2
...@@ -1,8 +1,6 @@...@@ -1,8 +1,6 @@
1const common = @import("./common.zig");1const common = @import("./common.zig");
2const extendf = @import("./extendf.zig").extendf;2const extendf = @import("./extendf.zig").extendf;
33
4pub const panic = common.panic;
5
6comptime {4comptime {
7 if (common.want_ppc_abi) {5 if (common.want_ppc_abi) {
8 @export(&__extendsftf2, .{ .name = "__extendsfkf2", .linkage = common.linkage, .visibility = common.visibility });6 @export(&__extendsftf2, .{ .name = "__extendsfkf2", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/extendsfxf2.zig-2
...@@ -1,8 +1,6 @@...@@ -1,8 +1,6 @@
1const common = @import("./common.zig");1const common = @import("./common.zig");
2const extend_f80 = @import("./extendf.zig").extend_f80;2const extend_f80 = @import("./extendf.zig").extend_f80;
33
4pub const panic = common.panic;
5
6comptime {4comptime {
7 @export(&__extendsfxf2, .{ .name = "__extendsfxf2", .linkage = common.linkage, .visibility = common.visibility });5 @export(&__extendsfxf2, .{ .name = "__extendsfxf2", .linkage = common.linkage, .visibility = common.visibility });
8}6}
lib/compiler_rt/extendxftf2.zig-2
...@@ -1,8 +1,6 @@...@@ -1,8 +1,6 @@
1const std = @import("std");1const std = @import("std");
2const common = @import("./common.zig");2const common = @import("./common.zig");
33
4pub const panic = common.panic;
5
6comptime {4comptime {
7 @export(&__extendxftf2, .{ .name = "__extendxftf2", .linkage = common.linkage, .visibility = common.visibility });5 @export(&__extendxftf2, .{ .name = "__extendxftf2", .linkage = common.linkage, .visibility = common.visibility });
8}6}
lib/compiler_rt/fabs.zig-2
...@@ -3,8 +3,6 @@ const builtin = @import("builtin");...@@ -3,8 +3,6 @@ const builtin = @import("builtin");
3const arch = builtin.cpu.arch;3const arch = builtin.cpu.arch;
4const common = @import("common.zig");4const common = @import("common.zig");
55
6pub const panic = common.panic;
7
8comptime {6comptime {
9 @export(&__fabsh, .{ .name = "__fabsh", .linkage = common.linkage, .visibility = common.visibility });7 @export(&__fabsh, .{ .name = "__fabsh", .linkage = common.linkage, .visibility = common.visibility });
10 @export(&fabsf, .{ .name = "fabsf", .linkage = common.linkage, .visibility = common.visibility });8 @export(&fabsf, .{ .name = "fabsf", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/fixdfdi.zig-2
...@@ -2,8 +2,6 @@ const builtin = @import("builtin");...@@ -2,8 +2,6 @@ const builtin = @import("builtin");
2const common = @import("./common.zig");2const common = @import("./common.zig");
3const intFromFloat = @import("./int_from_float.zig").intFromFloat;3const intFromFloat = @import("./int_from_float.zig").intFromFloat;
44
5pub const panic = common.panic;
6
7comptime {5comptime {
8 if (common.want_aeabi) {6 if (common.want_aeabi) {
9 @export(&__aeabi_d2lz, .{ .name = "__aeabi_d2lz", .linkage = common.linkage, .visibility = common.visibility });7 @export(&__aeabi_d2lz, .{ .name = "__aeabi_d2lz", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/fixdfei.zig-2
...@@ -3,8 +3,6 @@ const builtin = @import("builtin");...@@ -3,8 +3,6 @@ const builtin = @import("builtin");
3const common = @import("common.zig");3const common = @import("common.zig");
4const bigIntFromFloat = @import("int_from_float.zig").bigIntFromFloat;4const bigIntFromFloat = @import("int_from_float.zig").bigIntFromFloat;
55
6pub const panic = common.panic;
7
8comptime {6comptime {
9 @export(&__fixdfei, .{ .name = "__fixdfei", .linkage = common.linkage, .visibility = common.visibility });7 @export(&__fixdfei, .{ .name = "__fixdfei", .linkage = common.linkage, .visibility = common.visibility });
10}8}
lib/compiler_rt/fixdfsi.zig-2
...@@ -1,8 +1,6 @@...@@ -1,8 +1,6 @@
1const common = @import("./common.zig");1const common = @import("./common.zig");
2const intFromFloat = @import("./int_from_float.zig").intFromFloat;2const intFromFloat = @import("./int_from_float.zig").intFromFloat;
33
4pub const panic = common.panic;
5
6comptime {4comptime {
7 if (common.want_aeabi) {5 if (common.want_aeabi) {
8 @export(&__aeabi_d2iz, .{ .name = "__aeabi_d2iz", .linkage = common.linkage, .visibility = common.visibility });6 @export(&__aeabi_d2iz, .{ .name = "__aeabi_d2iz", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/fixdfti.zig-2
...@@ -2,8 +2,6 @@ const builtin = @import("builtin");...@@ -2,8 +2,6 @@ const builtin = @import("builtin");
2const common = @import("./common.zig");2const common = @import("./common.zig");
3const intFromFloat = @import("./int_from_float.zig").intFromFloat;3const intFromFloat = @import("./int_from_float.zig").intFromFloat;
44
5pub const panic = common.panic;
6
7comptime {5comptime {
8 if (common.want_windows_v2u64_abi) {6 if (common.want_windows_v2u64_abi) {
9 @export(&__fixdfti_windows_x86_64, .{ .name = "__fixdfti", .linkage = common.linkage, .visibility = common.visibility });7 @export(&__fixdfti_windows_x86_64, .{ .name = "__fixdfti", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/fixhfdi.zig-2
...@@ -1,8 +1,6 @@...@@ -1,8 +1,6 @@
1const common = @import("./common.zig");1const common = @import("./common.zig");
2const intFromFloat = @import("./int_from_float.zig").intFromFloat;2const intFromFloat = @import("./int_from_float.zig").intFromFloat;
33
4pub const panic = common.panic;
5
6comptime {4comptime {
7 @export(&__fixhfdi, .{ .name = "__fixhfdi", .linkage = common.linkage, .visibility = common.visibility });5 @export(&__fixhfdi, .{ .name = "__fixhfdi", .linkage = common.linkage, .visibility = common.visibility });
8}6}
lib/compiler_rt/fixhfei.zig-2
...@@ -3,8 +3,6 @@ const builtin = @import("builtin");...@@ -3,8 +3,6 @@ const builtin = @import("builtin");
3const common = @import("common.zig");3const common = @import("common.zig");
4const bigIntFromFloat = @import("int_from_float.zig").bigIntFromFloat;4const bigIntFromFloat = @import("int_from_float.zig").bigIntFromFloat;
55
6pub const panic = common.panic;
7
8comptime {6comptime {
9 @export(&__fixhfei, .{ .name = "__fixhfei", .linkage = common.linkage, .visibility = common.visibility });7 @export(&__fixhfei, .{ .name = "__fixhfei", .linkage = common.linkage, .visibility = common.visibility });
10}8}
lib/compiler_rt/fixhfsi.zig-2
...@@ -1,8 +1,6 @@...@@ -1,8 +1,6 @@
1const common = @import("./common.zig");1const common = @import("./common.zig");
2const intFromFloat = @import("./int_from_float.zig").intFromFloat;2const intFromFloat = @import("./int_from_float.zig").intFromFloat;
33
4pub const panic = common.panic;
5
6comptime {4comptime {
7 @export(&__fixhfsi, .{ .name = "__fixhfsi", .linkage = common.linkage, .visibility = common.visibility });5 @export(&__fixhfsi, .{ .name = "__fixhfsi", .linkage = common.linkage, .visibility = common.visibility });
8}6}
lib/compiler_rt/fixhfti.zig-2
...@@ -2,8 +2,6 @@ const builtin = @import("builtin");...@@ -2,8 +2,6 @@ const builtin = @import("builtin");
2const common = @import("./common.zig");2const common = @import("./common.zig");
3const intFromFloat = @import("./int_from_float.zig").intFromFloat;3const intFromFloat = @import("./int_from_float.zig").intFromFloat;
44
5pub const panic = common.panic;
6
7comptime {5comptime {
8 if (common.want_windows_v2u64_abi) {6 if (common.want_windows_v2u64_abi) {
9 @export(&__fixhfti_windows_x86_64, .{ .name = "__fixhfti", .linkage = common.linkage, .visibility = common.visibility });7 @export(&__fixhfti_windows_x86_64, .{ .name = "__fixhfti", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/fixsfdi.zig-2
...@@ -2,8 +2,6 @@ const builtin = @import("builtin");...@@ -2,8 +2,6 @@ const builtin = @import("builtin");
2const common = @import("./common.zig");2const common = @import("./common.zig");
3const intFromFloat = @import("./int_from_float.zig").intFromFloat;3const intFromFloat = @import("./int_from_float.zig").intFromFloat;
44
5pub const panic = common.panic;
6
7comptime {5comptime {
8 if (common.want_aeabi) {6 if (common.want_aeabi) {
9 @export(&__aeabi_f2lz, .{ .name = "__aeabi_f2lz", .linkage = common.linkage, .visibility = common.visibility });7 @export(&__aeabi_f2lz, .{ .name = "__aeabi_f2lz", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/fixsfei.zig-2
...@@ -3,8 +3,6 @@ const builtin = @import("builtin");...@@ -3,8 +3,6 @@ const builtin = @import("builtin");
3const common = @import("common.zig");3const common = @import("common.zig");
4const bigIntFromFloat = @import("int_from_float.zig").bigIntFromFloat;4const bigIntFromFloat = @import("int_from_float.zig").bigIntFromFloat;
55
6pub const panic = common.panic;
7
8comptime {6comptime {
9 @export(&__fixsfei, .{ .name = "__fixsfei", .linkage = common.linkage, .visibility = common.visibility });7 @export(&__fixsfei, .{ .name = "__fixsfei", .linkage = common.linkage, .visibility = common.visibility });
10}8}
lib/compiler_rt/fixsfsi.zig-2
...@@ -1,8 +1,6 @@...@@ -1,8 +1,6 @@
1const common = @import("./common.zig");1const common = @import("./common.zig");
2const intFromFloat = @import("./int_from_float.zig").intFromFloat;2const intFromFloat = @import("./int_from_float.zig").intFromFloat;
33
4pub const panic = common.panic;
5
6comptime {4comptime {
7 if (common.want_aeabi) {5 if (common.want_aeabi) {
8 @export(&__aeabi_f2iz, .{ .name = "__aeabi_f2iz", .linkage = common.linkage, .visibility = common.visibility });6 @export(&__aeabi_f2iz, .{ .name = "__aeabi_f2iz", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/fixsfti.zig-2
...@@ -2,8 +2,6 @@ const builtin = @import("builtin");...@@ -2,8 +2,6 @@ const builtin = @import("builtin");
2const common = @import("./common.zig");2const common = @import("./common.zig");
3const intFromFloat = @import("./int_from_float.zig").intFromFloat;3const intFromFloat = @import("./int_from_float.zig").intFromFloat;
44
5pub const panic = common.panic;
6
7comptime {5comptime {
8 if (common.want_windows_v2u64_abi) {6 if (common.want_windows_v2u64_abi) {
9 @export(&__fixsfti_windows_x86_64, .{ .name = "__fixsfti", .linkage = common.linkage, .visibility = common.visibility });7 @export(&__fixsfti_windows_x86_64, .{ .name = "__fixsfti", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/fixtfdi.zig-2
...@@ -1,8 +1,6 @@...@@ -1,8 +1,6 @@
1const common = @import("./common.zig");1const common = @import("./common.zig");
2const intFromFloat = @import("./int_from_float.zig").intFromFloat;2const intFromFloat = @import("./int_from_float.zig").intFromFloat;
33
4pub const panic = common.panic;
5
6comptime {4comptime {
7 if (common.want_ppc_abi) {5 if (common.want_ppc_abi) {
8 @export(&__fixtfdi, .{ .name = "__fixkfdi", .linkage = common.linkage, .visibility = common.visibility });6 @export(&__fixtfdi, .{ .name = "__fixkfdi", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/fixtfei.zig-2
...@@ -3,8 +3,6 @@ const builtin = @import("builtin");...@@ -3,8 +3,6 @@ const builtin = @import("builtin");
3const common = @import("common.zig");3const common = @import("common.zig");
4const bigIntFromFloat = @import("int_from_float.zig").bigIntFromFloat;4const bigIntFromFloat = @import("int_from_float.zig").bigIntFromFloat;
55
6pub const panic = common.panic;
7
8comptime {6comptime {
9 @export(&__fixtfei, .{ .name = "__fixtfei", .linkage = common.linkage, .visibility = common.visibility });7 @export(&__fixtfei, .{ .name = "__fixtfei", .linkage = common.linkage, .visibility = common.visibility });
10}8}
lib/compiler_rt/fixtfsi.zig-2
...@@ -1,8 +1,6 @@...@@ -1,8 +1,6 @@
1const common = @import("./common.zig");1const common = @import("./common.zig");
2const intFromFloat = @import("./int_from_float.zig").intFromFloat;2const intFromFloat = @import("./int_from_float.zig").intFromFloat;
33
4pub const panic = common.panic;
5
6comptime {4comptime {
7 if (common.want_ppc_abi) {5 if (common.want_ppc_abi) {
8 @export(&__fixtfsi, .{ .name = "__fixkfsi", .linkage = common.linkage, .visibility = common.visibility });6 @export(&__fixtfsi, .{ .name = "__fixkfsi", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/fixtfti.zig-2
...@@ -2,8 +2,6 @@ const builtin = @import("builtin");...@@ -2,8 +2,6 @@ const builtin = @import("builtin");
2const common = @import("./common.zig");2const common = @import("./common.zig");
3const intFromFloat = @import("./int_from_float.zig").intFromFloat;3const intFromFloat = @import("./int_from_float.zig").intFromFloat;
44
5pub const panic = common.panic;
6
7comptime {5comptime {
8 if (common.want_windows_v2u64_abi) {6 if (common.want_windows_v2u64_abi) {
9 @export(&__fixtfti_windows_x86_64, .{ .name = "__fixtfti", .linkage = common.linkage, .visibility = common.visibility });7 @export(&__fixtfti_windows_x86_64, .{ .name = "__fixtfti", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/fixunsdfdi.zig-2
...@@ -2,8 +2,6 @@ const builtin = @import("builtin");...@@ -2,8 +2,6 @@ const builtin = @import("builtin");
2const common = @import("./common.zig");2const common = @import("./common.zig");
3const intFromFloat = @import("./int_from_float.zig").intFromFloat;3const intFromFloat = @import("./int_from_float.zig").intFromFloat;
44
5pub const panic = common.panic;
6
7comptime {5comptime {
8 if (common.want_aeabi) {6 if (common.want_aeabi) {
9 @export(&__aeabi_d2ulz, .{ .name = "__aeabi_d2ulz", .linkage = common.linkage, .visibility = common.visibility });7 @export(&__aeabi_d2ulz, .{ .name = "__aeabi_d2ulz", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/fixunsdfei.zig-2
...@@ -3,8 +3,6 @@ const builtin = @import("builtin");...@@ -3,8 +3,6 @@ const builtin = @import("builtin");
3const common = @import("common.zig");3const common = @import("common.zig");
4const bigIntFromFloat = @import("int_from_float.zig").bigIntFromFloat;4const bigIntFromFloat = @import("int_from_float.zig").bigIntFromFloat;
55
6pub const panic = common.panic;
7
8comptime {6comptime {
9 @export(&__fixunsdfei, .{ .name = "__fixunsdfei", .linkage = common.linkage, .visibility = common.visibility });7 @export(&__fixunsdfei, .{ .name = "__fixunsdfei", .linkage = common.linkage, .visibility = common.visibility });
10}8}
lib/compiler_rt/fixunsdfsi.zig-2
...@@ -1,8 +1,6 @@...@@ -1,8 +1,6 @@
1const common = @import("./common.zig");1const common = @import("./common.zig");
2const intFromFloat = @import("./int_from_float.zig").intFromFloat;2const intFromFloat = @import("./int_from_float.zig").intFromFloat;
33
4pub const panic = common.panic;
5
6comptime {4comptime {
7 if (common.want_aeabi) {5 if (common.want_aeabi) {
8 @export(&__aeabi_d2uiz, .{ .name = "__aeabi_d2uiz", .linkage = common.linkage, .visibility = common.visibility });6 @export(&__aeabi_d2uiz, .{ .name = "__aeabi_d2uiz", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/fixunsdfti.zig-2
...@@ -2,8 +2,6 @@ const builtin = @import("builtin");...@@ -2,8 +2,6 @@ const builtin = @import("builtin");
2const common = @import("./common.zig");2const common = @import("./common.zig");
3const intFromFloat = @import("./int_from_float.zig").intFromFloat;3const intFromFloat = @import("./int_from_float.zig").intFromFloat;
44
5pub const panic = common.panic;
6
7comptime {5comptime {
8 if (common.want_windows_v2u64_abi) {6 if (common.want_windows_v2u64_abi) {
9 @export(&__fixunsdfti_windows_x86_64, .{ .name = "__fixunsdfti", .linkage = common.linkage, .visibility = common.visibility });7 @export(&__fixunsdfti_windows_x86_64, .{ .name = "__fixunsdfti", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/fixunshfdi.zig-2
...@@ -1,8 +1,6 @@...@@ -1,8 +1,6 @@
1const common = @import("./common.zig");1const common = @import("./common.zig");
2const intFromFloat = @import("./int_from_float.zig").intFromFloat;2const intFromFloat = @import("./int_from_float.zig").intFromFloat;
33
4pub const panic = common.panic;
5
6comptime {4comptime {
7 @export(&__fixunshfdi, .{ .name = "__fixunshfdi", .linkage = common.linkage, .visibility = common.visibility });5 @export(&__fixunshfdi, .{ .name = "__fixunshfdi", .linkage = common.linkage, .visibility = common.visibility });
8}6}
lib/compiler_rt/fixunshfei.zig-2
...@@ -3,8 +3,6 @@ const builtin = @import("builtin");...@@ -3,8 +3,6 @@ const builtin = @import("builtin");
3const common = @import("common.zig");3const common = @import("common.zig");
4const bigIntFromFloat = @import("int_from_float.zig").bigIntFromFloat;4const bigIntFromFloat = @import("int_from_float.zig").bigIntFromFloat;
55
6pub const panic = common.panic;
7
8comptime {6comptime {
9 @export(&__fixunshfei, .{ .name = "__fixunshfei", .linkage = common.linkage, .visibility = common.visibility });7 @export(&__fixunshfei, .{ .name = "__fixunshfei", .linkage = common.linkage, .visibility = common.visibility });
10}8}
lib/compiler_rt/fixunshfsi.zig-2
...@@ -1,8 +1,6 @@...@@ -1,8 +1,6 @@
1const common = @import("./common.zig");1const common = @import("./common.zig");
2const intFromFloat = @import("./int_from_float.zig").intFromFloat;2const intFromFloat = @import("./int_from_float.zig").intFromFloat;
33
4pub const panic = common.panic;
5
6comptime {4comptime {
7 @export(&__fixunshfsi, .{ .name = "__fixunshfsi", .linkage = common.linkage, .visibility = common.visibility });5 @export(&__fixunshfsi, .{ .name = "__fixunshfsi", .linkage = common.linkage, .visibility = common.visibility });
8}6}
lib/compiler_rt/fixunshfti.zig-2
...@@ -2,8 +2,6 @@ const builtin = @import("builtin");...@@ -2,8 +2,6 @@ const builtin = @import("builtin");
2const common = @import("./common.zig");2const common = @import("./common.zig");
3const intFromFloat = @import("./int_from_float.zig").intFromFloat;3const intFromFloat = @import("./int_from_float.zig").intFromFloat;
44
5pub const panic = common.panic;
6
7comptime {5comptime {
8 if (common.want_windows_v2u64_abi) {6 if (common.want_windows_v2u64_abi) {
9 @export(&__fixunshfti_windows_x86_64, .{ .name = "__fixunshfti", .linkage = common.linkage, .visibility = common.visibility });7 @export(&__fixunshfti_windows_x86_64, .{ .name = "__fixunshfti", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/fixunssfdi.zig-2
...@@ -2,8 +2,6 @@ const builtin = @import("builtin");...@@ -2,8 +2,6 @@ const builtin = @import("builtin");
2const common = @import("./common.zig");2const common = @import("./common.zig");
3const intFromFloat = @import("./int_from_float.zig").intFromFloat;3const intFromFloat = @import("./int_from_float.zig").intFromFloat;
44
5pub const panic = common.panic;
6
7comptime {5comptime {
8 if (common.want_aeabi) {6 if (common.want_aeabi) {
9 @export(&__aeabi_f2ulz, .{ .name = "__aeabi_f2ulz", .linkage = common.linkage, .visibility = common.visibility });7 @export(&__aeabi_f2ulz, .{ .name = "__aeabi_f2ulz", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/fixunssfei.zig-2
...@@ -3,8 +3,6 @@ const builtin = @import("builtin");...@@ -3,8 +3,6 @@ const builtin = @import("builtin");
3const common = @import("common.zig");3const common = @import("common.zig");
4const bigIntFromFloat = @import("int_from_float.zig").bigIntFromFloat;4const bigIntFromFloat = @import("int_from_float.zig").bigIntFromFloat;
55
6pub const panic = common.panic;
7
8comptime {6comptime {
9 @export(&__fixunssfei, .{ .name = "__fixunssfei", .linkage = common.linkage, .visibility = common.visibility });7 @export(&__fixunssfei, .{ .name = "__fixunssfei", .linkage = common.linkage, .visibility = common.visibility });
10}8}
lib/compiler_rt/fixunssfsi.zig-2
...@@ -1,8 +1,6 @@...@@ -1,8 +1,6 @@
1const common = @import("./common.zig");1const common = @import("./common.zig");
2const intFromFloat = @import("./int_from_float.zig").intFromFloat;2const intFromFloat = @import("./int_from_float.zig").intFromFloat;
33
4pub const panic = common.panic;
5
6comptime {4comptime {
7 if (common.want_aeabi) {5 if (common.want_aeabi) {
8 @export(&__aeabi_f2uiz, .{ .name = "__aeabi_f2uiz", .linkage = common.linkage, .visibility = common.visibility });6 @export(&__aeabi_f2uiz, .{ .name = "__aeabi_f2uiz", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/fixunssfti.zig-2
...@@ -2,8 +2,6 @@ const builtin = @import("builtin");...@@ -2,8 +2,6 @@ const builtin = @import("builtin");
2const common = @import("./common.zig");2const common = @import("./common.zig");
3const intFromFloat = @import("./int_from_float.zig").intFromFloat;3const intFromFloat = @import("./int_from_float.zig").intFromFloat;
44
5pub const panic = common.panic;
6
7comptime {5comptime {
8 if (common.want_windows_v2u64_abi) {6 if (common.want_windows_v2u64_abi) {
9 @export(&__fixunssfti_windows_x86_64, .{ .name = "__fixunssfti", .linkage = common.linkage, .visibility = common.visibility });7 @export(&__fixunssfti_windows_x86_64, .{ .name = "__fixunssfti", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/fixunstfdi.zig-2
...@@ -1,8 +1,6 @@...@@ -1,8 +1,6 @@
1const common = @import("./common.zig");1const common = @import("./common.zig");
2const intFromFloat = @import("./int_from_float.zig").intFromFloat;2const intFromFloat = @import("./int_from_float.zig").intFromFloat;
33
4pub const panic = common.panic;
5
6comptime {4comptime {
7 if (common.want_ppc_abi) {5 if (common.want_ppc_abi) {
8 @export(&__fixunstfdi, .{ .name = "__fixunskfdi", .linkage = common.linkage, .visibility = common.visibility });6 @export(&__fixunstfdi, .{ .name = "__fixunskfdi", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/fixunstfei.zig-2
...@@ -3,8 +3,6 @@ const builtin = @import("builtin");...@@ -3,8 +3,6 @@ const builtin = @import("builtin");
3const common = @import("common.zig");3const common = @import("common.zig");
4const bigIntFromFloat = @import("int_from_float.zig").bigIntFromFloat;4const bigIntFromFloat = @import("int_from_float.zig").bigIntFromFloat;
55
6pub const panic = common.panic;
7
8comptime {6comptime {
9 @export(&__fixunstfei, .{ .name = "__fixunstfei", .linkage = common.linkage, .visibility = common.visibility });7 @export(&__fixunstfei, .{ .name = "__fixunstfei", .linkage = common.linkage, .visibility = common.visibility });
10}8}
lib/compiler_rt/fixunstfsi.zig-2
...@@ -1,8 +1,6 @@...@@ -1,8 +1,6 @@
1const common = @import("./common.zig");1const common = @import("./common.zig");
2const intFromFloat = @import("./int_from_float.zig").intFromFloat;2const intFromFloat = @import("./int_from_float.zig").intFromFloat;
33
4pub const panic = common.panic;
5
6comptime {4comptime {
7 if (common.want_ppc_abi) {5 if (common.want_ppc_abi) {
8 @export(&__fixunstfsi, .{ .name = "__fixunskfsi", .linkage = common.linkage, .visibility = common.visibility });6 @export(&__fixunstfsi, .{ .name = "__fixunskfsi", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/fixunstfti.zig-2
...@@ -2,8 +2,6 @@ const builtin = @import("builtin");...@@ -2,8 +2,6 @@ const builtin = @import("builtin");
2const common = @import("./common.zig");2const common = @import("./common.zig");
3const intFromFloat = @import("./int_from_float.zig").intFromFloat;3const intFromFloat = @import("./int_from_float.zig").intFromFloat;
44
5pub const panic = common.panic;
6
7comptime {5comptime {
8 if (common.want_windows_v2u64_abi) {6 if (common.want_windows_v2u64_abi) {
9 @export(&__fixunstfti_windows_x86_64, .{ .name = "__fixunstfti", .linkage = common.linkage, .visibility = common.visibility });7 @export(&__fixunstfti_windows_x86_64, .{ .name = "__fixunstfti", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/fixunsxfdi.zig-2
...@@ -1,8 +1,6 @@...@@ -1,8 +1,6 @@
1const common = @import("./common.zig");1const common = @import("./common.zig");
2const intFromFloat = @import("./int_from_float.zig").intFromFloat;2const intFromFloat = @import("./int_from_float.zig").intFromFloat;
33
4pub const panic = common.panic;
5
6comptime {4comptime {
7 @export(&__fixunsxfdi, .{ .name = "__fixunsxfdi", .linkage = common.linkage, .visibility = common.visibility });5 @export(&__fixunsxfdi, .{ .name = "__fixunsxfdi", .linkage = common.linkage, .visibility = common.visibility });
8}6}
lib/compiler_rt/fixunsxfei.zig-2
...@@ -3,8 +3,6 @@ const builtin = @import("builtin");...@@ -3,8 +3,6 @@ const builtin = @import("builtin");
3const common = @import("common.zig");3const common = @import("common.zig");
4const bigIntFromFloat = @import("int_from_float.zig").bigIntFromFloat;4const bigIntFromFloat = @import("int_from_float.zig").bigIntFromFloat;
55
6pub const panic = common.panic;
7
8comptime {6comptime {
9 @export(&__fixunsxfei, .{ .name = "__fixunsxfei", .linkage = common.linkage, .visibility = common.visibility });7 @export(&__fixunsxfei, .{ .name = "__fixunsxfei", .linkage = common.linkage, .visibility = common.visibility });
10}8}
lib/compiler_rt/fixunsxfsi.zig-2
...@@ -1,8 +1,6 @@...@@ -1,8 +1,6 @@
1const common = @import("./common.zig");1const common = @import("./common.zig");
2const intFromFloat = @import("./int_from_float.zig").intFromFloat;2const intFromFloat = @import("./int_from_float.zig").intFromFloat;
33
4pub const panic = common.panic;
5
6comptime {4comptime {
7 @export(&__fixunsxfsi, .{ .name = "__fixunsxfsi", .linkage = common.linkage, .visibility = common.visibility });5 @export(&__fixunsxfsi, .{ .name = "__fixunsxfsi", .linkage = common.linkage, .visibility = common.visibility });
8}6}
lib/compiler_rt/fixunsxfti.zig-2
...@@ -2,8 +2,6 @@ const builtin = @import("builtin");...@@ -2,8 +2,6 @@ const builtin = @import("builtin");
2const common = @import("./common.zig");2const common = @import("./common.zig");
3const intFromFloat = @import("./int_from_float.zig").intFromFloat;3const intFromFloat = @import("./int_from_float.zig").intFromFloat;
44
5pub const panic = common.panic;
6
7comptime {5comptime {
8 if (common.want_windows_v2u64_abi) {6 if (common.want_windows_v2u64_abi) {
9 @export(&__fixunsxfti_windows_x86_64, .{ .name = "__fixunsxfti", .linkage = common.linkage, .visibility = common.visibility });7 @export(&__fixunsxfti_windows_x86_64, .{ .name = "__fixunsxfti", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/fixxfdi.zig-2
...@@ -1,8 +1,6 @@...@@ -1,8 +1,6 @@
1const common = @import("./common.zig");1const common = @import("./common.zig");
2const intFromFloat = @import("./int_from_float.zig").intFromFloat;2const intFromFloat = @import("./int_from_float.zig").intFromFloat;
33
4pub const panic = common.panic;
5
6comptime {4comptime {
7 @export(&__fixxfdi, .{ .name = "__fixxfdi", .linkage = common.linkage, .visibility = common.visibility });5 @export(&__fixxfdi, .{ .name = "__fixxfdi", .linkage = common.linkage, .visibility = common.visibility });
8}6}
lib/compiler_rt/fixxfei.zig-2
...@@ -3,8 +3,6 @@ const builtin = @import("builtin");...@@ -3,8 +3,6 @@ const builtin = @import("builtin");
3const common = @import("common.zig");3const common = @import("common.zig");
4const bigIntFromFloat = @import("int_from_float.zig").bigIntFromFloat;4const bigIntFromFloat = @import("int_from_float.zig").bigIntFromFloat;
55
6pub const panic = common.panic;
7
8comptime {6comptime {
9 @export(&__fixxfei, .{ .name = "__fixxfei", .linkage = common.linkage, .visibility = common.visibility });7 @export(&__fixxfei, .{ .name = "__fixxfei", .linkage = common.linkage, .visibility = common.visibility });
10}8}
lib/compiler_rt/fixxfsi.zig-2
...@@ -1,8 +1,6 @@...@@ -1,8 +1,6 @@
1const common = @import("./common.zig");1const common = @import("./common.zig");
2const intFromFloat = @import("./int_from_float.zig").intFromFloat;2const intFromFloat = @import("./int_from_float.zig").intFromFloat;
33
4pub const panic = common.panic;
5
6comptime {4comptime {
7 @export(&__fixxfsi, .{ .name = "__fixxfsi", .linkage = common.linkage, .visibility = common.visibility });5 @export(&__fixxfsi, .{ .name = "__fixxfsi", .linkage = common.linkage, .visibility = common.visibility });
8}6}
lib/compiler_rt/fixxfti.zig-2
...@@ -2,8 +2,6 @@ const builtin = @import("builtin");...@@ -2,8 +2,6 @@ const builtin = @import("builtin");
2const common = @import("./common.zig");2const common = @import("./common.zig");
3const intFromFloat = @import("./int_from_float.zig").intFromFloat;3const intFromFloat = @import("./int_from_float.zig").intFromFloat;
44
5pub const panic = common.panic;
6
7comptime {5comptime {
8 if (common.want_windows_v2u64_abi) {6 if (common.want_windows_v2u64_abi) {
9 @export(&__fixxfti_windows_x86_64, .{ .name = "__fixxfti", .linkage = common.linkage, .visibility = common.visibility });7 @export(&__fixxfti_windows_x86_64, .{ .name = "__fixxfti", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/floatdidf.zig-2
...@@ -2,8 +2,6 @@ const builtin = @import("builtin");...@@ -2,8 +2,6 @@ const builtin = @import("builtin");
2const common = @import("./common.zig");2const common = @import("./common.zig");
3const floatFromInt = @import("./float_from_int.zig").floatFromInt;3const floatFromInt = @import("./float_from_int.zig").floatFromInt;
44
5pub const panic = common.panic;
6
7comptime {5comptime {
8 if (common.want_aeabi) {6 if (common.want_aeabi) {
9 @export(&__aeabi_l2d, .{ .name = "__aeabi_l2d", .linkage = common.linkage, .visibility = common.visibility });7 @export(&__aeabi_l2d, .{ .name = "__aeabi_l2d", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/floatdihf.zig-2
...@@ -1,8 +1,6 @@...@@ -1,8 +1,6 @@
1const common = @import("./common.zig");1const common = @import("./common.zig");
2const floatFromInt = @import("./float_from_int.zig").floatFromInt;2const floatFromInt = @import("./float_from_int.zig").floatFromInt;
33
4pub const panic = common.panic;
5
6comptime {4comptime {
7 @export(&__floatdihf, .{ .name = "__floatdihf", .linkage = common.linkage, .visibility = common.visibility });5 @export(&__floatdihf, .{ .name = "__floatdihf", .linkage = common.linkage, .visibility = common.visibility });
8}6}
lib/compiler_rt/floatdisf.zig-2
...@@ -2,8 +2,6 @@ const builtin = @import("builtin");...@@ -2,8 +2,6 @@ const builtin = @import("builtin");
2const common = @import("./common.zig");2const common = @import("./common.zig");
3const floatFromInt = @import("./float_from_int.zig").floatFromInt;3const floatFromInt = @import("./float_from_int.zig").floatFromInt;
44
5pub const panic = common.panic;
6
7comptime {5comptime {
8 if (common.want_aeabi) {6 if (common.want_aeabi) {
9 @export(&__aeabi_l2f, .{ .name = "__aeabi_l2f", .linkage = common.linkage, .visibility = common.visibility });7 @export(&__aeabi_l2f, .{ .name = "__aeabi_l2f", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/floatditf.zig-2
...@@ -1,8 +1,6 @@...@@ -1,8 +1,6 @@
1const common = @import("./common.zig");1const common = @import("./common.zig");
2const floatFromInt = @import("./float_from_int.zig").floatFromInt;2const floatFromInt = @import("./float_from_int.zig").floatFromInt;
33
4pub const panic = common.panic;
5
6comptime {4comptime {
7 if (common.want_ppc_abi) {5 if (common.want_ppc_abi) {
8 @export(&__floatditf, .{ .name = "__floatdikf", .linkage = common.linkage, .visibility = common.visibility });6 @export(&__floatditf, .{ .name = "__floatdikf", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/floatdixf.zig-2
...@@ -1,8 +1,6 @@...@@ -1,8 +1,6 @@
1const common = @import("./common.zig");1const common = @import("./common.zig");
2const floatFromInt = @import("./float_from_int.zig").floatFromInt;2const floatFromInt = @import("./float_from_int.zig").floatFromInt;
33
4pub const panic = common.panic;
5
6comptime {4comptime {
7 @export(&__floatdixf, .{ .name = "__floatdixf", .linkage = common.linkage, .visibility = common.visibility });5 @export(&__floatdixf, .{ .name = "__floatdixf", .linkage = common.linkage, .visibility = common.visibility });
8}6}
lib/compiler_rt/floateidf.zig-2
...@@ -3,8 +3,6 @@ const builtin = @import("builtin");...@@ -3,8 +3,6 @@ const builtin = @import("builtin");
3const common = @import("common.zig");3const common = @import("common.zig");
4const floatFromBigInt = @import("float_from_int.zig").floatFromBigInt;4const floatFromBigInt = @import("float_from_int.zig").floatFromBigInt;
55
6pub const panic = common.panic;
7
8comptime {6comptime {
9 @export(&__floateidf, .{ .name = "__floateidf", .linkage = common.linkage, .visibility = common.visibility });7 @export(&__floateidf, .{ .name = "__floateidf", .linkage = common.linkage, .visibility = common.visibility });
10}8}
lib/compiler_rt/floateihf.zig-2
...@@ -3,8 +3,6 @@ const builtin = @import("builtin");...@@ -3,8 +3,6 @@ const builtin = @import("builtin");
3const common = @import("common.zig");3const common = @import("common.zig");
4const floatFromBigInt = @import("float_from_int.zig").floatFromBigInt;4const floatFromBigInt = @import("float_from_int.zig").floatFromBigInt;
55
6pub const panic = common.panic;
7
8comptime {6comptime {
9 @export(&__floateihf, .{ .name = "__floateihf", .linkage = common.linkage, .visibility = common.visibility });7 @export(&__floateihf, .{ .name = "__floateihf", .linkage = common.linkage, .visibility = common.visibility });
10}8}
lib/compiler_rt/floateisf.zig-2
...@@ -3,8 +3,6 @@ const builtin = @import("builtin");...@@ -3,8 +3,6 @@ const builtin = @import("builtin");
3const common = @import("common.zig");3const common = @import("common.zig");
4const floatFromBigInt = @import("float_from_int.zig").floatFromBigInt;4const floatFromBigInt = @import("float_from_int.zig").floatFromBigInt;
55
6pub const panic = common.panic;
7
8comptime {6comptime {
9 @export(&__floateisf, .{ .name = "__floateisf", .linkage = common.linkage, .visibility = common.visibility });7 @export(&__floateisf, .{ .name = "__floateisf", .linkage = common.linkage, .visibility = common.visibility });
10}8}
lib/compiler_rt/floateitf.zig-2
...@@ -3,8 +3,6 @@ const builtin = @import("builtin");...@@ -3,8 +3,6 @@ const builtin = @import("builtin");
3const common = @import("common.zig");3const common = @import("common.zig");
4const floatFromBigInt = @import("float_from_int.zig").floatFromBigInt;4const floatFromBigInt = @import("float_from_int.zig").floatFromBigInt;
55
6pub const panic = common.panic;
7
8comptime {6comptime {
9 @export(&__floateitf, .{ .name = "__floateitf", .linkage = common.linkage, .visibility = common.visibility });7 @export(&__floateitf, .{ .name = "__floateitf", .linkage = common.linkage, .visibility = common.visibility });
10}8}
lib/compiler_rt/floateixf.zig-2
...@@ -3,8 +3,6 @@ const builtin = @import("builtin");...@@ -3,8 +3,6 @@ const builtin = @import("builtin");
3const common = @import("common.zig");3const common = @import("common.zig");
4const floatFromBigInt = @import("float_from_int.zig").floatFromBigInt;4const floatFromBigInt = @import("float_from_int.zig").floatFromBigInt;
55
6pub const panic = common.panic;
7
8comptime {6comptime {
9 @export(&__floateixf, .{ .name = "__floateixf", .linkage = common.linkage, .visibility = common.visibility });7 @export(&__floateixf, .{ .name = "__floateixf", .linkage = common.linkage, .visibility = common.visibility });
10}8}
lib/compiler_rt/floatsidf.zig-2
...@@ -1,8 +1,6 @@...@@ -1,8 +1,6 @@
1const common = @import("./common.zig");1const common = @import("./common.zig");
2const floatFromInt = @import("./float_from_int.zig").floatFromInt;2const floatFromInt = @import("./float_from_int.zig").floatFromInt;
33
4pub const panic = common.panic;
5
6comptime {4comptime {
7 if (common.want_aeabi) {5 if (common.want_aeabi) {
8 @export(&__aeabi_i2d, .{ .name = "__aeabi_i2d", .linkage = common.linkage, .visibility = common.visibility });6 @export(&__aeabi_i2d, .{ .name = "__aeabi_i2d", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/floatsihf.zig-2
...@@ -1,8 +1,6 @@...@@ -1,8 +1,6 @@
1const common = @import("./common.zig");1const common = @import("./common.zig");
2const floatFromInt = @import("./float_from_int.zig").floatFromInt;2const floatFromInt = @import("./float_from_int.zig").floatFromInt;
33
4pub const panic = common.panic;
5
6comptime {4comptime {
7 @export(&__floatsihf, .{ .name = "__floatsihf", .linkage = common.linkage, .visibility = common.visibility });5 @export(&__floatsihf, .{ .name = "__floatsihf", .linkage = common.linkage, .visibility = common.visibility });
8}6}
lib/compiler_rt/floatsisf.zig-2
...@@ -1,8 +1,6 @@...@@ -1,8 +1,6 @@
1const common = @import("./common.zig");1const common = @import("./common.zig");
2const floatFromInt = @import("./float_from_int.zig").floatFromInt;2const floatFromInt = @import("./float_from_int.zig").floatFromInt;
33
4pub const panic = common.panic;
5
6comptime {4comptime {
7 if (common.want_aeabi) {5 if (common.want_aeabi) {
8 @export(&__aeabi_i2f, .{ .name = "__aeabi_i2f", .linkage = common.linkage, .visibility = common.visibility });6 @export(&__aeabi_i2f, .{ .name = "__aeabi_i2f", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/floatsitf.zig-2
...@@ -1,8 +1,6 @@...@@ -1,8 +1,6 @@
1const common = @import("./common.zig");1const common = @import("./common.zig");
2const floatFromInt = @import("./float_from_int.zig").floatFromInt;2const floatFromInt = @import("./float_from_int.zig").floatFromInt;
33
4pub const panic = common.panic;
5
6comptime {4comptime {
7 if (common.want_ppc_abi) {5 if (common.want_ppc_abi) {
8 @export(&__floatsitf, .{ .name = "__floatsikf", .linkage = common.linkage, .visibility = common.visibility });6 @export(&__floatsitf, .{ .name = "__floatsikf", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/floatsixf.zig-2
...@@ -1,8 +1,6 @@...@@ -1,8 +1,6 @@
1const common = @import("./common.zig");1const common = @import("./common.zig");
2const floatFromInt = @import("./float_from_int.zig").floatFromInt;2const floatFromInt = @import("./float_from_int.zig").floatFromInt;
33
4pub const panic = common.panic;
5
6comptime {4comptime {
7 @export(&__floatsixf, .{ .name = "__floatsixf", .linkage = common.linkage, .visibility = common.visibility });5 @export(&__floatsixf, .{ .name = "__floatsixf", .linkage = common.linkage, .visibility = common.visibility });
8}6}
lib/compiler_rt/floattidf.zig-2
...@@ -2,8 +2,6 @@ const builtin = @import("builtin");...@@ -2,8 +2,6 @@ const builtin = @import("builtin");
2const common = @import("./common.zig");2const common = @import("./common.zig");
3const floatFromInt = @import("./float_from_int.zig").floatFromInt;3const floatFromInt = @import("./float_from_int.zig").floatFromInt;
44
5pub const panic = common.panic;
6
7comptime {5comptime {
8 if (common.want_windows_v2u64_abi) {6 if (common.want_windows_v2u64_abi) {
9 @export(&__floattidf_windows_x86_64, .{ .name = "__floattidf", .linkage = common.linkage, .visibility = common.visibility });7 @export(&__floattidf_windows_x86_64, .{ .name = "__floattidf", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/floattihf.zig-2
...@@ -2,8 +2,6 @@ const builtin = @import("builtin");...@@ -2,8 +2,6 @@ const builtin = @import("builtin");
2const common = @import("./common.zig");2const common = @import("./common.zig");
3const floatFromInt = @import("./float_from_int.zig").floatFromInt;3const floatFromInt = @import("./float_from_int.zig").floatFromInt;
44
5pub const panic = common.panic;
6
7comptime {5comptime {
8 if (common.want_windows_v2u64_abi) {6 if (common.want_windows_v2u64_abi) {
9 @export(&__floattihf_windows_x86_64, .{ .name = "__floattihf", .linkage = common.linkage, .visibility = common.visibility });7 @export(&__floattihf_windows_x86_64, .{ .name = "__floattihf", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/floattisf.zig-2
...@@ -2,8 +2,6 @@ const builtin = @import("builtin");...@@ -2,8 +2,6 @@ const builtin = @import("builtin");
2const common = @import("./common.zig");2const common = @import("./common.zig");
3const floatFromInt = @import("./float_from_int.zig").floatFromInt;3const floatFromInt = @import("./float_from_int.zig").floatFromInt;
44
5pub const panic = common.panic;
6
7comptime {5comptime {
8 if (common.want_windows_v2u64_abi) {6 if (common.want_windows_v2u64_abi) {
9 @export(&__floattisf_windows_x86_64, .{ .name = "__floattisf", .linkage = common.linkage, .visibility = common.visibility });7 @export(&__floattisf_windows_x86_64, .{ .name = "__floattisf", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/floattitf.zig-2
...@@ -2,8 +2,6 @@ const builtin = @import("builtin");...@@ -2,8 +2,6 @@ const builtin = @import("builtin");
2const common = @import("./common.zig");2const common = @import("./common.zig");
3const floatFromInt = @import("./float_from_int.zig").floatFromInt;3const floatFromInt = @import("./float_from_int.zig").floatFromInt;
44
5pub const panic = common.panic;
6
7comptime {5comptime {
8 if (common.want_windows_v2u64_abi) {6 if (common.want_windows_v2u64_abi) {
9 @export(&__floattitf_windows_x86_64, .{ .name = "__floattitf", .linkage = common.linkage, .visibility = common.visibility });7 @export(&__floattitf_windows_x86_64, .{ .name = "__floattitf", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/floattixf.zig-2
...@@ -2,8 +2,6 @@ const builtin = @import("builtin");...@@ -2,8 +2,6 @@ const builtin = @import("builtin");
2const common = @import("./common.zig");2const common = @import("./common.zig");
3const floatFromInt = @import("./float_from_int.zig").floatFromInt;3const floatFromInt = @import("./float_from_int.zig").floatFromInt;
44
5pub const panic = common.panic;
6
7comptime {5comptime {
8 if (common.want_windows_v2u64_abi) {6 if (common.want_windows_v2u64_abi) {
9 @export(&__floattixf_windows_x86_64, .{ .name = "__floattixf", .linkage = common.linkage, .visibility = common.visibility });7 @export(&__floattixf_windows_x86_64, .{ .name = "__floattixf", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/floatundidf.zig-2
...@@ -2,8 +2,6 @@ const builtin = @import("builtin");...@@ -2,8 +2,6 @@ const builtin = @import("builtin");
2const common = @import("./common.zig");2const common = @import("./common.zig");
3const floatFromInt = @import("./float_from_int.zig").floatFromInt;3const floatFromInt = @import("./float_from_int.zig").floatFromInt;
44
5pub const panic = common.panic;
6
7comptime {5comptime {
8 if (common.want_aeabi) {6 if (common.want_aeabi) {
9 @export(&__aeabi_ul2d, .{ .name = "__aeabi_ul2d", .linkage = common.linkage, .visibility = common.visibility });7 @export(&__aeabi_ul2d, .{ .name = "__aeabi_ul2d", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/floatundihf.zig-2
...@@ -1,8 +1,6 @@...@@ -1,8 +1,6 @@
1const common = @import("./common.zig");1const common = @import("./common.zig");
2const floatFromInt = @import("./float_from_int.zig").floatFromInt;2const floatFromInt = @import("./float_from_int.zig").floatFromInt;
33
4pub const panic = common.panic;
5
6comptime {4comptime {
7 @export(&__floatundihf, .{ .name = "__floatundihf", .linkage = common.linkage, .visibility = common.visibility });5 @export(&__floatundihf, .{ .name = "__floatundihf", .linkage = common.linkage, .visibility = common.visibility });
8}6}
lib/compiler_rt/floatundisf.zig-2
...@@ -2,8 +2,6 @@ const builtin = @import("builtin");...@@ -2,8 +2,6 @@ const builtin = @import("builtin");
2const common = @import("./common.zig");2const common = @import("./common.zig");
3const floatFromInt = @import("./float_from_int.zig").floatFromInt;3const floatFromInt = @import("./float_from_int.zig").floatFromInt;
44
5pub const panic = common.panic;
6
7comptime {5comptime {
8 if (common.want_aeabi) {6 if (common.want_aeabi) {
9 @export(&__aeabi_ul2f, .{ .name = "__aeabi_ul2f", .linkage = common.linkage, .visibility = common.visibility });7 @export(&__aeabi_ul2f, .{ .name = "__aeabi_ul2f", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/floatunditf.zig-2
...@@ -1,8 +1,6 @@...@@ -1,8 +1,6 @@
1const common = @import("./common.zig");1const common = @import("./common.zig");
2const floatFromInt = @import("./float_from_int.zig").floatFromInt;2const floatFromInt = @import("./float_from_int.zig").floatFromInt;
33
4pub const panic = common.panic;
5
6comptime {4comptime {
7 if (common.want_ppc_abi) {5 if (common.want_ppc_abi) {
8 @export(&__floatunditf, .{ .name = "__floatundikf", .linkage = common.linkage, .visibility = common.visibility });6 @export(&__floatunditf, .{ .name = "__floatundikf", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/floatundixf.zig-2
...@@ -1,8 +1,6 @@...@@ -1,8 +1,6 @@
1const common = @import("./common.zig");1const common = @import("./common.zig");
2const floatFromInt = @import("./float_from_int.zig").floatFromInt;2const floatFromInt = @import("./float_from_int.zig").floatFromInt;
33
4pub const panic = common.panic;
5
6comptime {4comptime {
7 @export(&__floatundixf, .{ .name = "__floatundixf", .linkage = common.linkage, .visibility = common.visibility });5 @export(&__floatundixf, .{ .name = "__floatundixf", .linkage = common.linkage, .visibility = common.visibility });
8}6}
lib/compiler_rt/floatuneidf.zig-2
...@@ -3,8 +3,6 @@ const builtin = @import("builtin");...@@ -3,8 +3,6 @@ const builtin = @import("builtin");
3const common = @import("common.zig");3const common = @import("common.zig");
4const floatFromBigInt = @import("float_from_int.zig").floatFromBigInt;4const floatFromBigInt = @import("float_from_int.zig").floatFromBigInt;
55
6pub const panic = common.panic;
7
8comptime {6comptime {
9 @export(&__floatuneidf, .{ .name = "__floatuneidf", .linkage = common.linkage, .visibility = common.visibility });7 @export(&__floatuneidf, .{ .name = "__floatuneidf", .linkage = common.linkage, .visibility = common.visibility });
10}8}
lib/compiler_rt/floatuneihf.zig-2
...@@ -3,8 +3,6 @@ const builtin = @import("builtin");...@@ -3,8 +3,6 @@ const builtin = @import("builtin");
3const common = @import("common.zig");3const common = @import("common.zig");
4const floatFromBigInt = @import("float_from_int.zig").floatFromBigInt;4const floatFromBigInt = @import("float_from_int.zig").floatFromBigInt;
55
6pub const panic = common.panic;
7
8comptime {6comptime {
9 @export(&__floatuneihf, .{ .name = "__floatuneihf", .linkage = common.linkage, .visibility = common.visibility });7 @export(&__floatuneihf, .{ .name = "__floatuneihf", .linkage = common.linkage, .visibility = common.visibility });
10}8}
lib/compiler_rt/floatuneisf.zig-2
...@@ -3,8 +3,6 @@ const builtin = @import("builtin");...@@ -3,8 +3,6 @@ const builtin = @import("builtin");
3const common = @import("common.zig");3const common = @import("common.zig");
4const floatFromBigInt = @import("float_from_int.zig").floatFromBigInt;4const floatFromBigInt = @import("float_from_int.zig").floatFromBigInt;
55
6pub const panic = common.panic;
7
8comptime {6comptime {
9 @export(&__floatuneisf, .{ .name = "__floatuneisf", .linkage = common.linkage, .visibility = common.visibility });7 @export(&__floatuneisf, .{ .name = "__floatuneisf", .linkage = common.linkage, .visibility = common.visibility });
10}8}
lib/compiler_rt/floatuneitf.zig-2
...@@ -3,8 +3,6 @@ const builtin = @import("builtin");...@@ -3,8 +3,6 @@ const builtin = @import("builtin");
3const common = @import("common.zig");3const common = @import("common.zig");
4const floatFromBigInt = @import("float_from_int.zig").floatFromBigInt;4const floatFromBigInt = @import("float_from_int.zig").floatFromBigInt;
55
6pub const panic = common.panic;
7
8comptime {6comptime {
9 @export(&__floatuneitf, .{ .name = "__floatuneitf", .linkage = common.linkage, .visibility = common.visibility });7 @export(&__floatuneitf, .{ .name = "__floatuneitf", .linkage = common.linkage, .visibility = common.visibility });
10}8}
lib/compiler_rt/floatuneixf.zig-2
...@@ -3,8 +3,6 @@ const builtin = @import("builtin");...@@ -3,8 +3,6 @@ const builtin = @import("builtin");
3const common = @import("common.zig");3const common = @import("common.zig");
4const floatFromBigInt = @import("float_from_int.zig").floatFromBigInt;4const floatFromBigInt = @import("float_from_int.zig").floatFromBigInt;
55
6pub const panic = common.panic;
7
8comptime {6comptime {
9 @export(&__floatuneixf, .{ .name = "__floatuneixf", .linkage = common.linkage, .visibility = common.visibility });7 @export(&__floatuneixf, .{ .name = "__floatuneixf", .linkage = common.linkage, .visibility = common.visibility });
10}8}
lib/compiler_rt/floatunsidf.zig-2
...@@ -1,8 +1,6 @@...@@ -1,8 +1,6 @@
1const common = @import("./common.zig");1const common = @import("./common.zig");
2const floatFromInt = @import("./float_from_int.zig").floatFromInt;2const floatFromInt = @import("./float_from_int.zig").floatFromInt;
33
4pub const panic = common.panic;
5
6comptime {4comptime {
7 if (common.want_aeabi) {5 if (common.want_aeabi) {
8 @export(&__aeabi_ui2d, .{ .name = "__aeabi_ui2d", .linkage = common.linkage, .visibility = common.visibility });6 @export(&__aeabi_ui2d, .{ .name = "__aeabi_ui2d", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/floatunsihf.zig-2
...@@ -1,8 +1,6 @@...@@ -1,8 +1,6 @@
1const common = @import("./common.zig");1const common = @import("./common.zig");
2const floatFromInt = @import("./float_from_int.zig").floatFromInt;2const floatFromInt = @import("./float_from_int.zig").floatFromInt;
33
4pub const panic = common.panic;
5
6comptime {4comptime {
7 @export(&__floatunsihf, .{ .name = "__floatunsihf", .linkage = common.linkage, .visibility = common.visibility });5 @export(&__floatunsihf, .{ .name = "__floatunsihf", .linkage = common.linkage, .visibility = common.visibility });
8}6}
lib/compiler_rt/floatunsisf.zig-2
...@@ -1,8 +1,6 @@...@@ -1,8 +1,6 @@
1const common = @import("./common.zig");1const common = @import("./common.zig");
2const floatFromInt = @import("./float_from_int.zig").floatFromInt;2const floatFromInt = @import("./float_from_int.zig").floatFromInt;
33
4pub const panic = common.panic;
5
6comptime {4comptime {
7 if (common.want_aeabi) {5 if (common.want_aeabi) {
8 @export(&__aeabi_ui2f, .{ .name = "__aeabi_ui2f", .linkage = common.linkage, .visibility = common.visibility });6 @export(&__aeabi_ui2f, .{ .name = "__aeabi_ui2f", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/floatunsitf.zig-2
...@@ -1,8 +1,6 @@...@@ -1,8 +1,6 @@
1const common = @import("./common.zig");1const common = @import("./common.zig");
2const floatFromInt = @import("./float_from_int.zig").floatFromInt;2const floatFromInt = @import("./float_from_int.zig").floatFromInt;
33
4pub const panic = common.panic;
5
6comptime {4comptime {
7 if (common.want_ppc_abi) {5 if (common.want_ppc_abi) {
8 @export(&__floatunsitf, .{ .name = "__floatunsikf", .linkage = common.linkage, .visibility = common.visibility });6 @export(&__floatunsitf, .{ .name = "__floatunsikf", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/floatunsixf.zig-2
...@@ -1,8 +1,6 @@...@@ -1,8 +1,6 @@
1const common = @import("./common.zig");1const common = @import("./common.zig");
2const floatFromInt = @import("./float_from_int.zig").floatFromInt;2const floatFromInt = @import("./float_from_int.zig").floatFromInt;
33
4pub const panic = common.panic;
5
6comptime {4comptime {
7 @export(&__floatunsixf, .{ .name = "__floatunsixf", .linkage = common.linkage, .visibility = common.visibility });5 @export(&__floatunsixf, .{ .name = "__floatunsixf", .linkage = common.linkage, .visibility = common.visibility });
8}6}
lib/compiler_rt/floatuntidf.zig-2
...@@ -2,8 +2,6 @@ const builtin = @import("builtin");...@@ -2,8 +2,6 @@ const builtin = @import("builtin");
2const common = @import("./common.zig");2const common = @import("./common.zig");
3const floatFromInt = @import("./float_from_int.zig").floatFromInt;3const floatFromInt = @import("./float_from_int.zig").floatFromInt;
44
5pub const panic = common.panic;
6
7comptime {5comptime {
8 if (common.want_windows_v2u64_abi) {6 if (common.want_windows_v2u64_abi) {
9 @export(&__floatuntidf_windows_x86_64, .{ .name = "__floatuntidf", .linkage = common.linkage, .visibility = common.visibility });7 @export(&__floatuntidf_windows_x86_64, .{ .name = "__floatuntidf", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/floatuntihf.zig-2
...@@ -2,8 +2,6 @@ const builtin = @import("builtin");...@@ -2,8 +2,6 @@ const builtin = @import("builtin");
2const common = @import("./common.zig");2const common = @import("./common.zig");
3const floatFromInt = @import("./float_from_int.zig").floatFromInt;3const floatFromInt = @import("./float_from_int.zig").floatFromInt;
44
5pub const panic = common.panic;
6
7comptime {5comptime {
8 if (common.want_windows_v2u64_abi) {6 if (common.want_windows_v2u64_abi) {
9 @export(&__floatuntihf_windows_x86_64, .{ .name = "__floatuntihf", .linkage = common.linkage, .visibility = common.visibility });7 @export(&__floatuntihf_windows_x86_64, .{ .name = "__floatuntihf", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/floatuntisf.zig-2
...@@ -2,8 +2,6 @@ const builtin = @import("builtin");...@@ -2,8 +2,6 @@ const builtin = @import("builtin");
2const common = @import("./common.zig");2const common = @import("./common.zig");
3const floatFromInt = @import("./float_from_int.zig").floatFromInt;3const floatFromInt = @import("./float_from_int.zig").floatFromInt;
44
5pub const panic = common.panic;
6
7comptime {5comptime {
8 if (common.want_windows_v2u64_abi) {6 if (common.want_windows_v2u64_abi) {
9 @export(&__floatuntisf_windows_x86_64, .{ .name = "__floatuntisf", .linkage = common.linkage, .visibility = common.visibility });7 @export(&__floatuntisf_windows_x86_64, .{ .name = "__floatuntisf", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/floatuntitf.zig-2
...@@ -2,8 +2,6 @@ const builtin = @import("builtin");...@@ -2,8 +2,6 @@ const builtin = @import("builtin");
2const common = @import("./common.zig");2const common = @import("./common.zig");
3const floatFromInt = @import("./float_from_int.zig").floatFromInt;3const floatFromInt = @import("./float_from_int.zig").floatFromInt;
44
5pub const panic = common.panic;
6
7comptime {5comptime {
8 if (common.want_windows_v2u64_abi) {6 if (common.want_windows_v2u64_abi) {
9 @export(&__floatuntitf_windows_x86_64, .{ .name = "__floatuntitf", .linkage = common.linkage, .visibility = common.visibility });7 @export(&__floatuntitf_windows_x86_64, .{ .name = "__floatuntitf", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/floatuntixf.zig-2
...@@ -2,8 +2,6 @@ const builtin = @import("builtin");...@@ -2,8 +2,6 @@ const builtin = @import("builtin");
2const common = @import("./common.zig");2const common = @import("./common.zig");
3const floatFromInt = @import("./float_from_int.zig").floatFromInt;3const floatFromInt = @import("./float_from_int.zig").floatFromInt;
44
5pub const panic = common.panic;
6
7comptime {5comptime {
8 if (common.want_windows_v2u64_abi) {6 if (common.want_windows_v2u64_abi) {
9 @export(&__floatuntixf_windows_x86_64, .{ .name = "__floatuntixf", .linkage = common.linkage, .visibility = common.visibility });7 @export(&__floatuntixf_windows_x86_64, .{ .name = "__floatuntixf", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/floor.zig-2
...@@ -13,8 +13,6 @@ const expect = std.testing.expect;...@@ -13,8 +13,6 @@ const expect = std.testing.expect;
13const arch = builtin.cpu.arch;13const arch = builtin.cpu.arch;
14const common = @import("common.zig");14const common = @import("common.zig");
1515
16pub const panic = common.panic;
17
18comptime {16comptime {
19 @export(&__floorh, .{ .name = "__floorh", .linkage = common.linkage, .visibility = common.visibility });17 @export(&__floorh, .{ .name = "__floorh", .linkage = common.linkage, .visibility = common.visibility });
20 @export(&floorf, .{ .name = "floorf", .linkage = common.linkage, .visibility = common.visibility });18 @export(&floorf, .{ .name = "floorf", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/fma.zig-2
...@@ -10,8 +10,6 @@ const math = std.math;...@@ -10,8 +10,6 @@ const math = std.math;
10const expect = std.testing.expect;10const expect = std.testing.expect;
11const common = @import("common.zig");11const common = @import("common.zig");
1212
13pub const panic = common.panic;
14
15comptime {13comptime {
16 @export(&__fmah, .{ .name = "__fmah", .linkage = common.linkage, .visibility = common.visibility });14 @export(&__fmah, .{ .name = "__fmah", .linkage = common.linkage, .visibility = common.visibility });
17 @export(&fmaf, .{ .name = "fmaf", .linkage = common.linkage, .visibility = common.visibility });15 @export(&fmaf, .{ .name = "fmaf", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/fmax.zig-2
...@@ -4,8 +4,6 @@ const math = std.math;...@@ -4,8 +4,6 @@ const math = std.math;
4const arch = builtin.cpu.arch;4const arch = builtin.cpu.arch;
5const common = @import("common.zig");5const common = @import("common.zig");
66
7pub const panic = common.panic;
8
9comptime {7comptime {
10 @export(&__fmaxh, .{ .name = "__fmaxh", .linkage = common.linkage, .visibility = common.visibility });8 @export(&__fmaxh, .{ .name = "__fmaxh", .linkage = common.linkage, .visibility = common.visibility });
11 @export(&fmaxf, .{ .name = "fmaxf", .linkage = common.linkage, .visibility = common.visibility });9 @export(&fmaxf, .{ .name = "fmaxf", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/fmin.zig-2
...@@ -4,8 +4,6 @@ const math = std.math;...@@ -4,8 +4,6 @@ const math = std.math;
4const arch = builtin.cpu.arch;4const arch = builtin.cpu.arch;
5const common = @import("common.zig");5const common = @import("common.zig");
66
7pub const panic = common.panic;
8
9comptime {7comptime {
10 @export(&__fminh, .{ .name = "__fminh", .linkage = common.linkage, .visibility = common.visibility });8 @export(&__fminh, .{ .name = "__fminh", .linkage = common.linkage, .visibility = common.visibility });
11 @export(&fminf, .{ .name = "fminf", .linkage = common.linkage, .visibility = common.visibility });9 @export(&fminf, .{ .name = "fminf", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/fmod.zig-2
...@@ -6,8 +6,6 @@ const arch = builtin.cpu.arch;...@@ -6,8 +6,6 @@ const arch = builtin.cpu.arch;
6const common = @import("common.zig");6const common = @import("common.zig");
7const normalize = common.normalize;7const normalize = common.normalize;
88
9pub const panic = common.panic;
10
11comptime {9comptime {
12 @export(&__fmodh, .{ .name = "__fmodh", .linkage = common.linkage, .visibility = common.visibility });10 @export(&__fmodh, .{ .name = "__fmodh", .linkage = common.linkage, .visibility = common.visibility });
13 @export(&fmodf, .{ .name = "fmodf", .linkage = common.linkage, .visibility = common.visibility });11 @export(&fmodf, .{ .name = "fmodf", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/gedf2.zig-2
...@@ -3,8 +3,6 @@...@@ -3,8 +3,6 @@
3const common = @import("./common.zig");3const common = @import("./common.zig");
4const comparef = @import("./comparef.zig");4const comparef = @import("./comparef.zig");
55
6pub const panic = common.panic;
7
8comptime {6comptime {
9 if (common.want_aeabi) {7 if (common.want_aeabi) {
10 @export(&__aeabi_dcmpge, .{ .name = "__aeabi_dcmpge", .linkage = common.linkage, .visibility = common.visibility });8 @export(&__aeabi_dcmpge, .{ .name = "__aeabi_dcmpge", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/gehf2.zig-2
...@@ -3,8 +3,6 @@...@@ -3,8 +3,6 @@
3const common = @import("./common.zig");3const common = @import("./common.zig");
4const comparef = @import("./comparef.zig");4const comparef = @import("./comparef.zig");
55
6pub const panic = common.panic;
7
8comptime {6comptime {
9 @export(&__gehf2, .{ .name = "__gehf2", .linkage = common.linkage, .visibility = common.visibility });7 @export(&__gehf2, .{ .name = "__gehf2", .linkage = common.linkage, .visibility = common.visibility });
10 @export(&__gthf2, .{ .name = "__gthf2", .linkage = common.linkage, .visibility = common.visibility });8 @export(&__gthf2, .{ .name = "__gthf2", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/gesf2.zig-2
...@@ -3,8 +3,6 @@...@@ -3,8 +3,6 @@
3const common = @import("./common.zig");3const common = @import("./common.zig");
4const comparef = @import("./comparef.zig");4const comparef = @import("./comparef.zig");
55
6pub const panic = common.panic;
7
8comptime {6comptime {
9 if (common.want_aeabi) {7 if (common.want_aeabi) {
10 @export(&__aeabi_fcmpge, .{ .name = "__aeabi_fcmpge", .linkage = common.linkage, .visibility = common.visibility });8 @export(&__aeabi_fcmpge, .{ .name = "__aeabi_fcmpge", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/getf2.zig-2
...@@ -3,8 +3,6 @@...@@ -3,8 +3,6 @@
3const common = @import("./common.zig");3const common = @import("./common.zig");
4const comparef = @import("./comparef.zig");4const comparef = @import("./comparef.zig");
55
6pub const panic = common.panic;
7
8comptime {6comptime {
9 if (common.want_ppc_abi) {7 if (common.want_ppc_abi) {
10 @export(&__getf2, .{ .name = "__gekf2", .linkage = common.linkage, .visibility = common.visibility });8 @export(&__getf2, .{ .name = "__gekf2", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/gexf2.zig-2
...@@ -1,8 +1,6 @@...@@ -1,8 +1,6 @@
1const common = @import("./common.zig");1const common = @import("./common.zig");
2const comparef = @import("./comparef.zig");2const comparef = @import("./comparef.zig");
33
4pub const panic = common.panic;
5
6comptime {4comptime {
7 @export(&__gexf2, .{ .name = "__gexf2", .linkage = common.linkage, .visibility = common.visibility });5 @export(&__gexf2, .{ .name = "__gexf2", .linkage = common.linkage, .visibility = common.visibility });
8 @export(&__gtxf2, .{ .name = "__gtxf2", .linkage = common.linkage, .visibility = common.visibility });6 @export(&__gtxf2, .{ .name = "__gtxf2", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/int.zig-2
...@@ -10,8 +10,6 @@ const common = @import("common.zig");...@@ -10,8 +10,6 @@ const common = @import("common.zig");
10const udivmod = @import("udivmod.zig").udivmod;10const udivmod = @import("udivmod.zig").udivmod;
11const __divti3 = @import("divti3.zig").__divti3;11const __divti3 = @import("divti3.zig").__divti3;
1212
13pub const panic = common.panic;
14
15comptime {13comptime {
16 @export(&__divmodti4, .{ .name = "__divmodti4", .linkage = common.linkage, .visibility = common.visibility });14 @export(&__divmodti4, .{ .name = "__divmodti4", .linkage = common.linkage, .visibility = common.visibility });
17 @export(&__udivmoddi4, .{ .name = "__udivmoddi4", .linkage = common.linkage, .visibility = common.visibility });15 @export(&__udivmoddi4, .{ .name = "__udivmoddi4", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/log.zig-2
...@@ -12,8 +12,6 @@ const expectEqual = std.testing.expectEqual;...@@ -12,8 +12,6 @@ const expectEqual = std.testing.expectEqual;
12const arch = builtin.cpu.arch;12const arch = builtin.cpu.arch;
13const common = @import("common.zig");13const common = @import("common.zig");
1414
15pub const panic = common.panic;
16
17comptime {15comptime {
18 @export(&__logh, .{ .name = "__logh", .linkage = common.linkage, .visibility = common.visibility });16 @export(&__logh, .{ .name = "__logh", .linkage = common.linkage, .visibility = common.visibility });
19 @export(&logf, .{ .name = "logf", .linkage = common.linkage, .visibility = common.visibility });17 @export(&logf, .{ .name = "logf", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/log10.zig-2
...@@ -13,8 +13,6 @@ const maxInt = std.math.maxInt;...@@ -13,8 +13,6 @@ const maxInt = std.math.maxInt;
13const arch = builtin.cpu.arch;13const arch = builtin.cpu.arch;
14const common = @import("common.zig");14const common = @import("common.zig");
1515
16pub const panic = common.panic;
17
18comptime {16comptime {
19 @export(&__log10h, .{ .name = "__log10h", .linkage = common.linkage, .visibility = common.visibility });17 @export(&__log10h, .{ .name = "__log10h", .linkage = common.linkage, .visibility = common.visibility });
20 @export(&log10f, .{ .name = "log10f", .linkage = common.linkage, .visibility = common.visibility });18 @export(&log10f, .{ .name = "log10f", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/log2.zig-2
...@@ -13,8 +13,6 @@ const maxInt = std.math.maxInt;...@@ -13,8 +13,6 @@ const maxInt = std.math.maxInt;
13const arch = builtin.cpu.arch;13const arch = builtin.cpu.arch;
14const common = @import("common.zig");14const common = @import("common.zig");
1515
16pub const panic = common.panic;
17
18comptime {16comptime {
19 @export(&__log2h, .{ .name = "__log2h", .linkage = common.linkage, .visibility = common.visibility });17 @export(&__log2h, .{ .name = "__log2h", .linkage = common.linkage, .visibility = common.visibility });
20 @export(&log2f, .{ .name = "log2f", .linkage = common.linkage, .visibility = common.visibility });18 @export(&log2f, .{ .name = "log2f", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/modti3.zig-2
...@@ -7,8 +7,6 @@ const builtin = @import("builtin");...@@ -7,8 +7,6 @@ const builtin = @import("builtin");
7const udivmod = @import("udivmod.zig").udivmod;7const udivmod = @import("udivmod.zig").udivmod;
8const common = @import("common.zig");8const common = @import("common.zig");
99
10pub const panic = common.panic;
11
12comptime {10comptime {
13 if (common.want_windows_v2u64_abi) {11 if (common.want_windows_v2u64_abi) {
14 @export(&__modti3_windows_x86_64, .{ .name = "__modti3", .linkage = common.linkage, .visibility = common.visibility });12 @export(&__modti3_windows_x86_64, .{ .name = "__modti3", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/mulXi3.zig-2
...@@ -4,8 +4,6 @@ const testing = std.testing;...@@ -4,8 +4,6 @@ const testing = std.testing;
4const common = @import("common.zig");4const common = @import("common.zig");
5const native_endian = builtin.cpu.arch.endian();5const native_endian = builtin.cpu.arch.endian();
66
7pub const panic = common.panic;
8
9comptime {7comptime {
10 @export(&__mulsi3, .{ .name = "__mulsi3", .linkage = common.linkage, .visibility = common.visibility });8 @export(&__mulsi3, .{ .name = "__mulsi3", .linkage = common.linkage, .visibility = common.visibility });
11 if (common.want_aeabi) {9 if (common.want_aeabi) {
lib/compiler_rt/muldc3.zig-2
...@@ -1,8 +1,6 @@...@@ -1,8 +1,6 @@
1const common = @import("./common.zig");1const common = @import("./common.zig");
2const mulc3 = @import("./mulc3.zig");2const mulc3 = @import("./mulc3.zig");
33
4pub const panic = common.panic;
5
6comptime {4comptime {
7 if (@import("builtin").zig_backend != .stage2_c) {5 if (@import("builtin").zig_backend != .stage2_c) {
8 @export(&__muldc3, .{ .name = "__muldc3", .linkage = common.linkage, .visibility = common.visibility });6 @export(&__muldc3, .{ .name = "__muldc3", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/muldf3.zig-2
...@@ -1,8 +1,6 @@...@@ -1,8 +1,6 @@
1const common = @import("./common.zig");1const common = @import("./common.zig");
2const mulf3 = @import("./mulf3.zig").mulf3;2const mulf3 = @import("./mulf3.zig").mulf3;
33
4pub const panic = common.panic;
5
6comptime {4comptime {
7 if (common.want_aeabi) {5 if (common.want_aeabi) {
8 @export(&__aeabi_dmul, .{ .name = "__aeabi_dmul", .linkage = common.linkage, .visibility = common.visibility });6 @export(&__aeabi_dmul, .{ .name = "__aeabi_dmul", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/mulhc3.zig-2
...@@ -1,8 +1,6 @@...@@ -1,8 +1,6 @@
1const common = @import("./common.zig");1const common = @import("./common.zig");
2const mulc3 = @import("./mulc3.zig");2const mulc3 = @import("./mulc3.zig");
33
4pub const panic = common.panic;
5
6comptime {4comptime {
7 if (@import("builtin").zig_backend != .stage2_c) {5 if (@import("builtin").zig_backend != .stage2_c) {
8 @export(&__mulhc3, .{ .name = "__mulhc3", .linkage = common.linkage, .visibility = common.visibility });6 @export(&__mulhc3, .{ .name = "__mulhc3", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/mulhf3.zig-2
...@@ -1,8 +1,6 @@...@@ -1,8 +1,6 @@
1const common = @import("./common.zig");1const common = @import("./common.zig");
2const mulf3 = @import("./mulf3.zig").mulf3;2const mulf3 = @import("./mulf3.zig").mulf3;
33
4pub const panic = common.panic;
5
6comptime {4comptime {
7 @export(&__mulhf3, .{ .name = "__mulhf3", .linkage = common.linkage, .visibility = common.visibility });5 @export(&__mulhf3, .{ .name = "__mulhf3", .linkage = common.linkage, .visibility = common.visibility });
8}6}
lib/compiler_rt/mulo.zig-2
...@@ -3,8 +3,6 @@ const builtin = @import("builtin");...@@ -3,8 +3,6 @@ const builtin = @import("builtin");
3const math = std.math;3const math = std.math;
4const common = @import("common.zig");4const common = @import("common.zig");
55
6pub const panic = common.panic;
7
8comptime {6comptime {
9 @export(&__mulosi4, .{ .name = "__mulosi4", .linkage = common.linkage, .visibility = common.visibility });7 @export(&__mulosi4, .{ .name = "__mulosi4", .linkage = common.linkage, .visibility = common.visibility });
10 @export(&__mulodi4, .{ .name = "__mulodi4", .linkage = common.linkage, .visibility = common.visibility });8 @export(&__mulodi4, .{ .name = "__mulodi4", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/mulsc3.zig-2
...@@ -1,8 +1,6 @@...@@ -1,8 +1,6 @@
1const common = @import("./common.zig");1const common = @import("./common.zig");
2const mulc3 = @import("./mulc3.zig");2const mulc3 = @import("./mulc3.zig");
33
4pub const panic = common.panic;
5
6comptime {4comptime {
7 if (@import("builtin").zig_backend != .stage2_c) {5 if (@import("builtin").zig_backend != .stage2_c) {
8 @export(&__mulsc3, .{ .name = "__mulsc3", .linkage = common.linkage, .visibility = common.visibility });6 @export(&__mulsc3, .{ .name = "__mulsc3", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/mulsf3.zig-2
...@@ -1,8 +1,6 @@...@@ -1,8 +1,6 @@
1const common = @import("./common.zig");1const common = @import("./common.zig");
2const mulf3 = @import("./mulf3.zig").mulf3;2const mulf3 = @import("./mulf3.zig").mulf3;
33
4pub const panic = common.panic;
5
6comptime {4comptime {
7 if (common.want_aeabi) {5 if (common.want_aeabi) {
8 @export(&__aeabi_fmul, .{ .name = "__aeabi_fmul", .linkage = common.linkage, .visibility = common.visibility });6 @export(&__aeabi_fmul, .{ .name = "__aeabi_fmul", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/multc3.zig-2
...@@ -1,8 +1,6 @@...@@ -1,8 +1,6 @@
1const common = @import("./common.zig");1const common = @import("./common.zig");
2const mulc3 = @import("./mulc3.zig");2const mulc3 = @import("./mulc3.zig");
33
4pub const panic = common.panic;
5
6comptime {4comptime {
7 if (@import("builtin").zig_backend != .stage2_c) {5 if (@import("builtin").zig_backend != .stage2_c) {
8 if (common.want_ppc_abi)6 if (common.want_ppc_abi)
lib/compiler_rt/multf3.zig-2
...@@ -1,8 +1,6 @@...@@ -1,8 +1,6 @@
1const common = @import("./common.zig");1const common = @import("./common.zig");
2const mulf3 = @import("./mulf3.zig").mulf3;2const mulf3 = @import("./mulf3.zig").mulf3;
33
4pub const panic = common.panic;
5
6comptime {4comptime {
7 if (common.want_ppc_abi) {5 if (common.want_ppc_abi) {
8 @export(&__multf3, .{ .name = "__mulkf3", .linkage = common.linkage, .visibility = common.visibility });6 @export(&__multf3, .{ .name = "__mulkf3", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/mulvsi3.zig-2
...@@ -2,8 +2,6 @@ const mulv = @import("mulo.zig");...@@ -2,8 +2,6 @@ const mulv = @import("mulo.zig");
2const common = @import("./common.zig");2const common = @import("./common.zig");
3const testing = @import("std").testing;3const testing = @import("std").testing;
44
5pub const panic = common.panic;
6
7comptime {5comptime {
8 @export(&__mulvsi3, .{ .name = "__mulvsi3", .linkage = common.linkage, .visibility = common.visibility });6 @export(&__mulvsi3, .{ .name = "__mulvsi3", .linkage = common.linkage, .visibility = common.visibility });
9}7}
lib/compiler_rt/mulxc3.zig-2
...@@ -1,8 +1,6 @@...@@ -1,8 +1,6 @@
1const common = @import("./common.zig");1const common = @import("./common.zig");
2const mulc3 = @import("./mulc3.zig");2const mulc3 = @import("./mulc3.zig");
33
4pub const panic = common.panic;
5
6comptime {4comptime {
7 if (@import("builtin").zig_backend != .stage2_c) {5 if (@import("builtin").zig_backend != .stage2_c) {
8 @export(&__mulxc3, .{ .name = "__mulxc3", .linkage = common.linkage, .visibility = common.visibility });6 @export(&__mulxc3, .{ .name = "__mulxc3", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/mulxf3.zig-2
...@@ -1,8 +1,6 @@...@@ -1,8 +1,6 @@
1const common = @import("./common.zig");1const common = @import("./common.zig");
2const mulf3 = @import("./mulf3.zig").mulf3;2const mulf3 = @import("./mulf3.zig").mulf3;
33
4pub const panic = common.panic;
5
6comptime {4comptime {
7 @export(&__mulxf3, .{ .name = "__mulxf3", .linkage = common.linkage, .visibility = common.visibility });5 @export(&__mulxf3, .{ .name = "__mulxf3", .linkage = common.linkage, .visibility = common.visibility });
8}6}
lib/compiler_rt/negXi2.zig-2
...@@ -10,8 +10,6 @@ const std = @import("std");...@@ -10,8 +10,6 @@ const std = @import("std");
10const builtin = @import("builtin");10const builtin = @import("builtin");
11const common = @import("common.zig");11const common = @import("common.zig");
1212
13pub const panic = common.panic;
14
15comptime {13comptime {
16 @export(&__negsi2, .{ .name = "__negsi2", .linkage = common.linkage, .visibility = common.visibility });14 @export(&__negsi2, .{ .name = "__negsi2", .linkage = common.linkage, .visibility = common.visibility });
17 @export(&__negdi2, .{ .name = "__negdi2", .linkage = common.linkage, .visibility = common.visibility });15 @export(&__negdi2, .{ .name = "__negdi2", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/negdf2.zig-2
...@@ -1,7 +1,5 @@...@@ -1,7 +1,5 @@
1const common = @import("./common.zig");1const common = @import("./common.zig");
22
3pub const panic = common.panic;
4
5comptime {3comptime {
6 if (common.want_aeabi) {4 if (common.want_aeabi) {
7 @export(&__aeabi_dneg, .{ .name = "__aeabi_dneg", .linkage = common.linkage, .visibility = common.visibility });5 @export(&__aeabi_dneg, .{ .name = "__aeabi_dneg", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/neghf2.zig-2
...@@ -1,7 +1,5 @@...@@ -1,7 +1,5 @@
1const common = @import("./common.zig");1const common = @import("./common.zig");
22
3pub const panic = common.panic;
4
5comptime {3comptime {
6 @export(&__neghf2, .{ .name = "__neghf2", .linkage = common.linkage, .visibility = common.visibility });4 @export(&__neghf2, .{ .name = "__neghf2", .linkage = common.linkage, .visibility = common.visibility });
7}5}
lib/compiler_rt/negsf2.zig-2
...@@ -1,7 +1,5 @@...@@ -1,7 +1,5 @@
1const common = @import("./common.zig");1const common = @import("./common.zig");
22
3pub const panic = common.panic;
4
5comptime {3comptime {
6 if (common.want_aeabi) {4 if (common.want_aeabi) {
7 @export(&__aeabi_fneg, .{ .name = "__aeabi_fneg", .linkage = common.linkage, .visibility = common.visibility });5 @export(&__aeabi_fneg, .{ .name = "__aeabi_fneg", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/negtf2.zig-2
...@@ -1,7 +1,5 @@...@@ -1,7 +1,5 @@
1const common = @import("./common.zig");1const common = @import("./common.zig");
22
3pub const panic = common.panic;
4
5comptime {3comptime {
6 if (common.want_ppc_abi)4 if (common.want_ppc_abi)
7 @export(&__negtf2, .{ .name = "__negkf2", .linkage = common.linkage, .visibility = common.visibility });5 @export(&__negtf2, .{ .name = "__negkf2", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/negv.zig-2
...@@ -5,8 +5,6 @@ const std = @import("std");...@@ -5,8 +5,6 @@ const std = @import("std");
5const builtin = @import("builtin");5const builtin = @import("builtin");
6const common = @import("common.zig");6const common = @import("common.zig");
77
8pub const panic = common.panic;
9
10comptime {8comptime {
11 @export(&__negvsi2, .{ .name = "__negvsi2", .linkage = common.linkage, .visibility = common.visibility });9 @export(&__negvsi2, .{ .name = "__negvsi2", .linkage = common.linkage, .visibility = common.visibility });
12 @export(&__negvdi2, .{ .name = "__negvdi2", .linkage = common.linkage, .visibility = common.visibility });10 @export(&__negvdi2, .{ .name = "__negvdi2", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/negxf2.zig-2
...@@ -1,7 +1,5 @@...@@ -1,7 +1,5 @@
1const common = @import("./common.zig");1const common = @import("./common.zig");
22
3pub const panic = common.panic;
4
5comptime {3comptime {
6 @export(&__negxf2, .{ .name = "__negxf2", .linkage = common.linkage, .visibility = common.visibility });4 @export(&__negxf2, .{ .name = "__negxf2", .linkage = common.linkage, .visibility = common.visibility });
7}5}
lib/compiler_rt/parity.zig-2
...@@ -5,8 +5,6 @@ const std = @import("std");...@@ -5,8 +5,6 @@ const std = @import("std");
5const builtin = @import("builtin");5const builtin = @import("builtin");
6const common = @import("common.zig");6const common = @import("common.zig");
77
8pub const panic = common.panic;
9
10comptime {8comptime {
11 @export(&__paritysi2, .{ .name = "__paritysi2", .linkage = common.linkage, .visibility = common.visibility });9 @export(&__paritysi2, .{ .name = "__paritysi2", .linkage = common.linkage, .visibility = common.visibility });
12 @export(&__paritydi2, .{ .name = "__paritydi2", .linkage = common.linkage, .visibility = common.visibility });10 @export(&__paritydi2, .{ .name = "__paritydi2", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/popcount.zig-2
...@@ -10,8 +10,6 @@ const builtin = @import("builtin");...@@ -10,8 +10,6 @@ const builtin = @import("builtin");
10const std = @import("std");10const std = @import("std");
11const common = @import("common.zig");11const common = @import("common.zig");
1212
13pub const panic = common.panic;
14
15comptime {13comptime {
16 @export(&__popcountsi2, .{ .name = "__popcountsi2", .linkage = common.linkage, .visibility = common.visibility });14 @export(&__popcountsi2, .{ .name = "__popcountsi2", .linkage = common.linkage, .visibility = common.visibility });
17 @export(&__popcountdi2, .{ .name = "__popcountdi2", .linkage = common.linkage, .visibility = common.visibility });15 @export(&__popcountdi2, .{ .name = "__popcountdi2", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/powiXf2.zig-2
...@@ -7,8 +7,6 @@ const builtin = @import("builtin");...@@ -7,8 +7,6 @@ const builtin = @import("builtin");
7const common = @import("common.zig");7const common = @import("common.zig");
8const std = @import("std");8const std = @import("std");
99
10pub const panic = common.panic;
11
12comptime {10comptime {
13 @export(&__powihf2, .{ .name = "__powihf2", .linkage = common.linkage, .visibility = common.visibility });11 @export(&__powihf2, .{ .name = "__powihf2", .linkage = common.linkage, .visibility = common.visibility });
14 @export(&__powisf2, .{ .name = "__powisf2", .linkage = common.linkage, .visibility = common.visibility });12 @export(&__powisf2, .{ .name = "__powisf2", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/round.zig-2
...@@ -12,8 +12,6 @@ const expect = std.testing.expect;...@@ -12,8 +12,6 @@ const expect = std.testing.expect;
12const arch = builtin.cpu.arch;12const arch = builtin.cpu.arch;
13const common = @import("common.zig");13const common = @import("common.zig");
1414
15pub const panic = common.panic;
16
17comptime {15comptime {
18 @export(&__roundh, .{ .name = "__roundh", .linkage = common.linkage, .visibility = common.visibility });16 @export(&__roundh, .{ .name = "__roundh", .linkage = common.linkage, .visibility = common.visibility });
19 @export(&roundf, .{ .name = "roundf", .linkage = common.linkage, .visibility = common.visibility });17 @export(&roundf, .{ .name = "roundf", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/shift.zig-2
...@@ -3,8 +3,6 @@ const builtin = @import("builtin");...@@ -3,8 +3,6 @@ const builtin = @import("builtin");
3const Log2Int = std.math.Log2Int;3const Log2Int = std.math.Log2Int;
4const common = @import("common.zig");4const common = @import("common.zig");
55
6pub const panic = common.panic;
7
8comptime {6comptime {
9 // symbol compatibility with libgcc7 // symbol compatibility with libgcc
10 @export(&__ashlsi3, .{ .name = "__ashlsi3", .linkage = common.linkage, .visibility = common.visibility });8 @export(&__ashlsi3, .{ .name = "__ashlsi3", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/sin.zig-2
...@@ -16,8 +16,6 @@ const trig = @import("trig.zig");...@@ -16,8 +16,6 @@ const trig = @import("trig.zig");
16const rem_pio2 = @import("rem_pio2.zig").rem_pio2;16const rem_pio2 = @import("rem_pio2.zig").rem_pio2;
17const rem_pio2f = @import("rem_pio2f.zig").rem_pio2f;17const rem_pio2f = @import("rem_pio2f.zig").rem_pio2f;
1818
19pub const panic = common.panic;
20
21comptime {19comptime {
22 @export(&__sinh, .{ .name = "__sinh", .linkage = common.linkage, .visibility = common.visibility });20 @export(&__sinh, .{ .name = "__sinh", .linkage = common.linkage, .visibility = common.visibility });
23 @export(&sinf, .{ .name = "sinf", .linkage = common.linkage, .visibility = common.visibility });21 @export(&sinf, .{ .name = "sinf", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/sincos.zig-2
...@@ -8,8 +8,6 @@ const rem_pio2 = @import("rem_pio2.zig").rem_pio2;...@@ -8,8 +8,6 @@ const rem_pio2 = @import("rem_pio2.zig").rem_pio2;
8const rem_pio2f = @import("rem_pio2f.zig").rem_pio2f;8const rem_pio2f = @import("rem_pio2f.zig").rem_pio2f;
9const common = @import("common.zig");9const common = @import("common.zig");
1010
11pub const panic = common.panic;
12
13comptime {11comptime {
14 @export(&__sincosh, .{ .name = "__sincosh", .linkage = common.linkage, .visibility = common.visibility });12 @export(&__sincosh, .{ .name = "__sincosh", .linkage = common.linkage, .visibility = common.visibility });
15 @export(&sincosf, .{ .name = "sincosf", .linkage = common.linkage, .visibility = common.visibility });13 @export(&sincosf, .{ .name = "sincosf", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/sqrt.zig-2
...@@ -11,8 +11,6 @@ const arch = builtin.cpu.arch;...@@ -11,8 +11,6 @@ const arch = builtin.cpu.arch;
11const math = std.math;11const math = std.math;
12const common = @import("common.zig");12const common = @import("common.zig");
1313
14pub const panic = common.panic;
15
16comptime {14comptime {
17 @export(&__sqrth, .{ .name = "__sqrth", .linkage = common.linkage, .visibility = common.visibility });15 @export(&__sqrth, .{ .name = "__sqrth", .linkage = common.linkage, .visibility = common.visibility });
18 @export(&sqrtf, .{ .name = "sqrtf", .linkage = common.linkage, .visibility = common.visibility });16 @export(&sqrtf, .{ .name = "sqrtf", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/stack_probe.zig-2
...@@ -5,8 +5,6 @@ const os_tag = builtin.os.tag;...@@ -5,8 +5,6 @@ const os_tag = builtin.os.tag;
5const arch = builtin.cpu.arch;5const arch = builtin.cpu.arch;
6const abi = builtin.abi;6const abi = builtin.abi;
77
8pub const panic = common.panic;
9
10comptime {8comptime {
11 if (builtin.os.tag == .windows) {9 if (builtin.os.tag == .windows) {
12 // Default stack-probe functions emitted by LLVM10 // Default stack-probe functions emitted by LLVM
lib/compiler_rt/subdf3.zig-2
...@@ -1,8 +1,6 @@...@@ -1,8 +1,6 @@
1const common = @import("./common.zig");1const common = @import("./common.zig");
2const addf3 = @import("./addf3.zig").addf3;2const addf3 = @import("./addf3.zig").addf3;
33
4pub const panic = common.panic;
5
6comptime {4comptime {
7 if (common.want_aeabi) {5 if (common.want_aeabi) {
8 @export(&__aeabi_dsub, .{ .name = "__aeabi_dsub", .linkage = common.linkage, .visibility = common.visibility });6 @export(&__aeabi_dsub, .{ .name = "__aeabi_dsub", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/subhf3.zig-2
...@@ -1,8 +1,6 @@...@@ -1,8 +1,6 @@
1const common = @import("./common.zig");1const common = @import("./common.zig");
2const addf3 = @import("./addf3.zig").addf3;2const addf3 = @import("./addf3.zig").addf3;
33
4pub const panic = common.panic;
5
6comptime {4comptime {
7 @export(&__subhf3, .{ .name = "__subhf3", .linkage = common.linkage, .visibility = common.visibility });5 @export(&__subhf3, .{ .name = "__subhf3", .linkage = common.linkage, .visibility = common.visibility });
8}6}
lib/compiler_rt/subsf3.zig-2
...@@ -1,8 +1,6 @@...@@ -1,8 +1,6 @@
1const common = @import("./common.zig");1const common = @import("./common.zig");
2const addf3 = @import("./addf3.zig").addf3;2const addf3 = @import("./addf3.zig").addf3;
33
4pub const panic = common.panic;
5
6comptime {4comptime {
7 if (common.want_aeabi) {5 if (common.want_aeabi) {
8 @export(&__aeabi_fsub, .{ .name = "__aeabi_fsub", .linkage = common.linkage, .visibility = common.visibility });6 @export(&__aeabi_fsub, .{ .name = "__aeabi_fsub", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/subtf3.zig-2
...@@ -1,8 +1,6 @@...@@ -1,8 +1,6 @@
1const common = @import("./common.zig");1const common = @import("./common.zig");
2const addf3 = @import("./addf3.zig").addf3;2const addf3 = @import("./addf3.zig").addf3;
33
4pub const panic = common.panic;
5
6comptime {4comptime {
7 if (common.want_ppc_abi) {5 if (common.want_ppc_abi) {
8 @export(&__subtf3, .{ .name = "__subkf3", .linkage = common.linkage, .visibility = common.visibility });6 @export(&__subtf3, .{ .name = "__subkf3", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/subvdi3.zig-2
...@@ -1,8 +1,6 @@...@@ -1,8 +1,6 @@
1const common = @import("./common.zig");1const common = @import("./common.zig");
2const testing = @import("std").testing;2const testing = @import("std").testing;
33
4pub const panic = common.panic;
5
6comptime {4comptime {
7 @export(&__subvdi3, .{ .name = "__subvdi3", .linkage = common.linkage, .visibility = common.visibility });5 @export(&__subvdi3, .{ .name = "__subvdi3", .linkage = common.linkage, .visibility = common.visibility });
8}6}
lib/compiler_rt/subvsi3.zig-2
...@@ -1,8 +1,6 @@...@@ -1,8 +1,6 @@
1const common = @import("./common.zig");1const common = @import("./common.zig");
2const testing = @import("std").testing;2const testing = @import("std").testing;
33
4pub const panic = common.panic;
5
6comptime {4comptime {
7 @export(&__subvsi3, .{ .name = "__subvsi3", .linkage = common.linkage, .visibility = common.visibility });5 @export(&__subvsi3, .{ .name = "__subvsi3", .linkage = common.linkage, .visibility = common.visibility });
8}6}
lib/compiler_rt/subxf3.zig-2
...@@ -1,8 +1,6 @@...@@ -1,8 +1,6 @@
1const std = @import("std");1const std = @import("std");
2const common = @import("./common.zig");2const common = @import("./common.zig");
33
4pub const panic = common.panic;
5
6comptime {4comptime {
7 @export(&__subxf3, .{ .name = "__subxf3", .linkage = common.linkage, .visibility = common.visibility });5 @export(&__subxf3, .{ .name = "__subxf3", .linkage = common.linkage, .visibility = common.visibility });
8}6}
lib/compiler_rt/tan.zig-2
...@@ -18,8 +18,6 @@ const rem_pio2f = @import("rem_pio2f.zig").rem_pio2f;...@@ -18,8 +18,6 @@ const rem_pio2f = @import("rem_pio2f.zig").rem_pio2f;
18const arch = builtin.cpu.arch;18const arch = builtin.cpu.arch;
19const common = @import("common.zig");19const common = @import("common.zig");
2020
21pub const panic = common.panic;
22
23comptime {21comptime {
24 @export(&__tanh, .{ .name = "__tanh", .linkage = common.linkage, .visibility = common.visibility });22 @export(&__tanh, .{ .name = "__tanh", .linkage = common.linkage, .visibility = common.visibility });
25 @export(&tanf, .{ .name = "tanf", .linkage = common.linkage, .visibility = common.visibility });23 @export(&tanf, .{ .name = "tanf", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/trunc.zig-2
...@@ -12,8 +12,6 @@ const mem = std.mem;...@@ -12,8 +12,6 @@ const mem = std.mem;
12const expect = std.testing.expect;12const expect = std.testing.expect;
13const common = @import("common.zig");13const common = @import("common.zig");
1414
15pub const panic = common.panic;
16
17comptime {15comptime {
18 @export(&__trunch, .{ .name = "__trunch", .linkage = common.linkage, .visibility = common.visibility });16 @export(&__trunch, .{ .name = "__trunch", .linkage = common.linkage, .visibility = common.visibility });
19 @export(&truncf, .{ .name = "truncf", .linkage = common.linkage, .visibility = common.visibility });17 @export(&truncf, .{ .name = "truncf", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/truncdfhf2.zig-2
...@@ -1,8 +1,6 @@...@@ -1,8 +1,6 @@
1const common = @import("./common.zig");1const common = @import("./common.zig");
2const truncf = @import("./truncf.zig").truncf;2const truncf = @import("./truncf.zig").truncf;
33
4pub const panic = common.panic;
5
6comptime {4comptime {
7 if (common.want_aeabi) {5 if (common.want_aeabi) {
8 @export(&__aeabi_d2h, .{ .name = "__aeabi_d2h", .linkage = common.linkage, .visibility = common.visibility });6 @export(&__aeabi_d2h, .{ .name = "__aeabi_d2h", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/truncdfsf2.zig-2
...@@ -1,8 +1,6 @@...@@ -1,8 +1,6 @@
1const common = @import("./common.zig");1const common = @import("./common.zig");
2const truncf = @import("./truncf.zig").truncf;2const truncf = @import("./truncf.zig").truncf;
33
4pub const panic = common.panic;
5
6comptime {4comptime {
7 if (common.want_aeabi) {5 if (common.want_aeabi) {
8 @export(&__aeabi_d2f, .{ .name = "__aeabi_d2f", .linkage = common.linkage, .visibility = common.visibility });6 @export(&__aeabi_d2f, .{ .name = "__aeabi_d2f", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/truncsfhf2.zig-2
...@@ -1,8 +1,6 @@...@@ -1,8 +1,6 @@
1const common = @import("./common.zig");1const common = @import("./common.zig");
2const truncf = @import("./truncf.zig").truncf;2const truncf = @import("./truncf.zig").truncf;
33
4pub const panic = common.panic;
5
6comptime {4comptime {
7 if (common.gnu_f16_abi) {5 if (common.gnu_f16_abi) {
8 @export(&__gnu_f2h_ieee, .{ .name = "__gnu_f2h_ieee", .linkage = common.linkage, .visibility = common.visibility });6 @export(&__gnu_f2h_ieee, .{ .name = "__gnu_f2h_ieee", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/trunctfdf2.zig-2
...@@ -1,8 +1,6 @@...@@ -1,8 +1,6 @@
1const common = @import("./common.zig");1const common = @import("./common.zig");
2const truncf = @import("./truncf.zig").truncf;2const truncf = @import("./truncf.zig").truncf;
33
4pub const panic = common.panic;
5
6comptime {4comptime {
7 if (common.want_ppc_abi) {5 if (common.want_ppc_abi) {
8 @export(&__trunctfdf2, .{ .name = "__trunckfdf2", .linkage = common.linkage, .visibility = common.visibility });6 @export(&__trunctfdf2, .{ .name = "__trunckfdf2", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/trunctfhf2.zig-2
...@@ -1,8 +1,6 @@...@@ -1,8 +1,6 @@
1const common = @import("./common.zig");1const common = @import("./common.zig");
2const truncf = @import("./truncf.zig").truncf;2const truncf = @import("./truncf.zig").truncf;
33
4pub const panic = common.panic;
5
6comptime {4comptime {
7 @export(&__trunctfhf2, .{ .name = "__trunctfhf2", .linkage = common.linkage, .visibility = common.visibility });5 @export(&__trunctfhf2, .{ .name = "__trunctfhf2", .linkage = common.linkage, .visibility = common.visibility });
8}6}
lib/compiler_rt/trunctfsf2.zig-2
...@@ -1,8 +1,6 @@...@@ -1,8 +1,6 @@
1const common = @import("./common.zig");1const common = @import("./common.zig");
2const truncf = @import("./truncf.zig").truncf;2const truncf = @import("./truncf.zig").truncf;
33
4pub const panic = common.panic;
5
6comptime {4comptime {
7 if (common.want_ppc_abi) {5 if (common.want_ppc_abi) {
8 @export(&__trunctfsf2, .{ .name = "__trunckfsf2", .linkage = common.linkage, .visibility = common.visibility });6 @export(&__trunctfsf2, .{ .name = "__trunckfsf2", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/trunctfxf2.zig-2
...@@ -2,8 +2,6 @@ const math = @import("std").math;...@@ -2,8 +2,6 @@ const math = @import("std").math;
2const common = @import("./common.zig");2const common = @import("./common.zig");
3const trunc_f80 = @import("./truncf.zig").trunc_f80;3const trunc_f80 = @import("./truncf.zig").trunc_f80;
44
5pub const panic = common.panic;
6
7comptime {5comptime {
8 @export(&__trunctfxf2, .{ .name = "__trunctfxf2", .linkage = common.linkage, .visibility = common.visibility });6 @export(&__trunctfxf2, .{ .name = "__trunctfxf2", .linkage = common.linkage, .visibility = common.visibility });
9}7}
lib/compiler_rt/truncxfdf2.zig-2
...@@ -1,8 +1,6 @@...@@ -1,8 +1,6 @@
1const common = @import("./common.zig");1const common = @import("./common.zig");
2const trunc_f80 = @import("./truncf.zig").trunc_f80;2const trunc_f80 = @import("./truncf.zig").trunc_f80;
33
4pub const panic = common.panic;
5
6comptime {4comptime {
7 @export(&__truncxfdf2, .{ .name = "__truncxfdf2", .linkage = common.linkage, .visibility = common.visibility });5 @export(&__truncxfdf2, .{ .name = "__truncxfdf2", .linkage = common.linkage, .visibility = common.visibility });
8}6}
lib/compiler_rt/truncxfhf2.zig-2
...@@ -1,8 +1,6 @@...@@ -1,8 +1,6 @@
1const common = @import("./common.zig");1const common = @import("./common.zig");
2const trunc_f80 = @import("./truncf.zig").trunc_f80;2const trunc_f80 = @import("./truncf.zig").trunc_f80;
33
4pub const panic = common.panic;
5
6comptime {4comptime {
7 @export(&__truncxfhf2, .{ .name = "__truncxfhf2", .linkage = common.linkage, .visibility = common.visibility });5 @export(&__truncxfhf2, .{ .name = "__truncxfhf2", .linkage = common.linkage, .visibility = common.visibility });
8}6}
lib/compiler_rt/truncxfsf2.zig-2
...@@ -1,8 +1,6 @@...@@ -1,8 +1,6 @@
1const common = @import("./common.zig");1const common = @import("./common.zig");
2const trunc_f80 = @import("./truncf.zig").trunc_f80;2const trunc_f80 = @import("./truncf.zig").trunc_f80;
33
4pub const panic = common.panic;
5
6comptime {4comptime {
7 @export(&__truncxfsf2, .{ .name = "__truncxfsf2", .linkage = common.linkage, .visibility = common.visibility });5 @export(&__truncxfsf2, .{ .name = "__truncxfsf2", .linkage = common.linkage, .visibility = common.visibility });
8}6}
lib/compiler_rt/udivmodti4.zig-2
...@@ -3,8 +3,6 @@ const builtin = @import("builtin");...@@ -3,8 +3,6 @@ const builtin = @import("builtin");
3const udivmod = @import("udivmod.zig").udivmod;3const udivmod = @import("udivmod.zig").udivmod;
4const common = @import("common.zig");4const common = @import("common.zig");
55
6pub const panic = common.panic;
7
8comptime {6comptime {
9 if (common.want_windows_v2u64_abi) {7 if (common.want_windows_v2u64_abi) {
10 @export(&__udivmodti4_windows_x86_64, .{ .name = "__udivmodti4", .linkage = common.linkage, .visibility = common.visibility });8 @export(&__udivmodti4_windows_x86_64, .{ .name = "__udivmodti4", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/udivti3.zig-2
...@@ -3,8 +3,6 @@ const builtin = @import("builtin");...@@ -3,8 +3,6 @@ const builtin = @import("builtin");
3const udivmod = @import("udivmod.zig").udivmod;3const udivmod = @import("udivmod.zig").udivmod;
4const common = @import("common.zig");4const common = @import("common.zig");
55
6pub const panic = common.panic;
7
8comptime {6comptime {
9 if (common.want_windows_v2u64_abi) {7 if (common.want_windows_v2u64_abi) {
10 @export(&__udivti3_windows_x86_64, .{ .name = "__udivti3", .linkage = common.linkage, .visibility = common.visibility });8 @export(&__udivti3_windows_x86_64, .{ .name = "__udivti3", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/umodti3.zig-2
...@@ -3,8 +3,6 @@ const builtin = @import("builtin");...@@ -3,8 +3,6 @@ const builtin = @import("builtin");
3const udivmod = @import("udivmod.zig").udivmod;3const udivmod = @import("udivmod.zig").udivmod;
4const common = @import("common.zig");4const common = @import("common.zig");
55
6pub const panic = common.panic;
7
8comptime {6comptime {
9 if (common.want_windows_v2u64_abi) {7 if (common.want_windows_v2u64_abi) {
10 @export(&__umodti3_windows_x86_64, .{ .name = "__umodti3", .linkage = common.linkage, .visibility = common.visibility });8 @export(&__umodti3_windows_x86_64, .{ .name = "__umodti3", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/unorddf2.zig-2
...@@ -1,8 +1,6 @@...@@ -1,8 +1,6 @@
1const common = @import("./common.zig");1const common = @import("./common.zig");
2const comparef = @import("./comparef.zig");2const comparef = @import("./comparef.zig");
33
4pub const panic = common.panic;
5
6comptime {4comptime {
7 if (common.want_aeabi) {5 if (common.want_aeabi) {
8 @export(&__aeabi_dcmpun, .{ .name = "__aeabi_dcmpun", .linkage = common.linkage, .visibility = common.visibility });6 @export(&__aeabi_dcmpun, .{ .name = "__aeabi_dcmpun", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/unordhf2.zig-2
...@@ -1,8 +1,6 @@...@@ -1,8 +1,6 @@
1const common = @import("./common.zig");1const common = @import("./common.zig");
2const comparef = @import("./comparef.zig");2const comparef = @import("./comparef.zig");
33
4pub const panic = common.panic;
5
6comptime {4comptime {
7 @export(&__unordhf2, .{ .name = "__unordhf2", .linkage = common.linkage, .visibility = common.visibility });5 @export(&__unordhf2, .{ .name = "__unordhf2", .linkage = common.linkage, .visibility = common.visibility });
8}6}
lib/compiler_rt/unordsf2.zig-2
...@@ -1,8 +1,6 @@...@@ -1,8 +1,6 @@
1const common = @import("./common.zig");1const common = @import("./common.zig");
2const comparef = @import("./comparef.zig");2const comparef = @import("./comparef.zig");
33
4pub const panic = common.panic;
5
6comptime {4comptime {
7 if (common.want_aeabi) {5 if (common.want_aeabi) {
8 @export(&__aeabi_fcmpun, .{ .name = "__aeabi_fcmpun", .linkage = common.linkage, .visibility = common.visibility });6 @export(&__aeabi_fcmpun, .{ .name = "__aeabi_fcmpun", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/unordtf2.zig-2
...@@ -1,8 +1,6 @@...@@ -1,8 +1,6 @@
1const common = @import("./common.zig");1const common = @import("./common.zig");
2const comparef = @import("./comparef.zig");2const comparef = @import("./comparef.zig");
33
4pub const panic = common.panic;
5
6comptime {4comptime {
7 if (common.want_ppc_abi) {5 if (common.want_ppc_abi) {
8 @export(&__unordtf2, .{ .name = "__unordkf2", .linkage = common.linkage, .visibility = common.visibility });6 @export(&__unordtf2, .{ .name = "__unordkf2", .linkage = common.linkage, .visibility = common.visibility });
lib/compiler_rt/unordxf2.zig-2
...@@ -1,8 +1,6 @@...@@ -1,8 +1,6 @@
1const common = @import("./common.zig");1const common = @import("./common.zig");
2const comparef = @import("./comparef.zig");2const comparef = @import("./comparef.zig");
33
4pub const panic = common.panic;
5
6comptime {4comptime {
7 @export(&__unordxf2, .{ .name = "__unordxf2", .linkage = common.linkage, .visibility = common.visibility });5 @export(&__unordxf2, .{ .name = "__unordxf2", .linkage = common.linkage, .visibility = common.visibility });
8}6}
lib/init/src/main.zig+11-9
...@@ -3,19 +3,21 @@ const Io = std.Io;...@@ -3,19 +3,21 @@ const Io = std.Io;
33
4const _NAME = @import(".NAME");4const _NAME = @import(".NAME");
55
6pub fn main() !void {6pub fn main(init: std.process.Init) !void {
7 // Prints to stderr, unbuffered, ignoring potential errors.7 // Prints to stderr, unbuffered, ignoring potential errors.
8 std.debug.print("All your {s} are belong to us.\n", .{"codebase"});8 std.debug.print("All your {s} are belong to us.\n", .{"codebase"});
99
10 // In order to allocate memory we must construct an `Allocator` instance.10 // This is appropriate for anything that lives as long as the process.
11 var debug_allocator: std.heap.DebugAllocator(.{}) = .init;11 const arena: std.mem.Allocator = init.arena.allocator();
12 defer _ = debug_allocator.deinit(); // This checks for leaks.
13 const gpa = debug_allocator.allocator();
1412
15 // In order to do I/O operations we must construct an `Io` instance.13 // Accessing command line arguments:
16 var threaded: std.Io.Threaded = .init(gpa, .{});14 const args = try init.minimal.args.toSlice(arena);
17 defer threaded.deinit();15 for (args) |arg| {
18 const io = threaded.io();16 std.log.info("arg: {s}", .{arg});
17 }
18
19 // In order to do I/O operations need an `Io` instance.
20 const io = init.io;
1921
20 // Stdout is for the actual output of your application, for example if you22 // Stdout is for the actual output of your application, for example if you
21 // are implementing gzip, then only the compressed bytes should be sent to23 // are implementing gzip, then only the compressed bytes should be sent to
lib/std/Build.zig+29-35
...@@ -12,7 +12,6 @@ const StringHashMap = std.StringHashMap;...@@ -12,7 +12,6 @@ const StringHashMap = std.StringHashMap;
12const Allocator = std.mem.Allocator;12const Allocator = std.mem.Allocator;
13const Target = std.Target;13const Target = std.Target;
14const process = std.process;14const process = std.process;
15const EnvMap = std.process.EnvMap;
16const File = std.Io.File;15const File = std.Io.File;
17const Sha256 = std.crypto.hash.sha2.Sha256;16const Sha256 = std.crypto.hash.sha2.Sha256;
18const ArrayList = std.ArrayList;17const ArrayList = std.ArrayList;
...@@ -118,7 +117,7 @@ pub const Graph = struct {...@@ -118,7 +117,7 @@ pub const Graph = struct {
118 debug_compiler_runtime_libs: bool = false,117 debug_compiler_runtime_libs: bool = false,
119 cache: Cache,118 cache: Cache,
120 zig_exe: [:0]const u8,119 zig_exe: [:0]const u8,
121 env_map: EnvMap,120 environ_map: process.Environ.Map,
122 global_cache_root: Cache.Directory,121 global_cache_root: Cache.Directory,
123 zig_lib_directory: Cache.Directory,122 zig_lib_directory: Cache.Directory,
124 needed_lazy_dependencies: std.StringArrayHashMapUnmanaged(void) = .empty,123 needed_lazy_dependencies: std.StringArrayHashMapUnmanaged(void) = .empty,
...@@ -190,7 +189,7 @@ pub const RunError = error{...@@ -190,7 +189,7 @@ pub const RunError = error{
190 ExitCodeFailure,189 ExitCodeFailure,
191 ProcessTerminated,190 ProcessTerminated,
192 ExecNotSupported,191 ExecNotSupported,
193} || std.process.Child.SpawnError;192} || std.process.SpawnError;
194193
195pub const PkgConfigError = error{194pub const PkgConfigError = error{
196 PkgConfigCrashed,195 PkgConfigCrashed,
...@@ -289,7 +288,7 @@ pub fn create(...@@ -289,7 +288,7 @@ pub fn create(
289 .lib_dir = undefined,288 .lib_dir = undefined,
290 .exe_dir = undefined,289 .exe_dir = undefined,
291 .h_dir = undefined,290 .h_dir = undefined,
292 .dest_dir = graph.env_map.get("DESTDIR"),291 .dest_dir = graph.environ_map.get("DESTDIR"),
293 .install_tls = .{292 .install_tls = .{
294 .step = .init(.{293 .step = .init(.{
295 .id = TopLevelStep.base_id,294 .id = TopLevelStep.base_id,
...@@ -1738,8 +1737,7 @@ pub fn pathFromRoot(b: *Build, sub_path: []const u8) []u8 {...@@ -1738,8 +1737,7 @@ pub fn pathFromRoot(b: *Build, sub_path: []const u8) []u8 {
1738}1737}
17391738
1740fn pathFromCwd(b: *Build, sub_path: []const u8) []u8 {1739fn pathFromCwd(b: *Build, sub_path: []const u8) []u8 {
1741 const cwd = process.getCwdAlloc(b.allocator) catch @panic("OOM");1740 return b.pathResolve(&.{ b.graph.cache.cwd, sub_path });
1742 return b.pathResolve(&.{ cwd, sub_path });
1743}1741}
17441742
1745pub fn pathJoin(b: *Build, paths: []const []const u8) []u8 {1743pub 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 {...@@ -1755,7 +1753,7 @@ pub fn fmt(b: *Build, comptime format: []const u8, args: anytype) []u8 {
1755}1753}
17561754
1757fn supportedWindowsProgramExtension(ext: []const u8) bool {1755fn supportedWindowsProgramExtension(ext: []const u8) bool {
1758 inline for (@typeInfo(std.process.Child.WindowsExtension).@"enum".fields) |field| {1756 inline for (@typeInfo(std.process.WindowsExtension).@"enum".fields) |field| {
1759 if (std.ascii.eqlIgnoreCase(ext, "." ++ field.name)) return true;1757 if (std.ascii.eqlIgnoreCase(ext, "." ++ field.name)) return true;
1760 }1758 }
1761 return false;1759 return false;
...@@ -1773,7 +1771,7 @@ fn tryFindProgram(b: *Build, full_path: []const u8) ?[]const u8 {...@@ -1773,7 +1771,7 @@ fn tryFindProgram(b: *Build, full_path: []const u8) ?[]const u8 {
1773 }1771 }
17741772
1775 if (builtin.os.tag == .windows) {1773 if (builtin.os.tag == .windows) {
1776 if (b.graph.env_map.get("PATHEXT")) |PATHEXT| {1774 if (b.graph.environ_map.get("PATHEXT")) |PATHEXT| {
1777 var it = mem.tokenizeScalar(u8, PATHEXT, fs.path.delimiter);1775 var it = mem.tokenizeScalar(u8, PATHEXT, fs.path.delimiter);
17781776
1779 while (it.next()) |ext| {1777 while (it.next()) |ext| {
...@@ -1804,7 +1802,7 @@ pub fn findProgram(b: *Build, names: []const []const u8, paths: []const []const...@@ -1804,7 +1802,7 @@ pub fn findProgram(b: *Build, names: []const []const u8, paths: []const []const
1804 return tryFindProgram(b, b.pathJoin(&.{ search_prefix, "bin", name })) orelse continue;1802 return tryFindProgram(b, b.pathJoin(&.{ search_prefix, "bin", name })) orelse continue;
1805 }1803 }
1806 }1804 }
1807 if (b.graph.env_map.get("PATH")) |PATH| {1805 if (b.graph.environ_map.get("PATH")) |PATH| {
1808 for (names) |name| {1806 for (names) |name| {
1809 if (fs.path.isAbsolute(name)) {1807 if (fs.path.isAbsolute(name)) {
1810 return name;1808 return name;
...@@ -1830,24 +1828,26 @@ pub fn runAllowFail(...@@ -1830,24 +1828,26 @@ pub fn runAllowFail(
1830 b: *Build,1828 b: *Build,
1831 argv: []const []const u8,1829 argv: []const []const u8,
1832 out_code: *u8,1830 out_code: *u8,
1833 stderr_behavior: std.process.Child.StdIo,1831 stderr_behavior: std.process.SpawnOptions.StdIo,
1834) RunError![]u8 {1832) RunError![]u8 {
1835 assert(argv.len != 0);1833 assert(argv.len != 0);
18361834
1837 if (!process.can_spawn)1835 if (!process.can_spawn)
1838 return error.ExecNotSupported;1836 return error.ExecNotSupported;
18391837
1840 const io = b.graph.io;1838 const graph = b.graph;
1839 const io = graph.io;
18411840
1842 const max_output_size = 400 * 1024;1841 const max_output_size = 400 * 1024;
1843 var child = std.process.Child.init(argv, b.allocator);1842 try Step.handleVerbose2(b, null, &graph.environ_map, argv);
1844 child.stdin_behavior = .Ignore;1843
1845 child.stdout_behavior = .Pipe;1844 var child = try std.process.spawn(io, .{
1846 child.stderr_behavior = stderr_behavior;1845 .argv = argv,
1847 child.env_map = &b.graph.env_map;1846 .environ_map = &graph.environ_map,
18481847 .stdin = .ignore,
1849 try Step.handleVerbose2(b, null, child.env_map, argv);1848 .stdout = .pipe,
1850 try child.spawn(io);1849 .stderr = stderr_behavior,
1850 });
18511851
1852 var stdout_reader = child.stdout.?.readerStreaming(io, &.{});1852 var stdout_reader = child.stdout.?.readerStreaming(io, &.{});
1853 const stdout = stdout_reader.interface.allocRemaining(b.allocator, .limited(max_output_size)) catch {1853 const stdout = stdout_reader.interface.allocRemaining(b.allocator, .limited(max_output_size)) catch {
...@@ -1857,14 +1857,18 @@ pub fn runAllowFail(...@@ -1857,14 +1857,18 @@ pub fn runAllowFail(
18571857
1858 const term = try child.wait(io);1858 const term = try child.wait(io);
1859 switch (term) {1859 switch (term) {
1860 .Exited => |code| {1860 .exited => |code| {
1861 if (code != 0) {1861 if (code != 0) {
1862 out_code.* = @as(u8, @truncate(code));1862 out_code.* = @as(u8, @truncate(code));
1863 return error.ExitCodeFailure;1863 return error.ExitCodeFailure;
1864 }1864 }
1865 return stdout;1865 return stdout;
1866 },1866 },
1867 .Signal, .Stopped, .Unknown => |code| {1867 .signal => |sig| {
1868 out_code.* = @as(u8, @truncate(@intFromEnum(sig)));
1869 return error.ProcessTerminated;
1870 },
1871 .stopped, .unknown => |code| {
1868 out_code.* = @as(u8, @truncate(code));1872 out_code.* = @as(u8, @truncate(code));
1869 return error.ProcessTerminated;1873 return error.ProcessTerminated;
1870 },1874 },
...@@ -1875,21 +1879,11 @@ pub fn runAllowFail(...@@ -1875,21 +1879,11 @@ pub fn runAllowFail(
1875/// inside step make() functions. If any errors occur, it fails the build with1879/// inside step make() functions. If any errors occur, it fails the build with
1876/// a helpful message.1880/// a helpful message.
1877pub fn run(b: *Build, argv: []const []const u8) []u8 {1881pub fn run(b: *Build, argv: []const []const u8) []u8 {
1878 if (!process.can_spawn) {
1879 std.debug.print("unable to spawn the following command: cannot spawn child process\n{s}\n", .{
1880 try Step.allocPrintCmd(b.allocator, null, argv),
1881 });
1882 process.exit(1);
1883 }
1884
1885 var code: u8 = undefined;1882 var code: u8 = undefined;
1886 return b.runAllowFail(argv, &code, .Inherit) catch |err| {1883 return b.runAllowFail(argv, &code, .inherit) catch |err| process.fatal(
1887 const printed_cmd = Step.allocPrintCmd(b.allocator, null, argv) catch @panic("OOM");1884 "the following command failed with {t}:\n{s}",
1888 std.debug.print("unable to spawn the following command: {s}\n{s}\n", .{1885 .{ err, Step.allocPrintCmd(b.allocator, null, null, argv) catch @panic("OOM") },
1889 @errorName(err), printed_cmd,1886 );
1890 });
1891 process.exit(1);
1892 };
1893}1887}
18941888
1895pub fn addSearchPrefix(b: *Build, search_prefix: []const u8) void {1889pub fn addSearchPrefix(b: *Build, search_prefix: []const u8) void {
lib/std/Build/Cache.zig+27-8
...@@ -30,6 +30,8 @@ mutex: Io.Mutex = .init,...@@ -30,6 +30,8 @@ mutex: Io.Mutex = .init,
30/// and usefulness of the cache for advanced use cases.30/// and usefulness of the cache for advanced use cases.
31prefixes_buffer: [4]Directory = undefined,31prefixes_buffer: [4]Directory = undefined,
32prefixes_len: usize = 0,32prefixes_len: usize = 0,
33/// Used to identify prefixes. References external memory.
34cwd: []const u8,
3335
34pub const Path = @import("Cache/Path.zig");36pub const Path = @import("Cache/Path.zig");
35pub const Directory = @import("Cache/Directory.zig");37pub const Directory = @import("Cache/Directory.zig");
...@@ -78,11 +80,12 @@ fn findPrefix(cache: *const Cache, file_path: []const u8) !PrefixedPath {...@@ -78,11 +80,12 @@ fn findPrefix(cache: *const Cache, file_path: []const u8) !PrefixedPath {
78/// Takes ownership of `resolved_path` on success.80/// Takes ownership of `resolved_path` on success.
79fn findPrefixResolved(cache: *const Cache, resolved_path: []u8) !PrefixedPath {81fn findPrefixResolved(cache: *const Cache, resolved_path: []u8) !PrefixedPath {
80 const gpa = cache.gpa;82 const gpa = cache.gpa;
83 const cwd = cache.cwd;
81 const prefixes_slice = cache.prefixes();84 const prefixes_slice = cache.prefixes();
82 var i: u8 = 1; // Start at 1 to skip over checking the null prefix.85 var i: u8 = 1; // Start at 1 to skip over checking the null prefix.
83 while (i < prefixes_slice.len) : (i += 1) {86 while (i < prefixes_slice.len) : (i += 1) {
84 const p = prefixes_slice[i].path.?;87 const p = prefixes_slice[i].path.?;
85 const sub_path = getPrefixSubpath(gpa, p, resolved_path) catch |err| switch (err) {88 const sub_path = getPrefixSubpath(gpa, cwd, p, resolved_path) catch |err| switch (err) {
86 error.NotASubPath => continue,89 error.NotASubPath => continue,
87 else => |e| return e,90 else => |e| return e,
88 };91 };
...@@ -100,10 +103,10 @@ fn findPrefixResolved(cache: *const Cache, resolved_path: []u8) !PrefixedPath {...@@ -100,10 +103,10 @@ fn findPrefixResolved(cache: *const Cache, resolved_path: []u8) !PrefixedPath {
100 };103 };
101}104}
102105
103fn getPrefixSubpath(allocator: Allocator, prefix: []const u8, path: []u8) ![]u8 {106fn getPrefixSubpath(gpa: Allocator, cwd: []const u8, prefix: []const u8, path: []u8) ![]u8 {
104 const relative = try std.fs.path.relative(allocator, prefix, path);107 const relative = try std.fs.path.relative(gpa, cwd, null, prefix, path);
105 errdefer allocator.free(relative);108 errdefer gpa.free(relative);
106 var component_iterator = std.fs.path.NativeComponentIterator.init(relative);109 var component_iterator: std.fs.path.NativeComponentIterator = .init(relative);
107 if (component_iterator.root() != null) {110 if (component_iterator.root() != null) {
108 return error.NotASubPath;111 return error.NotASubPath;
109 }112 }
...@@ -1307,11 +1310,14 @@ fn testGetCurrentFileTimestamp(io: Io, dir: Io.Dir) !Io.Timestamp {...@@ -1307,11 +1310,14 @@ fn testGetCurrentFileTimestamp(io: Io, dir: Io.Dir) !Io.Timestamp {
1307}1310}
13081311
1309test "cache file and then recall it" {1312test "cache file and then recall it" {
1310 const io = std.testing.io;1313 const io = testing.io;
13111314
1312 var tmp = testing.tmpDir(.{});1315 var tmp = testing.tmpDir(.{});
1313 defer tmp.cleanup();1316 defer tmp.cleanup();
13141317
1318 const cwd = try std.process.getCwdAlloc(testing.allocator);
1319 defer testing.allocator.free(cwd);
1320
1315 const temp_file = "test.txt";1321 const temp_file = "test.txt";
1316 const temp_manifest_dir = "temp_manifest_dir";1322 const temp_manifest_dir = "temp_manifest_dir";
13171323
...@@ -1331,6 +1337,7 @@ test "cache file and then recall it" {...@@ -1331,6 +1337,7 @@ test "cache file and then recall it" {
1331 .io = io,1337 .io = io,
1332 .gpa = testing.allocator,1338 .gpa = testing.allocator,
1333 .manifest_dir = try tmp.dir.createDirPathOpen(io, temp_manifest_dir, .{}),1339 .manifest_dir = try tmp.dir.createDirPathOpen(io, temp_manifest_dir, .{}),
1340 .cwd = cwd,
1334 };1341 };
1335 cache.addPrefix(.{ .path = null, .handle = tmp.dir });1342 cache.addPrefix(.{ .path = null, .handle = tmp.dir });
1336 defer cache.manifest_dir.close(io);1343 defer cache.manifest_dir.close(io);
...@@ -1371,11 +1378,14 @@ test "cache file and then recall it" {...@@ -1371,11 +1378,14 @@ test "cache file and then recall it" {
1371}1378}
13721379
1373test "check that changing a file makes cache fail" {1380test "check that changing a file makes cache fail" {
1374 const io = std.testing.io;1381 const io = testing.io;
13751382
1376 var tmp = testing.tmpDir(.{});1383 var tmp = testing.tmpDir(.{});
1377 defer tmp.cleanup();1384 defer tmp.cleanup();
13781385
1386 const cwd = try std.process.getCwdAlloc(testing.allocator);
1387 defer testing.allocator.free(cwd);
1388
1379 const temp_file = "cache_hash_change_file_test.txt";1389 const temp_file = "cache_hash_change_file_test.txt";
1380 const temp_manifest_dir = "cache_hash_change_file_manifest_dir";1390 const temp_manifest_dir = "cache_hash_change_file_manifest_dir";
1381 const original_temp_file_contents = "Hello, world!\n";1391 const original_temp_file_contents = "Hello, world!\n";
...@@ -1397,6 +1407,7 @@ test "check that changing a file makes cache fail" {...@@ -1397,6 +1407,7 @@ test "check that changing a file makes cache fail" {
1397 .io = io,1407 .io = io,
1398 .gpa = testing.allocator,1408 .gpa = testing.allocator,
1399 .manifest_dir = try tmp.dir.createDirPathOpen(io, temp_manifest_dir, .{}),1409 .manifest_dir = try tmp.dir.createDirPathOpen(io, temp_manifest_dir, .{}),
1410 .cwd = cwd,
1400 };1411 };
1401 cache.addPrefix(.{ .path = null, .handle = tmp.dir });1412 cache.addPrefix(.{ .path = null, .handle = tmp.dir });
1402 defer cache.manifest_dir.close(io);1413 defer cache.manifest_dir.close(io);
...@@ -1448,6 +1459,9 @@ test "no file inputs" {...@@ -1448,6 +1459,9 @@ test "no file inputs" {
1448 var tmp = testing.tmpDir(.{});1459 var tmp = testing.tmpDir(.{});
1449 defer tmp.cleanup();1460 defer tmp.cleanup();
14501461
1462 const cwd = try std.process.getCwdAlloc(testing.allocator);
1463 defer testing.allocator.free(cwd);
1464
1451 const temp_manifest_dir = "no_file_inputs_manifest_dir";1465 const temp_manifest_dir = "no_file_inputs_manifest_dir";
14521466
1453 var digest1: HexDigest = undefined;1467 var digest1: HexDigest = undefined;
...@@ -1457,6 +1471,7 @@ test "no file inputs" {...@@ -1457,6 +1471,7 @@ test "no file inputs" {
1457 .io = io,1471 .io = io,
1458 .gpa = testing.allocator,1472 .gpa = testing.allocator,
1459 .manifest_dir = try tmp.dir.createDirPathOpen(io, temp_manifest_dir, .{}),1473 .manifest_dir = try tmp.dir.createDirPathOpen(io, temp_manifest_dir, .{}),
1474 .cwd = cwd,
1460 };1475 };
1461 cache.addPrefix(.{ .path = null, .handle = tmp.dir });1476 cache.addPrefix(.{ .path = null, .handle = tmp.dir });
1462 defer cache.manifest_dir.close(io);1477 defer cache.manifest_dir.close(io);
...@@ -1489,11 +1504,14 @@ test "no file inputs" {...@@ -1489,11 +1504,14 @@ test "no file inputs" {
1489}1504}
14901505
1491test "Manifest with files added after initial hash work" {1506test "Manifest with files added after initial hash work" {
1492 const io = std.testing.io;1507 const io = testing.io;
14931508
1494 var tmp = testing.tmpDir(.{});1509 var tmp = testing.tmpDir(.{});
1495 defer tmp.cleanup();1510 defer tmp.cleanup();
14961511
1512 const cwd = try std.process.getCwdAlloc(testing.allocator);
1513 defer testing.allocator.free(cwd);
1514
1497 const temp_file1 = "cache_hash_post_file_test1.txt";1515 const temp_file1 = "cache_hash_post_file_test1.txt";
1498 const temp_file2 = "cache_hash_post_file_test2.txt";1516 const temp_file2 = "cache_hash_post_file_test2.txt";
1499 const temp_manifest_dir = "cache_hash_post_file_manifest_dir";1517 const temp_manifest_dir = "cache_hash_post_file_manifest_dir";
...@@ -1516,6 +1534,7 @@ test "Manifest with files added after initial hash work" {...@@ -1516,6 +1534,7 @@ test "Manifest with files added after initial hash work" {
1516 .io = io,1534 .io = io,
1517 .gpa = testing.allocator,1535 .gpa = testing.allocator,
1518 .manifest_dir = try tmp.dir.createDirPathOpen(io, temp_manifest_dir, .{}),1536 .manifest_dir = try tmp.dir.createDirPathOpen(io, temp_manifest_dir, .{}),
1537 .cwd = cwd,
1519 };1538 };
1520 cache.addPrefix(.{ .path = null, .handle = tmp.dir });1539 cache.addPrefix(.{ .path = null, .handle = tmp.dir });
1521 defer cache.manifest_dir.close(io);1540 defer cache.manifest_dir.close(io);
lib/std/Build/Step.zig+43-42
...@@ -348,19 +348,21 @@ pub fn captureChildProcess(...@@ -348,19 +348,21 @@ pub fn captureChildProcess(
348 gpa: Allocator,348 gpa: Allocator,
349 progress_node: std.Progress.Node,349 progress_node: std.Progress.Node,
350 argv: []const []const u8,350 argv: []const []const u8,
351) !std.process.Child.RunResult {351) !std.process.RunResult {
352 const arena = s.owner.allocator;352 const graph = s.owner.graph;
353 const io = s.owner.graph.io;353 const arena = graph.arena;
354 const io = graph.io;
354355
355 // If an error occurs, it's happened in this command:356 // If an error occurs, it's happened in this command:
356 assert(s.result_failed_command == null);357 assert(s.result_failed_command == null);
357 s.result_failed_command = try allocPrintCmd(gpa, null, argv);358 s.result_failed_command = try allocPrintCmd(gpa, null, null, argv);
358359
359 try handleChildProcUnsupported(s);360 try handleChildProcUnsupported(s);
360 try handleVerbose(s.owner, null, argv);361 try handleVerbose(s.owner, null, argv);
361362
362 const result = std.process.Child.run(arena, io, .{363 const result = std.process.run(arena, io, .{
363 .argv = argv,364 .argv = argv,
365 .environ_map = &graph.environ_map,
364 .progress_node = progress_node,366 .progress_node = progress_node,
365 }) catch |err| return s.fail("failed to run {s}: {t}", .{ argv[0], err });367 }) catch |err| return s.fail("failed to run {s}: {t}", .{ argv[0], err });
366368
...@@ -406,7 +408,7 @@ pub fn evalZigProcess(...@@ -406,7 +408,7 @@ pub fn evalZigProcess(
406408
407 // If an error occurs, it's happened in this command:409 // If an error occurs, it's happened in this command:
408 assert(s.result_failed_command == null);410 assert(s.result_failed_command == null);
409 s.result_failed_command = try allocPrintCmd(gpa, null, argv);411 s.result_failed_command = try allocPrintCmd(gpa, null, null, argv);
410412
411 if (s.getZigProcess()) |zp| update: {413 if (s.getZigProcess()) |zp| update: {
412 assert(watch);414 assert(watch);
...@@ -442,35 +444,34 @@ pub fn evalZigProcess(...@@ -442,35 +444,34 @@ pub fn evalZigProcess(
442 return result;444 return result;
443 }445 }
444 assert(argv.len != 0);446 assert(argv.len != 0);
445 const arena = b.allocator;
446447
447 try handleChildProcUnsupported(s);448 try handleChildProcUnsupported(s);
448 try handleVerbose(s.owner, null, argv);449 try handleVerbose(s.owner, null, argv);
449450
450 var child = std.process.Child.init(argv, arena);451 const zp = try gpa.create(ZigProcess);
451 child.env_map = &b.graph.env_map;452 defer if (!watch) gpa.destroy(zp);
452 child.stdin_behavior = .Pipe;
453 child.stdout_behavior = .Pipe;
454 child.stderr_behavior = .Pipe;
455 child.request_resource_usage_statistics = true;
456 child.progress_node = prog_node;
457453
458 child.spawn(io) catch |err| return s.fail("failed to spawn zig compiler {s}: {t}", .{ argv[0], err });454 zp.child = std.process.spawn(io, .{
455 .argv = argv,
456 .environ_map = &b.graph.environ_map,
457 .stdin = .pipe,
458 .stdout = .pipe,
459 .stderr = .pipe,
460 .request_resource_usage_statistics = true,
461 .progress_node = prog_node,
462 }) catch |err| return s.fail("failed to spawn zig compiler {s}: {t}", .{ argv[0], err });
463 defer if (!watch) zp.child.kill(io);
459464
460 const zp = try gpa.create(ZigProcess);
461 zp.* = .{465 zp.* = .{
462 .child = child,466 .child = zp.child,
463 .poller = Io.poll(gpa, ZigProcess.StreamEnum, .{467 .poller = Io.poll(gpa, ZigProcess.StreamEnum, .{
464 .stdout = child.stdout.?,468 .stdout = zp.child.stdout.?,
465 .stderr = child.stderr.?,469 .stderr = zp.child.stderr.?,
466 }),470 }),
467 .progress_ipc_fd = if (std.Progress.have_ipc) child.progress_node.getIpcFd() else {},471 .progress_ipc_fd = if (std.Progress.have_ipc) prog_node.getIpcFd() else {},
468 };472 };
469 if (watch) s.setZigProcess(zp);473 if (watch) s.setZigProcess(zp);
470 defer if (!watch) {474 defer if (!watch) zp.poller.deinit();
471 zp.poller.deinit();
472 gpa.destroy(zp);
473 };
474475
475 const result = try zigProcessUpdate(s, zp, watch, web_server, gpa);476 const result = try zigProcessUpdate(s, zp, watch, web_server, gpa);
476477
...@@ -486,7 +487,7 @@ pub fn evalZigProcess(...@@ -486,7 +487,7 @@ pub fn evalZigProcess(
486487
487 // Special handling for Compile step that is expecting compile errors.488 // Special handling for Compile step that is expecting compile errors.
488 if (s.cast(Compile)) |compile| switch (term) {489 if (s.cast(Compile)) |compile| switch (term) {
489 .Exited => {490 .exited => {
490 // Note that the exit code may be 0 in this case due to the491 // Note that the exit code may be 0 in this case due to the
491 // compiler server protocol.492 // compiler server protocol.
492 if (compile.expect_errors != null) {493 if (compile.expect_errors != null) {
...@@ -692,13 +693,17 @@ pub fn handleVerbose(...@@ -692,13 +693,17 @@ pub fn handleVerbose(
692pub fn handleVerbose2(693pub fn handleVerbose2(
693 b: *Build,694 b: *Build,
694 opt_cwd: ?[]const u8,695 opt_cwd: ?[]const u8,
695 opt_env: ?*const std.process.EnvMap,696 opt_env: ?*const std.process.Environ.Map,
696 argv: []const []const u8,697 argv: []const []const u8,
697) error{OutOfMemory}!void {698) error{OutOfMemory}!void {
698 if (b.verbose) {699 if (b.verbose) {
700 const graph = b.graph;
699 // Intention of verbose is to print all sub-process command lines to701 // Intention of verbose is to print all sub-process command lines to
700 // stderr before spawning them.702 // stderr before spawning them.
701 const text = try allocPrintCmd2(b.allocator, opt_cwd, opt_env, argv);703 const text = try allocPrintCmd(b.allocator, opt_cwd, if (opt_env) |env| .{
704 .child = env,
705 .parent = &graph.environ_map,
706 } else null, argv);
702 std.debug.print("{s}\n", .{text});707 std.debug.print("{s}\n", .{text});
703 }708 }
704}709}
...@@ -714,12 +719,15 @@ pub inline fn handleChildProcUnsupported(s: *Step) error{ OutOfMemory, MakeFaile...@@ -714,12 +719,15 @@ pub inline fn handleChildProcUnsupported(s: *Step) error{ OutOfMemory, MakeFaile
714pub fn handleChildProcessTerm(s: *Step, term: std.process.Child.Term) error{ MakeFailed, OutOfMemory }!void {719pub fn handleChildProcessTerm(s: *Step, term: std.process.Child.Term) error{ MakeFailed, OutOfMemory }!void {
715 assert(s.result_failed_command != null);720 assert(s.result_failed_command != null);
716 switch (term) {721 switch (term) {
717 .Exited => |code| {722 .exited => |code| {
718 if (code != 0) {723 if (code != 0) {
719 return s.fail("process exited with error code {d}", .{code});724 return s.fail("process exited with error code {d}", .{code});
720 }725 }
721 },726 },
722 .Signal, .Stopped, .Unknown => {727 .signal => |sig| {
728 return s.fail("process terminated with signal {t}", .{sig});
729 },
730 .stopped, .unknown => {
723 return s.fail("process terminated unexpectedly", .{});731 return s.fail("process terminated unexpectedly", .{});
724 },732 },
725 }733 }
...@@ -728,15 +736,10 @@ pub fn handleChildProcessTerm(s: *Step, term: std.process.Child.Term) error{ Mak...@@ -728,15 +736,10 @@ pub fn handleChildProcessTerm(s: *Step, term: std.process.Child.Term) error{ Mak
728pub fn allocPrintCmd(736pub fn allocPrintCmd(
729 gpa: Allocator,737 gpa: Allocator,
730 opt_cwd: ?[]const u8,738 opt_cwd: ?[]const u8,
731 argv: []const []const u8,739 opt_env: ?struct {
732) Allocator.Error![]u8 {740 child: *const std.process.Environ.Map,
733 return allocPrintCmd2(gpa, opt_cwd, null, argv);741 parent: *const std.process.Environ.Map,
734}742 },
735
736pub fn allocPrintCmd2(
737 gpa: Allocator,
738 opt_cwd: ?[]const u8,
739 opt_env: ?*const std.process.EnvMap,
740 argv: []const []const u8,743 argv: []const []const u8,
741) Allocator.Error![]u8 {744) Allocator.Error![]u8 {
742 const shell = struct {745 const shell = struct {
...@@ -779,13 +782,11 @@ pub fn allocPrintCmd2(...@@ -779,13 +782,11 @@ pub fn allocPrintCmd2(
779 const writer = &aw.writer;782 const writer = &aw.writer;
780 if (opt_cwd) |cwd| writer.print("cd {s} && ", .{cwd}) catch return error.OutOfMemory;783 if (opt_cwd) |cwd| writer.print("cd {s} && ", .{cwd}) catch return error.OutOfMemory;
781 if (opt_env) |env| {784 if (opt_env) |env| {
782 var process_env_map = std.process.getEnvMap(gpa) catch std.process.EnvMap.init(gpa);785 var it = env.child.iterator();
783 defer process_env_map.deinit();
784 var it = env.iterator();
785 while (it.next()) |entry| {786 while (it.next()) |entry| {
786 const key = entry.key_ptr.*;787 const key = entry.key_ptr.*;
787 const value = entry.value_ptr.*;788 const value = entry.value_ptr.*;
788 if (process_env_map.get(key)) |process_value| {789 if (env.parent.get(key)) |process_value| {
789 if (std.mem.eql(u8, value, process_value)) continue;790 if (std.mem.eql(u8, value, process_value)) continue;
790 }791 }
791 writer.print("{s}=", .{key}) catch return error.OutOfMemory;792 writer.print("{s}=", .{key}) catch return error.OutOfMemory;
lib/std/Build/Step/Compile.zig+4-4
...@@ -741,13 +741,13 @@ fn runPkgConfig(compile: *Compile, lib_name: []const u8) !PkgConfigResult {...@@ -741,13 +741,13 @@ fn runPkgConfig(compile: *Compile, lib_name: []const u8) !PkgConfigResult {
741 };741 };
742742
743 var code: u8 = undefined;743 var code: u8 = undefined;
744 const pkg_config_exe = b.graph.env_map.get("PKG_CONFIG") orelse "pkg-config";744 const pkg_config_exe = b.graph.environ_map.get("PKG_CONFIG") orelse "pkg-config";
745 const stdout = if (b.runAllowFail(&[_][]const u8{745 const stdout = if (b.runAllowFail(&[_][]const u8{
746 pkg_config_exe,746 pkg_config_exe,
747 pkg_name,747 pkg_name,
748 "--cflags",748 "--cflags",
749 "--libs",749 "--libs",
750 }, &code, .Ignore)) |stdout| stdout else |err| switch (err) {750 }, &code, .ignore)) |stdout| stdout else |err| switch (err) {
751 error.ProcessTerminated => return error.PkgConfigCrashed,751 error.ProcessTerminated => return error.PkgConfigCrashed,
752 error.ExecNotSupported => return error.PkgConfigFailed,752 error.ExecNotSupported => return error.PkgConfigFailed,
753 error.ExitCodeFailure => return error.PkgConfigFailed,753 error.ExitCodeFailure => return error.PkgConfigFailed,
...@@ -1846,8 +1846,8 @@ pub fn doAtomicSymLinks(...@@ -1846,8 +1846,8 @@ pub fn doAtomicSymLinks(
1846}1846}
18471847
1848fn execPkgConfigList(b: *std.Build, out_code: *u8) (PkgConfigError || RunError)![]const PkgConfigPkg {1848fn execPkgConfigList(b: *std.Build, out_code: *u8) (PkgConfigError || RunError)![]const PkgConfigPkg {
1849 const pkg_config_exe = b.graph.env_map.get("PKG_CONFIG") orelse "pkg-config";1849 const pkg_config_exe = b.graph.environ_map.get("PKG_CONFIG") orelse "pkg-config";
1850 const stdout = try b.runAllowFail(&[_][]const u8{ pkg_config_exe, "--list-all" }, out_code, .Ignore);1850 const stdout = try b.runAllowFail(&[_][]const u8{ pkg_config_exe, "--list-all" }, out_code, .ignore);
1851 var list = std.array_list.Managed(PkgConfigPkg).init(b.allocator);1851 var list = std.array_list.Managed(PkgConfigPkg).init(b.allocator);
1852 errdefer list.deinit();1852 errdefer list.deinit();
1853 var line_it = mem.tokenizeAny(u8, stdout, "\r\n");1853 var line_it = mem.tokenizeAny(u8, stdout, "\r\n");
lib/std/Build/Step/Fmt.zig+1-1
...@@ -69,7 +69,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void {...@@ -69,7 +69,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
6969
70 const run_result = try step.captureChildProcess(options.gpa, prog_node, argv.items);70 const run_result = try step.captureChildProcess(options.gpa, prog_node, argv.items);
71 if (fmt.check) switch (run_result.term) {71 if (fmt.check) switch (run_result.term) {
72 .Exited => |code| if (code != 0 and run_result.stdout.len != 0) {72 .exited => |code| if (code != 0 and run_result.stdout.len != 0) {
73 var it = std.mem.tokenizeScalar(u8, run_result.stdout, '\n');73 var it = std.mem.tokenizeScalar(u8, run_result.stdout, '\n');
74 while (it.next()) |bad_file_name| {74 while (it.next()) |bad_file_name| {
75 try step.addError("{s}: non-conforming formatting", .{bad_file_name});75 try step.addError("{s}: non-conforming formatting", .{bad_file_name});
lib/std/Build/Step/Options.zig+5-1
...@@ -537,6 +537,9 @@ test Options {...@@ -537,6 +537,9 @@ test Options {
537 var arena = std.heap.ArenaAllocator.init(std.testing.allocator);537 var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
538 defer arena.deinit();538 defer arena.deinit();
539539
540 const cwd = try std.process.getCwdAlloc(std.testing.allocator);
541 defer std.testing.allocator.free(cwd);
542
540 var graph: std.Build.Graph = .{543 var graph: std.Build.Graph = .{
541 .io = io,544 .io = io,
542 .arena = arena.allocator(),545 .arena = arena.allocator(),
...@@ -544,9 +547,10 @@ test Options {...@@ -544,9 +547,10 @@ test Options {
544 .io = io,547 .io = io,
545 .gpa = arena.allocator(),548 .gpa = arena.allocator(),
546 .manifest_dir = Io.Dir.cwd(),549 .manifest_dir = Io.Dir.cwd(),
550 .cwd = cwd,
547 },551 },
548 .zig_exe = "test",552 .zig_exe = "test",
549 .env_map = std.process.EnvMap.init(arena.allocator()),553 .environ_map = std.process.Environ.Map.init(arena.allocator()),
550 .global_cache_root = .{ .path = "test", .handle = Io.Dir.cwd() },554 .global_cache_root = .{ .path = "test", .handle = Io.Dir.cwd() },
551 .host = .{555 .host = .{
552 .query = .{},556 .query = .{},
lib/std/Build/Step/Run.zig+132-148
...@@ -8,7 +8,7 @@ const Step = std.Build.Step;...@@ -8,7 +8,7 @@ const Step = std.Build.Step;
8const Dir = std.Io.Dir;8const Dir = std.Io.Dir;
9const mem = std.mem;9const mem = std.mem;
10const process = std.process;10const process = std.process;
11const EnvMap = std.process.EnvMap;11const EnvMap = std.process.Environ.Map;
12const assert = std.debug.assert;12const assert = std.debug.assert;
13const Path = std.Build.Cache.Path;13const Path = std.Build.Cache.Path;
1414
...@@ -23,7 +23,7 @@ argv: std.ArrayList(Arg),...@@ -23,7 +23,7 @@ argv: std.ArrayList(Arg),
23cwd: ?Build.LazyPath,23cwd: ?Build.LazyPath,
2424
25/// Override this field to modify the environment, or use setEnvironmentVariable25/// Override this field to modify the environment, or use setEnvironmentVariable
26env_map: ?*EnvMap,26environ_map: ?*EnvMap,
2727
28/// Controls the `NO_COLOR` and `CLICOLOR_FORCE` environment variables.28/// Controls the `NO_COLOR` and `CLICOLOR_FORCE` environment variables.
29color: Color = .auto,29color: Color = .auto,
...@@ -149,7 +149,7 @@ pub const StdIo = union(enum) {...@@ -149,7 +149,7 @@ pub const StdIo = union(enum) {
149 expect_stderr_match: []const u8,149 expect_stderr_match: []const u8,
150 expect_stdout_exact: []const u8,150 expect_stdout_exact: []const u8,
151 expect_stdout_match: []const u8,151 expect_stdout_match: []const u8,
152 expect_term: std.process.Child.Term,152 expect_term: process.Child.Term,
153 };153 };
154};154};
155155
...@@ -215,7 +215,7 @@ pub fn create(owner: *std.Build, name: []const u8) *Run {...@@ -215,7 +215,7 @@ pub fn create(owner: *std.Build, name: []const u8) *Run {
215 }),215 }),
216 .argv = .{},216 .argv = .{},
217 .cwd = null,217 .cwd = null,
218 .env_map = null,218 .environ_map = null,
219 .disable_zig_progress = false,219 .disable_zig_progress = false,
220 .stdio = .infer_from_args,220 .stdio = .infer_from_args,
221 .stdin = .none,221 .stdin = .none,
...@@ -540,12 +540,12 @@ pub fn clearEnvironment(run: *Run) void {...@@ -540,12 +540,12 @@ pub fn clearEnvironment(run: *Run) void {
540 const b = run.step.owner;540 const b = run.step.owner;
541 const new_env_map = b.allocator.create(EnvMap) catch @panic("OOM");541 const new_env_map = b.allocator.create(EnvMap) catch @panic("OOM");
542 new_env_map.* = .init(b.allocator);542 new_env_map.* = .init(b.allocator);
543 run.env_map = new_env_map;543 run.environ_map = new_env_map;
544}544}
545545
546pub fn addPathDir(run: *Run, search_path: []const u8) void {546pub fn addPathDir(run: *Run, search_path: []const u8) void {
547 const b = run.step.owner;547 const b = run.step.owner;
548 const env_map = getEnvMapInternal(run);548 const environ_map = getEnvMapInternal(run);
549549
550 const use_wine = b.enable_wine and b.graph.host.result.os.tag != .windows and use_wine: switch (run.argv.items[0]) {550 const use_wine = b.enable_wine and b.graph.host.result.os.tag != .windows and use_wine: switch (run.argv.items[0]) {
551 .artifact => |p| p.artifact.rootModuleTarget().os.tag == .windows,551 .artifact => |p| p.artifact.rootModuleTarget().os.tag == .windows,
...@@ -562,7 +562,7 @@ pub fn addPathDir(run: *Run, search_path: []const u8) void {...@@ -562,7 +562,7 @@ pub fn addPathDir(run: *Run, search_path: []const u8) void {
562 .output_file, .output_directory => false,562 .output_file, .output_directory => false,
563 };563 };
564 const key = if (use_wine) "WINEPATH" else "PATH";564 const key = if (use_wine) "WINEPATH" else "PATH";
565 const prev_path = env_map.get(key);565 const prev_path = environ_map.get(key);
566566
567 if (prev_path) |pp| {567 if (prev_path) |pp| {
568 const new_path = b.fmt("{s}{c}{s}", .{568 const new_path = b.fmt("{s}{c}{s}", .{
...@@ -570,9 +570,9 @@ pub fn addPathDir(run: *Run, search_path: []const u8) void {...@@ -570,9 +570,9 @@ pub fn addPathDir(run: *Run, search_path: []const u8) void {
570 if (use_wine) Dir.path.delimiter_windows else Dir.path.delimiter,570 if (use_wine) Dir.path.delimiter_windows else Dir.path.delimiter,
571 search_path,571 search_path,
572 });572 });
573 env_map.put(key, new_path) catch @panic("OOM");573 environ_map.put(key, new_path) catch @panic("OOM");
574 } else {574 } else {
575 env_map.put(key, b.dupePath(search_path)) catch @panic("OOM");575 environ_map.put(key, b.dupePath(search_path)) catch @panic("OOM");
576 }576 }
577}577}
578578
...@@ -581,43 +581,51 @@ pub fn getEnvMap(run: *Run) *EnvMap {...@@ -581,43 +581,51 @@ pub fn getEnvMap(run: *Run) *EnvMap {
581}581}
582582
583fn getEnvMapInternal(run: *Run) *EnvMap {583fn getEnvMapInternal(run: *Run) *EnvMap {
584 const arena = run.step.owner.allocator;584 const graph = run.step.owner.graph;
585 return run.env_map orelse {585 const arena = graph.arena;
586 const env_map = arena.create(EnvMap) catch @panic("OOM");586 return run.environ_map orelse {
587 env_map.* = process.getEnvMap(arena) catch @panic("unhandled error");587 const cloned_map = arena.create(EnvMap) catch @panic("OOM");
588 run.env_map = env_map;588 cloned_map.* = graph.environ_map.clone(arena) catch @panic("OOM");
589 return env_map;589 run.environ_map = cloned_map;
590 return cloned_map;
590 };591 };
591}592}
592593
593pub fn setEnvironmentVariable(run: *Run, key: []const u8, value: []const u8) void {594pub fn setEnvironmentVariable(run: *Run, key: []const u8, value: []const u8) void {
594 const b = run.step.owner;595 const environ_map = run.getEnvMap();
595 const env_map = run.getEnvMap();596 // This data structure already dupes keys and values.
596 env_map.put(b.dupe(key), b.dupe(value)) catch @panic("unhandled error");597 environ_map.put(key, value) catch @panic("OOM");
597}598}
598599
599pub fn removeEnvironmentVariable(run: *Run, key: []const u8) void {600pub fn removeEnvironmentVariable(run: *Run, key: []const u8) void {
600 run.getEnvMap().remove(key);601 _ = run.getEnvMap().swapRemove(key);
601}602}
602603
603/// Adds a check for exact stderr match. Does not add any other checks.604/// Adds a check for exact stderr match. Does not add any other checks.
604pub fn expectStdErrEqual(run: *Run, bytes: []const u8) void {605pub fn expectStdErrEqual(run: *Run, bytes: []const u8) void {
605 const new_check: StdIo.Check = .{ .expect_stderr_exact = run.step.owner.dupe(bytes) };606 run.addCheck(.{ .expect_stderr_exact = run.step.owner.dupe(bytes) });
606 run.addCheck(new_check);607}
608
609pub fn expectStdErrMatch(run: *Run, bytes: []const u8) void {
610 run.addCheck(.{ .expect_stderr_match = run.step.owner.dupe(bytes) });
607}611}
608612
609/// Adds a check for exact stdout match as well as a check for exit code 0, if613/// Adds a check for exact stdout match as well as a check for exit code 0, if
610/// there is not already an expected termination check.614/// there is not already an expected termination check.
611pub fn expectStdOutEqual(run: *Run, bytes: []const u8) void {615pub fn expectStdOutEqual(run: *Run, bytes: []const u8) void {
612 const new_check: StdIo.Check = .{ .expect_stdout_exact = run.step.owner.dupe(bytes) };616 run.addCheck(.{ .expect_stdout_exact = run.step.owner.dupe(bytes) });
613 run.addCheck(new_check);617 if (!run.hasTermCheck()) run.expectExitCode(0);
614 if (!run.hasTermCheck()) {618}
615 run.expectExitCode(0);619
616 }620/// Adds a check for stdout match as well as a check for exit code 0, if there
621/// is not already an expected termination check.
622pub fn expectStdOutMatch(run: *Run, bytes: []const u8) void {
623 run.addCheck(.{ .expect_stdout_match = run.step.owner.dupe(bytes) });
624 if (!run.hasTermCheck()) run.expectExitCode(0);
617}625}
618626
619pub fn expectExitCode(run: *Run, code: u8) void {627pub fn expectExitCode(run: *Run, code: u8) void {
620 const new_check: StdIo.Check = .{ .expect_term = .{ .Exited = code } };628 const new_check: StdIo.Check = .{ .expect_term = .{ .exited = code } };
621 run.addCheck(new_check);629 run.addCheck(new_check);
622}630}
623631
...@@ -749,28 +757,30 @@ fn checksContainStderr(checks: []const StdIo.Check) bool {...@@ -749,28 +757,30 @@ fn checksContainStderr(checks: []const StdIo.Check) bool {
749/// to make sure the child doesn't see paths relative to a cwd other than its own.757/// to make sure the child doesn't see paths relative to a cwd other than its own.
750fn convertPathArg(run: *Run, path: Build.Cache.Path) []const u8 {758fn convertPathArg(run: *Run, path: Build.Cache.Path) []const u8 {
751 const b = run.step.owner;759 const b = run.step.owner;
752 const path_str = path.toString(b.graph.arena) catch @panic("OOM");760 const graph = b.graph;
761 const arena = graph.arena;
762
763 const path_str = path.toString(arena) catch @panic("OOM");
753 if (Dir.path.isAbsolute(path_str)) {764 if (Dir.path.isAbsolute(path_str)) {
754 // Absolute paths don't need changing.765 // Absolute paths don't need changing.
755 return path_str;766 return path_str;
756 }767 }
757 const child_cwd_rel: []const u8 = rel: {768 const child_cwd_rel: []const u8 = rel: {
758 const child_lazy_cwd = run.cwd orelse break :rel path_str;769 const child_lazy_cwd = run.cwd orelse break :rel path_str;
759 const child_cwd = child_lazy_cwd.getPath3(b, &run.step).toString(b.graph.arena) catch @panic("OOM");770 const child_cwd = child_lazy_cwd.getPath3(b, &run.step).toString(arena) catch @panic("OOM");
760 // Convert it from relative to *our* cwd, to relative to the *child's* cwd.771 // Convert it from relative to *our* cwd, to relative to the *child's* cwd.
761 break :rel Dir.path.relative(b.graph.arena, child_cwd, path_str) catch @panic("OOM");772 break :rel Dir.path.relative(arena, graph.cache.cwd, &graph.environ_map, child_cwd, path_str) catch @panic("OOM");
762 };773 };
763 // Not every path can be made relative, e.g. if the path and the child cwd are on different774 // Not every path can be made relative, e.g. if the path and the child cwd are on different
764 // disk designators on Windows. In that case, `relative` will return an absolute path which we can775 // disk designators on Windows. In that case, `relative` will return an absolute path which we can
765 // just return.776 // just return.
766 if (Dir.path.isAbsolute(child_cwd_rel)) {777 if (Dir.path.isAbsolute(child_cwd_rel)) return child_cwd_rel;
767 return child_cwd_rel;778
768 }
769 // We're not done yet. In some cases this path must be prefixed with './':779 // We're not done yet. In some cases this path must be prefixed with './':
770 // * On POSIX, the executable name cannot be a single component like 'foo'780 // * On POSIX, the executable name cannot be a single component like 'foo'
771 // * Some executables might treat a leading '-' like a flag, which we must avoid781 // * Some executables might treat a leading '-' like a flag, which we must avoid
772 // There's no harm in it, so just *always* apply this prefix.782 // There's no harm in it, so just *always* apply this prefix.
773 return Dir.path.join(b.graph.arena, &.{ ".", child_cwd_rel }) catch @panic("OOM");783 return Dir.path.join(arena, &.{ ".", child_cwd_rel }) catch @panic("OOM");
774}784}
775785
776const IndexedOutput = struct {786const IndexedOutput = struct {
...@@ -791,32 +801,10 @@ fn make(step: *Step, options: Step.MakeOptions) !void {...@@ -791,32 +801,10 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
791 var man = b.graph.cache.obtain();801 var man = b.graph.cache.obtain();
792 defer man.deinit();802 defer man.deinit();
793803
794 if (run.env_map) |env_map| {804 if (run.environ_map) |environ_map| {
795 const KV = struct { []const u8, []const u8 };805 for (environ_map.keys(), environ_map.values()) |key, value| {
796 var kv_pairs = try std.array_list.Managed(KV).initCapacity(arena, env_map.count());806 man.hash.addBytes(key);
797 var iter = env_map.iterator();807 man.hash.addBytes(value);
798 while (iter.next()) |entry| {
799 kv_pairs.appendAssumeCapacity(.{ entry.key_ptr.*, entry.value_ptr.* });
800 }
801
802 std.mem.sortUnstable(KV, kv_pairs.items, {}, struct {
803 fn lessThan(_: void, kv1: KV, kv2: KV) bool {
804 const k1 = kv1[0];
805 const k2 = kv2[0];
806
807 if (k1.len != k2.len) return k1.len < k2.len;
808
809 for (k1, k2) |c1, c2| {
810 if (c1 == c2) continue;
811 return c1 < c2;
812 }
813 unreachable; // two keys cannot be equal
814 }
815 }.lessThan);
816
817 for (kv_pairs.items) |kv| {
818 man.hash.addBytes(kv[0]);
819 man.hash.addBytes(kv[1]);
820 }808 }
821 }809 }
822810
...@@ -1181,40 +1169,40 @@ fn populateGeneratedPaths(...@@ -1181,40 +1169,40 @@ fn populateGeneratedPaths(
1181 }1169 }
1182}1170}
11831171
1184fn formatTerm(term: ?std.process.Child.Term, w: *std.Io.Writer) std.Io.Writer.Error!void {1172fn formatTerm(term: ?process.Child.Term, w: *std.Io.Writer) std.Io.Writer.Error!void {
1185 if (term) |t| switch (t) {1173 if (term) |t| switch (t) {
1186 .Exited => |code| try w.print("exited with code {d}", .{code}),1174 .exited => |code| try w.print("exited with code {d}", .{code}),
1187 .Signal => |sig| try w.print("terminated with signal {d}", .{sig}),1175 .signal => |sig| try w.print("terminated with signal {t}", .{sig}),
1188 .Stopped => |sig| try w.print("stopped with signal {d}", .{sig}),1176 .stopped => |sig| try w.print("stopped with signal {d}", .{sig}),
1189 .Unknown => |code| try w.print("terminated for unknown reason with code {d}", .{code}),1177 .unknown => |code| try w.print("terminated for unknown reason with code {d}", .{code}),
1190 } else {1178 } else {
1191 try w.writeAll("exited with any code");1179 try w.writeAll("exited with any code");
1192 }1180 }
1193}1181}
1194fn fmtTerm(term: ?std.process.Child.Term) std.fmt.Alt(?std.process.Child.Term, formatTerm) {1182fn fmtTerm(term: ?process.Child.Term) std.fmt.Alt(?process.Child.Term, formatTerm) {
1195 return .{ .data = term };1183 return .{ .data = term };
1196}1184}
11971185
1198fn termMatches(expected: ?std.process.Child.Term, actual: std.process.Child.Term) bool {1186fn termMatches(expected: ?process.Child.Term, actual: process.Child.Term) bool {
1199 return if (expected) |e| switch (e) {1187 return if (expected) |e| switch (e) {
1200 .Exited => |expected_code| switch (actual) {1188 .exited => |expected_code| switch (actual) {
1201 .Exited => |actual_code| expected_code == actual_code,1189 .exited => |actual_code| expected_code == actual_code,
1202 else => false,1190 else => false,
1203 },1191 },
1204 .Signal => |expected_sig| switch (actual) {1192 .signal => |expected_sig| switch (actual) {
1205 .Signal => |actual_sig| expected_sig == actual_sig,1193 .signal => |actual_sig| expected_sig == actual_sig,
1206 else => false,1194 else => false,
1207 },1195 },
1208 .Stopped => |expected_sig| switch (actual) {1196 .stopped => |expected_sig| switch (actual) {
1209 .Stopped => |actual_sig| expected_sig == actual_sig,1197 .stopped => |actual_sig| expected_sig == actual_sig,
1210 else => false,1198 else => false,
1211 },1199 },
1212 .Unknown => |expected_code| switch (actual) {1200 .unknown => |expected_code| switch (actual) {
1213 .Unknown => |actual_code| expected_code == actual_code,1201 .unknown => |actual_code| expected_code == actual_code,
1214 else => false,1202 else => false,
1215 },1203 },
1216 } else switch (actual) {1204 } else switch (actual) {
1217 .Exited => true,1205 .exited => true,
1218 else => false,1206 else => false,
1219 };1207 };
1220}1208}
...@@ -1241,7 +1229,7 @@ fn runCommand(...@@ -1241,7 +1229,7 @@ fn runCommand(
1241 const cwd: ?[]const u8 = if (run.cwd) |lazy_cwd| lazy_cwd.getPath2(b, step) else null;1229 const cwd: ?[]const u8 = if (run.cwd) |lazy_cwd| lazy_cwd.getPath2(b, step) else null;
12421230
1243 try step.handleChildProcUnsupported();1231 try step.handleChildProcUnsupported();
1244 try Step.handleVerbose2(step.owner, cwd, run.env_map, argv);1232 try Step.handleVerbose2(step.owner, cwd, run.environ_map, argv);
12451233
1246 const allow_skip = switch (run.stdio) {1234 const allow_skip = switch (run.stdio) {
1247 .check, .zig_test => run.skip_foreign_checks,1235 .check, .zig_test => run.skip_foreign_checks,
...@@ -1251,13 +1239,13 @@ fn runCommand(...@@ -1251,13 +1239,13 @@ fn runCommand(
1251 var interp_argv = std.array_list.Managed([]const u8).init(b.allocator);1239 var interp_argv = std.array_list.Managed([]const u8).init(b.allocator);
1252 defer interp_argv.deinit();1240 defer interp_argv.deinit();
12531241
1254 var env_map: EnvMap = env: {1242 var environ_map: EnvMap = env: {
1255 const orig = run.env_map orelse &b.graph.env_map;1243 const orig = run.environ_map orelse &b.graph.environ_map;
1256 break :env try orig.clone(gpa);1244 break :env try orig.clone(gpa);
1257 };1245 };
1258 defer env_map.deinit();1246 defer environ_map.deinit();
12591247
1260 const opt_generic_result = spawnChildAndCollect(run, argv, &env_map, has_side_effects, options, fuzz_context) catch |err| term: {1248 const opt_generic_result = spawnChildAndCollect(run, argv, &environ_map, has_side_effects, options, fuzz_context) catch |err| term: {
1261 // InvalidExe: cpu arch mismatch1249 // InvalidExe: cpu arch mismatch
1262 // FileNotFound: can happen with a wrong dynamic linker path1250 // FileNotFound: can happen with a wrong dynamic linker path
1263 if (err == error.InvalidExe or err == error.FileNotFound) interpret: {1251 if (err == error.InvalidExe or err == error.FileNotFound) interpret: {
...@@ -1293,8 +1281,8 @@ fn runCommand(...@@ -1293,8 +1281,8 @@ fn runCommand(
12931281
1294 // Wine's excessive stderr logging is only situationally helpful. Disable it by default, but1282 // Wine's excessive stderr logging is only situationally helpful. Disable it by default, but
1295 // allow the user to override it (e.g. with `WINEDEBUG=err+all`) if desired.1283 // allow the user to override it (e.g. with `WINEDEBUG=err+all`) if desired.
1296 if (env_map.get("WINEDEBUG") == null) {1284 if (environ_map.get("WINEDEBUG") == null) {
1297 try env_map.put("WINEDEBUG", "-all");1285 try environ_map.put("WINEDEBUG", "-all");
1298 }1286 }
1299 } else {1287 } else {
1300 return failForeign(run, "-fwine", argv[0], exe);1288 return failForeign(run, "-fwine", argv[0], exe);
...@@ -1391,9 +1379,9 @@ fn runCommand(...@@ -1391,9 +1379,9 @@ fn runCommand(
13911379
1392 gpa.free(step.result_failed_command.?);1380 gpa.free(step.result_failed_command.?);
1393 step.result_failed_command = null;1381 step.result_failed_command = null;
1394 try Step.handleVerbose2(step.owner, cwd, run.env_map, interp_argv.items);1382 try Step.handleVerbose2(step.owner, cwd, run.environ_map, interp_argv.items);
13951383
1396 break :term spawnChildAndCollect(run, interp_argv.items, &env_map, has_side_effects, options, fuzz_context) catch |e| {1384 break :term spawnChildAndCollect(run, interp_argv.items, &environ_map, has_side_effects, options, fuzz_context) catch |e| {
1397 if (!run.failing_to_execute_foreign_is_an_error) return error.MakeSkipped;1385 if (!run.failing_to_execute_foreign_is_an_error) return error.MakeSkipped;
1398 if (e == error.MakeFailed) return error.MakeFailed; // error already reported1386 if (e == error.MakeFailed) return error.MakeFailed; // error already reported
1399 return step.fail("unable to spawn interpreter {s}: {s}", .{1387 return step.fail("unable to spawn interpreter {s}: {s}", .{
...@@ -1525,8 +1513,8 @@ fn runCommand(...@@ -1525,8 +1513,8 @@ fn runCommand(
1525 else => {1513 else => {
1526 // On failure, report captured stderr like normal standard error output.1514 // On failure, report captured stderr like normal standard error output.
1527 const bad_exit = switch (generic_result.term) {1515 const bad_exit = switch (generic_result.term) {
1528 .Exited => |code| code != 0,1516 .exited => |code| code != 0,
1529 .Signal, .Stopped, .Unknown => true,1517 .signal, .stopped, .unknown => true,
1530 };1518 };
1531 if (bad_exit) {1519 if (bad_exit) {
1532 if (generic_result.stderr) |bytes| {1520 if (generic_result.stderr) |bytes| {
...@@ -1540,7 +1528,7 @@ fn runCommand(...@@ -1540,7 +1528,7 @@ fn runCommand(
1540}1528}
15411529
1542const EvalGenericResult = struct {1530const EvalGenericResult = struct {
1543 term: std.process.Child.Term,1531 term: process.Child.Term,
1544 stdout: ?[]const u8,1532 stdout: ?[]const u8,
1545 stderr: ?[]const u8,1533 stderr: ?[]const u8,
1546};1534};
...@@ -1548,13 +1536,12 @@ const EvalGenericResult = struct {...@@ -1548,13 +1536,12 @@ const EvalGenericResult = struct {
1548fn spawnChildAndCollect(1536fn spawnChildAndCollect(
1549 run: *Run,1537 run: *Run,
1550 argv: []const []const u8,1538 argv: []const []const u8,
1551 env_map: *EnvMap,1539 environ_map: *EnvMap,
1552 has_side_effects: bool,1540 has_side_effects: bool,
1553 options: Step.MakeOptions,1541 options: Step.MakeOptions,
1554 fuzz_context: ?FuzzContext,1542 fuzz_context: ?FuzzContext,
1555) !?EvalGenericResult {1543) !?EvalGenericResult {
1556 const b = run.step.owner;1544 const b = run.step.owner;
1557 const arena = b.allocator;
1558 const graph = b.graph;1545 const graph = b.graph;
1559 const io = graph.io;1546 const io = graph.io;
15601547
...@@ -1563,75 +1550,76 @@ fn spawnChildAndCollect(...@@ -1563,75 +1550,76 @@ fn spawnChildAndCollect(
1563 assert(run.stdio == .zig_test);1550 assert(run.stdio == .zig_test);
1564 }1551 }
15651552
1566 var child = std.process.Child.init(argv, arena);1553 const child_cwd = if (run.cwd) |lazy_cwd| lazy_cwd.getPath2(b, &run.step) else null;
1567 if (run.cwd) |lazy_cwd| {
1568 child.cwd = lazy_cwd.getPath2(b, &run.step);
1569 }
1570 child.env_map = env_map;
1571 child.request_resource_usage_statistics = true;
1572
1573 child.stdin_behavior = switch (run.stdio) {
1574 .infer_from_args => if (has_side_effects) .Inherit else .Ignore,
1575 .inherit => .Inherit,
1576 .check => .Ignore,
1577 .zig_test => .Pipe,
1578 };
1579 child.stdout_behavior = switch (run.stdio) {
1580 .infer_from_args => if (has_side_effects) .Inherit else .Ignore,
1581 .inherit => .Inherit,
1582 .check => |checks| if (checksContainStdout(checks.items)) .Pipe else .Ignore,
1583 .zig_test => .Pipe,
1584 };
1585 child.stderr_behavior = switch (run.stdio) {
1586 .infer_from_args => if (has_side_effects) .Inherit else .Pipe,
1587 .inherit => .Inherit,
1588 .check => .Pipe,
1589 .zig_test => .Pipe,
1590 };
1591 if (run.captured_stdout != null) child.stdout_behavior = .Pipe;
1592 if (run.captured_stderr != null) child.stderr_behavior = .Pipe;
1593 if (run.stdin != .none) {
1594 assert(run.stdio != .inherit);
1595 child.stdin_behavior = .Pipe;
1596 }
15971554
1598 // If an error occurs, it's caused by this command:1555 // If an error occurs, it's caused by this command:
1599 assert(run.step.result_failed_command == null);1556 assert(run.step.result_failed_command == null);
1600 run.step.result_failed_command = try Step.allocPrintCmd(options.gpa, child.cwd, argv);1557 run.step.result_failed_command = try Step.allocPrintCmd(options.gpa, child_cwd, .{
1558 .child = environ_map,
1559 .parent = &graph.environ_map,
1560 }, argv);
1561
1562 var spawn_options: process.SpawnOptions = .{
1563 .argv = argv,
1564 .cwd = child_cwd,
1565 .environ_map = environ_map,
1566 .request_resource_usage_statistics = true,
1567 .stdin = if (run.stdin != .none) s: {
1568 assert(run.stdio != .inherit);
1569 break :s .pipe;
1570 } else switch (run.stdio) {
1571 .infer_from_args => if (has_side_effects) .inherit else .ignore,
1572 .inherit => .inherit,
1573 .check => .ignore,
1574 .zig_test => .pipe,
1575 },
1576 .stdout = if (run.captured_stdout != null) .pipe else switch (run.stdio) {
1577 .infer_from_args => if (has_side_effects) .inherit else .ignore,
1578 .inherit => .inherit,
1579 .check => |checks| if (checksContainStdout(checks.items)) .pipe else .ignore,
1580 .zig_test => .pipe,
1581 },
1582 .stderr = if (run.captured_stderr != null) .pipe else switch (run.stdio) {
1583 .infer_from_args => if (has_side_effects) .inherit else .pipe,
1584 .inherit => .inherit,
1585 .check => .pipe,
1586 .zig_test => .pipe,
1587 },
1588 };
16011589
1602 if (run.stdio == .zig_test) {1590 if (run.stdio == .zig_test) {
1603 var timer = try std.time.Timer.start();1591 var timer = try std.time.Timer.start();
1604 defer run.step.result_duration_ns = timer.read();1592 defer run.step.result_duration_ns = timer.read();
1605 try evalZigTest(run, &child, options, fuzz_context);1593 try evalZigTest(run, spawn_options, options, fuzz_context);
1606 return null;1594 return null;
1607 } else {1595 } else {
1608 const inherit = child.stdout_behavior == .Inherit or child.stderr_behavior == .Inherit;1596 const inherit = spawn_options.stdout == .inherit or spawn_options.stderr == .inherit;
1609 if (!run.disable_zig_progress and !inherit) {1597 if (!run.disable_zig_progress and !inherit) {
1610 child.progress_node = options.progress_node;1598 spawn_options.progress_node = options.progress_node;
1611 }1599 }
1612 const terminal_mode: Io.Terminal.Mode = if (inherit) m: {1600 const terminal_mode: Io.Terminal.Mode = if (inherit) m: {
1613 const stderr = try io.lockStderr(&.{}, graph.stderr_mode);1601 const stderr = try io.lockStderr(&.{}, graph.stderr_mode);
1614 break :m stderr.terminal_mode;1602 break :m stderr.terminal_mode;
1615 } else .no_color;1603 } else .no_color;
1616 defer if (inherit) io.unlockStderr();1604 defer if (inherit) io.unlockStderr();
1617 try setColorEnvironmentVariables(run, env_map, terminal_mode);1605 try setColorEnvironmentVariables(run, environ_map, terminal_mode);
1618 var timer = try std.time.Timer.start();1606 var timer = try std.time.Timer.start();
1619 const res = try evalGeneric(run, &child);1607 const res = try evalGeneric(run, spawn_options);
1620 run.step.result_duration_ns = timer.read();1608 run.step.result_duration_ns = timer.read();
1621 return .{ .term = res.term, .stdout = res.stdout, .stderr = res.stderr };1609 return .{ .term = res.term, .stdout = res.stdout, .stderr = res.stderr };
1622 }1610 }
1623}1611}
16241612
1625fn setColorEnvironmentVariables(run: *Run, env_map: *EnvMap, terminal_mode: Io.Terminal.Mode) !void {1613fn setColorEnvironmentVariables(run: *Run, environ_map: *EnvMap, terminal_mode: Io.Terminal.Mode) !void {
1626 color: switch (run.color) {1614 color: switch (run.color) {
1627 .manual => {},1615 .manual => {},
1628 .enable => {1616 .enable => {
1629 try env_map.put("CLICOLOR_FORCE", "1");1617 try environ_map.put("CLICOLOR_FORCE", "1");
1630 env_map.remove("NO_COLOR");1618 _ = environ_map.swapRemove("NO_COLOR");
1631 },1619 },
1632 .disable => {1620 .disable => {
1633 try env_map.put("NO_COLOR", "1");1621 try environ_map.put("NO_COLOR", "1");
1634 env_map.remove("CLICOLOR_FORCE");1622 _ = environ_map.swapRemove("CLICOLOR_FORCE");
1635 },1623 },
1636 .inherit => switch (terminal_mode) {1624 .inherit => switch (terminal_mode) {
1637 .no_color, .windows_api => continue :color .disable,1625 .no_color, .windows_api => continue :color .disable,
...@@ -1655,7 +1643,7 @@ const StdioPollEnum = enum { stdout, stderr };...@@ -1655,7 +1643,7 @@ const StdioPollEnum = enum { stdout, stderr };
16551643
1656fn evalZigTest(1644fn evalZigTest(
1657 run: *Run,1645 run: *Run,
1658 child: *std.process.Child,1646 spawn_options: process.SpawnOptions,
1659 options: Step.MakeOptions,1647 options: Step.MakeOptions,
1660 fuzz_context: ?FuzzContext,1648 fuzz_context: ?FuzzContext,
1661) !void {1649) !void {
...@@ -1679,14 +1667,14 @@ fn evalZigTest(...@@ -1679,14 +1667,14 @@ fn evalZigTest(
1679 var test_metadata: ?TestMetadata = null;1667 var test_metadata: ?TestMetadata = null;
16801668
1681 while (true) {1669 while (true) {
1682 try child.spawn(io);1670 var child = try process.spawn(io, spawn_options);
1683 var poller = std.Io.poll(gpa, StdioPollEnum, .{1671 var poller = std.Io.poll(gpa, StdioPollEnum, .{
1684 .stdout = child.stdout.?,1672 .stdout = child.stdout.?,
1685 .stderr = child.stderr.?,1673 .stderr = child.stderr.?,
1686 });1674 });
1687 var child_killed = false;1675 var child_killed = false;
1688 defer if (!child_killed) {1676 defer if (!child_killed) {
1689 _ = child.kill(io) catch {};1677 child.kill(io);
1690 poller.deinit();1678 poller.deinit();
1691 run.step.result_peak_rss = @max(1679 run.step.result_peak_rss = @max(
1692 run.step.result_peak_rss,1680 run.step.result_peak_rss,
...@@ -1694,11 +1682,9 @@ fn evalZigTest(...@@ -1694,11 +1682,9 @@ fn evalZigTest(
1694 );1682 );
1695 };1683 };
16961684
1697 try child.waitForSpawn();
1698
1699 switch (try pollZigTest(1685 switch (try pollZigTest(
1700 run,1686 run,
1701 child,1687 &child,
1702 options,1688 options,
1703 fuzz_context,1689 fuzz_context,
1704 &poller,1690 &poller,
...@@ -1760,7 +1746,7 @@ fn evalZigTest(...@@ -1760,7 +1746,7 @@ fn evalZigTest(
1760 // Report an error if the child terminated uncleanly or if we were still trying to run more tests.1746 // Report an error if the child terminated uncleanly or if we were still trying to run more tests.
1761 run.step.result_stderr = stderr_owned;1747 run.step.result_stderr = stderr_owned;
1762 const tests_done = test_metadata != null and test_metadata.?.next_index == std.math.maxInt(u32);1748 const tests_done = test_metadata != null and test_metadata.?.next_index == std.math.maxInt(u32);
1763 if (!tests_done or !termMatches(.{ .Exited = 0 }, term)) {1749 if (!tests_done or !termMatches(.{ .exited = 0 }, term)) {
1764 // The individual unit test results are irrelevant: the test runner itself broke!1750 // The individual unit test results are irrelevant: the test runner itself broke!
1765 // Fail immediately without populating `s.test_results`.1751 // Fail immediately without populating `s.test_results`.
1766 return run.step.fail("test process unexpectedly {f}", .{fmtTerm(term)});1752 return run.step.fail("test process unexpectedly {f}", .{fmtTerm(term)});
...@@ -1815,7 +1801,7 @@ fn evalZigTest(...@@ -1815,7 +1801,7 @@ fn evalZigTest(
1815/// * `poll` fails, indicating the child closed stdout and stderr1801/// * `poll` fails, indicating the child closed stdout and stderr
1816fn pollZigTest(1802fn pollZigTest(
1817 run: *Run,1803 run: *Run,
1818 child: *std.process.Child,1804 child: *process.Child,
1819 options: Step.MakeOptions,1805 options: Step.MakeOptions,
1820 fuzz_context: ?FuzzContext,1806 fuzz_context: ?FuzzContext,
1821 poller: *std.Io.Poller(StdioPollEnum),1807 poller: *std.Io.Poller(StdioPollEnum),
...@@ -2173,15 +2159,13 @@ fn sendRunFuzzTestMessage(...@@ -2173,15 +2159,13 @@ fn sendRunFuzzTestMessage(
2173 };2159 };
2174}2160}
21752161
2176fn evalGeneric(run: *Run, child: *std.process.Child) !EvalGenericResult {2162fn evalGeneric(run: *Run, spawn_options: process.SpawnOptions) !EvalGenericResult {
2177 const b = run.step.owner;2163 const b = run.step.owner;
2178 const io = b.graph.io;2164 const io = b.graph.io;
2179 const arena = b.allocator;2165 const arena = b.allocator;
21802166
2181 try child.spawn(io);2167 var child = try process.spawn(io, spawn_options);
2182 errdefer _ = child.kill(io) catch {};2168 defer child.kill(io);
2183
2184 try child.waitForSpawn();
21852169
2186 switch (run.stdin) {2170 switch (run.stdin) {
2187 .bytes => |bytes| {2171 .bytes => |bytes| {
...@@ -2331,10 +2315,10 @@ fn hashStdIo(hh: *std.Build.Cache.HashHelper, stdio: StdIo) void {...@@ -2331,10 +2315,10 @@ fn hashStdIo(hh: *std.Build.Cache.HashHelper, stdio: StdIo) void {
2331 => |s| hh.addBytes(s),2315 => |s| hh.addBytes(s),
23322316
2333 .expect_term => |term| {2317 .expect_term => |term| {
2334 hh.add(@as(std.meta.Tag(std.process.Child.Term), term));2318 hh.add(@as(std.meta.Tag(process.Child.Term), term));
2335 switch (term) {2319 switch (term) {
2336 .Exited => |x| hh.add(x),2320 inline .exited, .signal => |x| hh.add(x),
2337 .Signal, .Stopped, .Unknown => |x| hh.add(x),2321 .stopped, .unknown => |x| hh.add(x),
2338 }2322 }
2339 },2323 },
2340 }2324 }
lib/std/Build/Watch.zig+11-10
...@@ -99,7 +99,8 @@ const Os = switch (builtin.os.tag) {...@@ -99,7 +99,8 @@ const Os = switch (builtin.os.tag) {
99 };99 };
100 };100 };
101101
102 fn init() !Watch {102 fn init(cwd_path: []const u8) !Watch {
103 _ = cwd_path;
103 return .{104 return .{
104 .dir_table = .{},105 .dir_table = .{},
105 .dir_count = 0,106 .dir_count = 0,
...@@ -427,7 +428,8 @@ const Os = switch (builtin.os.tag) {...@@ -427,7 +428,8 @@ const Os = switch (builtin.os.tag) {
427 }428 }
428 };429 };
429430
430 fn init() !Watch {431 fn init(cwd_path: []const u8) !Watch {
432 _ = cwd_path;
431 return .{433 return .{
432 .dir_table = .{},434 .dir_table = .{},
433 .dir_count = 0,435 .dir_count = 0,
...@@ -658,14 +660,13 @@ const Os = switch (builtin.os.tag) {...@@ -658,14 +660,13 @@ const Os = switch (builtin.os.tag) {
658 const EV = std.c.EV;660 const EV = std.c.EV;
659 const NOTE = std.c.NOTE;661 const NOTE = std.c.NOTE;
660662
661 fn init() !Watch {663 fn init(cwd_path: []const u8) !Watch {
662 const kq_fd = try posix.kqueue();664 _ = cwd_path;
663 errdefer posix.close(kq_fd);
664 return .{665 return .{
665 .dir_table = .{},666 .dir_table = .{},
666 .dir_count = 0,667 .dir_count = 0,
667 .os = .{668 .os = .{
668 .kq_fd = kq_fd,669 .kq_fd = try posix.kqueue(),
669 .handles = .empty,670 .handles = .empty,
670 },671 },
671 .generation = 0,672 .generation = 0,
...@@ -841,9 +842,9 @@ const Os = switch (builtin.os.tag) {...@@ -841,9 +842,9 @@ const Os = switch (builtin.os.tag) {
841 .macos => struct {842 .macos => struct {
842 fse: FsEvents,843 fse: FsEvents,
843844
844 fn init() !Watch {845 fn init(cwd_path: []const u8) !Watch {
845 return .{846 return .{
846 .os = .{ .fse = try .init() },847 .os = .{ .fse = try .init(cwd_path) },
847 .dir_count = 0,848 .dir_count = 0,
848 .dir_table = undefined,849 .dir_table = undefined,
849 .generation = undefined,850 .generation = undefined,
...@@ -863,8 +864,8 @@ const Os = switch (builtin.os.tag) {...@@ -863,8 +864,8 @@ const Os = switch (builtin.os.tag) {
863 else => void,864 else => void,
864};865};
865866
866pub fn init() !Watch {867pub fn init(cwd_path: []const u8) !Watch {
867 return Os.init();868 return Os.init(cwd_path);
868}869}
869870
870pub const Match = struct {871pub const Match = struct {
lib/std/Build/Watch/FsEvents.zig+7-5
...@@ -43,6 +43,8 @@ dispatch_queue: dispatch_queue_t,...@@ -43,6 +43,8 @@ dispatch_queue: dispatch_queue_t,
43/// of writing. See the comment at the start of `wait` for details.43/// of writing. See the comment at the start of `wait` for details.
44since_event: FSEventStreamEventId,44since_event: FSEventStreamEventId,
4545
46cwd_path: []const u8,
47
46/// All of the symbols we pull from the `dlopen`ed CoreServices framework. If any of these symbols48/// All of the symbols we pull from the `dlopen`ed CoreServices framework. If any of these symbols
47/// is not present, `init` will close the framework and return an error.49/// is not present, `init` will close the framework and return an error.
48const ResolvedSymbols = struct {50const ResolvedSymbols = struct {
...@@ -78,7 +80,7 @@ const ResolvedSymbols = struct {...@@ -78,7 +80,7 @@ const ResolvedSymbols = struct {
78 kCFAllocatorUseContext: *const CFAllocatorRef,80 kCFAllocatorUseContext: *const CFAllocatorRef,
79};81};
8082
81pub fn init() error{ OpenFrameworkFailed, MissingCoreServicesSymbol }!FsEvents {83pub fn init(cwd_path: []const u8) error{ OpenFrameworkFailed, MissingCoreServicesSymbol }!FsEvents {
82 var core_services = std.DynLib.open("/System/Library/Frameworks/CoreServices.framework/CoreServices") catch84 var core_services = std.DynLib.open("/System/Library/Frameworks/CoreServices.framework/CoreServices") catch
83 return error.OpenFrameworkFailed;85 return error.OpenFrameworkFailed;
84 errdefer core_services.close();86 errdefer core_services.close();
...@@ -99,6 +101,7 @@ pub fn init() error{ OpenFrameworkFailed, MissingCoreServicesSymbol }!FsEvents {...@@ -99,6 +101,7 @@ pub fn init() error{ OpenFrameworkFailed, MissingCoreServicesSymbol }!FsEvents {
99 // Not `.since_now`, because this means we can init `FsEvents` *before* we do work in order101 // Not `.since_now`, because this means we can init `FsEvents` *before* we do work in order
100 // to notice any changes which happened during said work.102 // to notice any changes which happened during said work.
101 .since_event = resolved_symbols.FSEventsGetCurrentEventId(),103 .since_event = resolved_symbols.FSEventsGetCurrentEventId(),
104 .cwd_path = cwd_path,
102 };105 };
103}106}
104107
...@@ -120,9 +123,6 @@ pub fn setPaths(fse: *FsEvents, gpa: Allocator, steps: []const *std.Build.Step)...@@ -120,9 +123,6 @@ pub fn setPaths(fse: *FsEvents, gpa: Allocator, steps: []const *std.Build.Step)
120 defer fse.paths_arena = paths_arena_instance.state;123 defer fse.paths_arena = paths_arena_instance.state;
121 const paths_arena = paths_arena_instance.allocator();124 const paths_arena = paths_arena_instance.allocator();
122125
123 const cwd_path = try std.process.getCwdAlloc(gpa);
124 defer gpa.free(cwd_path);
125
126 var need_dirs: std.StringArrayHashMapUnmanaged(void) = .empty;126 var need_dirs: std.StringArrayHashMapUnmanaged(void) = .empty;
127 defer need_dirs.deinit(gpa);127 defer need_dirs.deinit(gpa);
128128
...@@ -131,7 +131,9 @@ pub fn setPaths(fse: *FsEvents, gpa: Allocator, steps: []const *std.Build.Step)...@@ -131,7 +131,9 @@ pub fn setPaths(fse: *FsEvents, gpa: Allocator, steps: []const *std.Build.Step)
131 // We take `step` by pointer for a slight memory optimization in a moment.131 // We take `step` by pointer for a slight memory optimization in a moment.
132 for (steps) |*step| {132 for (steps) |*step| {
133 for (step.*.inputs.table.keys(), step.*.inputs.table.values()) |path, *files| {133 for (step.*.inputs.table.keys(), step.*.inputs.table.values()) |path, *files| {
134 const resolved_dir = try std.fs.path.resolvePosix(paths_arena, &.{ cwd_path, path.root_dir.path orelse ".", path.sub_path });134 const resolved_dir = try std.fs.path.resolvePosix(paths_arena, &.{
135 fse.cwd_path, path.root_dir.path orelse ".", path.sub_path,
136 });
135 try need_dirs.put(gpa, resolved_dir, {});137 try need_dirs.put(gpa, resolved_dir, {});
136 for (files.items) |file_name| {138 for (files.items) |file_name| {
137 const watch_path = if (std.mem.eql(u8, file_name, "."))139 const watch_path = if (std.mem.eql(u8, file_name, "."))
lib/std/Build/WebServer.zig+25-21
...@@ -482,8 +482,8 @@ pub fn serveFile(...@@ -482,8 +482,8 @@ pub fn serveFile(
482 });482 });
483}483}
484pub fn serveTarFile(ws: *WebServer, request: *http.Server.Request, paths: []const Cache.Path) !void {484pub fn serveTarFile(ws: *WebServer, request: *http.Server.Request, paths: []const Cache.Path) !void {
485 const gpa = ws.gpa;485 const graph = ws.graph;
486 const io = ws.graph.io;486 const io = graph.io;
487487
488 var send_buffer: [0x4000]u8 = undefined;488 var send_buffer: [0x4000]u8 = undefined;
489 var response = try request.respondStreaming(&send_buffer, .{489 var response = try request.respondStreaming(&send_buffer, .{
...@@ -495,9 +495,6 @@ pub fn serveTarFile(ws: *WebServer, request: *http.Server.Request, paths: []cons...@@ -495,9 +495,6 @@ pub fn serveTarFile(ws: *WebServer, request: *http.Server.Request, paths: []cons
495 },495 },
496 });496 });
497497
498 var cached_cwd_path: ?[]const u8 = null;
499 defer if (cached_cwd_path) |p| gpa.free(p);
500
501 var archiver: std.tar.Writer = .{ .underlying_writer = &response.writer };498 var archiver: std.tar.Writer = .{ .underlying_writer = &response.writer };
502499
503 for (paths) |path| {500 for (paths) |path| {
...@@ -516,10 +513,7 @@ pub fn serveTarFile(ws: *WebServer, request: *http.Server.Request, paths: []cons...@@ -516,10 +513,7 @@ pub fn serveTarFile(ws: *WebServer, request: *http.Server.Request, paths: []cons
516 // resulting in modules named "" and "src". The compiler needs to tell the build system513 // resulting in modules named "" and "src". The compiler needs to tell the build system
517 // about the module graph so that the build system can correctly encode this information in514 // about the module graph so that the build system can correctly encode this information in
518 // the tar file.515 // the tar file.
519 archiver.prefix = path.root_dir.path orelse cwd: {516 archiver.prefix = path.root_dir.path orelse graph.cache.cwd;
520 if (cached_cwd_path == null) cached_cwd_path = try std.process.getCwdAlloc(gpa);
521 break :cwd cached_cwd_path.?;
522 };
523 try archiver.writeFile(path.sub_path, &file_reader, @intCast(stat.mtime.toSeconds()));517 try archiver.writeFile(path.sub_path, &file_reader, @intCast(stat.mtime.toSeconds()));
524 }518 }
525519
...@@ -528,13 +522,13 @@ pub fn serveTarFile(ws: *WebServer, request: *http.Server.Request, paths: []cons...@@ -528,13 +522,13 @@ pub fn serveTarFile(ws: *WebServer, request: *http.Server.Request, paths: []cons
528}522}
529523
530fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.OptimizeMode) !Cache.Path {524fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.OptimizeMode) !Cache.Path {
531 const io = ws.graph.io;
532 const root_name = "build-web";525 const root_name = "build-web";
533 const arch_os_abi = "wasm32-freestanding";526 const arch_os_abi = "wasm32-freestanding";
534 const cpu_features = "baseline+atomics+bulk_memory+multivalue+mutable_globals+nontrapping_fptoint+reference_types+sign_ext";527 const cpu_features = "baseline+atomics+bulk_memory+multivalue+mutable_globals+nontrapping_fptoint+reference_types+sign_ext";
535528
536 const gpa = ws.gpa;529 const gpa = ws.gpa;
537 const graph = ws.graph;530 const graph = ws.graph;
531 const io = graph.io;
538532
539 const main_src_path: Cache.Path = .{533 const main_src_path: Cache.Path = .{
540 .root_dir = graph.zig_lib_directory,534 .root_dir = graph.zig_lib_directory,
...@@ -572,11 +566,14 @@ fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.Optim...@@ -572,11 +566,14 @@ fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.Optim
572 "--listen=-",566 "--listen=-",
573 });567 });
574568
575 var child: std.process.Child = .init(argv.items, gpa);569 var child = try std.process.spawn(io, .{
576 child.stdin_behavior = .Pipe;570 .argv = argv.items,
577 child.stdout_behavior = .Pipe;571 .environ_map = &graph.environ_map,
578 child.stderr_behavior = .Pipe;572 .stdin = .pipe,
579 try child.spawn(io);573 .stdout = .pipe,
574 .stderr = .pipe,
575 });
576 defer child.kill(io);
580577
581 var poller = Io.poll(gpa, enum { stdout, stderr }, .{578 var poller = Io.poll(gpa, enum { stdout, stderr }, .{
582 .stdout = child.stdout.?,579 .stdout = child.stdout.?,
...@@ -636,19 +633,26 @@ fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.Optim...@@ -636,19 +633,26 @@ fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.Optim
636 child.stdin = null;633 child.stdin = null;
637634
638 switch (try child.wait(io)) {635 switch (try child.wait(io)) {
639 .Exited => |code| {636 .exited => |code| {
640 if (code != 0) {637 if (code != 0) {
641 log.err(638 log.err(
642 "the following command exited with error code {d}:\n{s}",639 "the following command exited with error code {d}:\n{s}",
643 .{ code, try Build.Step.allocPrintCmd(arena, null, argv.items) },640 .{ code, try Build.Step.allocPrintCmd(arena, null, null, argv.items) },
644 );641 );
645 return error.WasmCompilationFailed;642 return error.WasmCompilationFailed;
646 }643 }
647 },644 },
648 .Signal, .Stopped, .Unknown => {645 .signal => |sig| {
646 log.err(
647 "the following command terminated with signal {t}:\n{s}",
648 .{ sig, try Build.Step.allocPrintCmd(arena, null, null, argv.items) },
649 );
650 return error.WasmCompilationFailed;
651 },
652 .stopped, .unknown => {
649 log.err(653 log.err(
650 "the following command terminated unexpectedly:\n{s}",654 "the following command terminated unexpectedly:\n{s}",
651 .{try Build.Step.allocPrintCmd(arena, null, argv.items)},655 .{try Build.Step.allocPrintCmd(arena, null, null, argv.items)},
652 );656 );
653 return error.WasmCompilationFailed;657 return error.WasmCompilationFailed;
654 },658 },
...@@ -658,14 +662,14 @@ fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.Optim...@@ -658,14 +662,14 @@ fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.Optim
658 try result_error_bundle.renderToStderr(io, .{}, .auto);662 try result_error_bundle.renderToStderr(io, .{}, .auto);
659 log.err("the following command failed with {d} compilation errors:\n{s}", .{663 log.err("the following command failed with {d} compilation errors:\n{s}", .{
660 result_error_bundle.errorMessageCount(),664 result_error_bundle.errorMessageCount(),
661 try Build.Step.allocPrintCmd(arena, null, argv.items),665 try Build.Step.allocPrintCmd(arena, null, null, argv.items),
662 });666 });
663 return error.WasmCompilationFailed;667 return error.WasmCompilationFailed;
664 }668 }
665669
666 const base_path = result orelse {670 const base_path = result orelse {
667 log.err("child process failed to report result\n{s}", .{671 log.err("child process failed to report result\n{s}", .{
668 try Build.Step.allocPrintCmd(arena, null, argv.items),672 try Build.Step.allocPrintCmd(arena, null, null, argv.items),
669 });673 });
670 return error.WasmCompilationFailed;674 return error.WasmCompilationFailed;
671 };675 };
lib/std/Io.zig+8
...@@ -717,6 +717,14 @@ pub const VTable = struct {...@@ -717,6 +717,14 @@ pub const VTable = struct {
717 tryLockStderr: *const fn (?*anyopaque, ?Terminal.Mode) Cancelable!?LockedStderr,717 tryLockStderr: *const fn (?*anyopaque, ?Terminal.Mode) Cancelable!?LockedStderr,
718 unlockStderr: *const fn (?*anyopaque) void,718 unlockStderr: *const fn (?*anyopaque) void,
719 processSetCurrentDir: *const fn (?*anyopaque, Dir) std.process.SetCurrentDirError!void,719 processSetCurrentDir: *const fn (?*anyopaque, Dir) std.process.SetCurrentDirError!void,
720 processReplace: *const fn (?*anyopaque, std.process.ReplaceOptions) std.process.ReplaceError,
721 processReplacePath: *const fn (?*anyopaque, Dir, std.process.ReplaceOptions) std.process.ReplaceError,
722 processSpawn: *const fn (?*anyopaque, std.process.SpawnOptions) std.process.SpawnError!std.process.Child,
723 processSpawnPath: *const fn (?*anyopaque, Dir, std.process.SpawnOptions) std.process.SpawnError!std.process.Child,
724 childWait: *const fn (?*anyopaque, *std.process.Child) std.process.Child.WaitError!std.process.Child.Term,
725 childKill: *const fn (?*anyopaque, *std.process.Child) void,
726
727 progressParentFile: *const fn (?*anyopaque) std.Progress.ParentFileError!File,
720728
721 now: *const fn (?*anyopaque, Clock) Clock.Error!Timestamp,729 now: *const fn (?*anyopaque, Clock) Clock.Error!Timestamp,
722 sleep: *const fn (?*anyopaque, Timeout) SleepError!void,730 sleep: *const fn (?*anyopaque, Timeout) SleepError!void,
lib/std/Io/Dir.zig+4-3
...@@ -82,13 +82,14 @@ pub const Entry = struct {...@@ -82,13 +82,14 @@ pub const Entry = struct {
82///82///
83/// On POSIX targets, this function is comptime-callable.83/// On POSIX targets, this function is comptime-callable.
84///84///
85/// On WASI, the value this returns is application-configurable.85/// This function is overridable via `std.Options.cwd`.
86pub fn cwd() Dir {86pub fn cwd() Dir {
87 return switch (native_os) {87 const cwdFn = std.Options.cwd orelse return switch (native_os) {
88 .windows => .{ .handle = std.os.windows.peb().ProcessParameters.CurrentDirectory.Handle },88 .windows => .{ .handle = std.os.windows.peb().ProcessParameters.CurrentDirectory.Handle },
89 .wasi => .{ .handle = std.options.wasiCwd() },89 .wasi => .{ .handle = 3 }, // Expect the first preopen to be current working directory.
90 else => .{ .handle = std.posix.AT.FDCWD },90 else => .{ .handle = std.posix.AT.FDCWD },
91 };91 };
92 return cwdFn();
92}93}
9394
94pub const Reader = struct {95pub const Reader = struct {
lib/std/Io/Threaded.zig+2410-218
...@@ -13,6 +13,7 @@ const File = std.Io.File;...@@ -13,6 +13,7 @@ const File = std.Io.File;
13const Dir = std.Io.Dir;13const Dir = std.Io.Dir;
14const HostName = std.Io.net.HostName;14const HostName = std.Io.net.HostName;
15const IpAddress = std.Io.net.IpAddress;15const IpAddress = std.Io.net.IpAddress;
16const process = std.process;
16const Allocator = std.mem.Allocator;17const Allocator = std.mem.Allocator;
17const Alignment = std.mem.Alignment;18const Alignment = std.mem.Alignment;
18const assert = std.debug.assert;19const assert = std.debug.assert;
...@@ -63,24 +64,41 @@ stderr_writer_initialized: bool = false,...@@ -63,24 +64,41 @@ stderr_writer_initialized: bool = false,
63argv0: Argv0,64argv0: Argv0,
64environ: Environ,65environ: Environ,
6566
67null_file: NullFile = .{},
68
66pub const Argv0 = switch (native_os) {69pub const Argv0 = switch (native_os) {
67 .openbsd, .haiku => struct {70 .openbsd, .haiku => struct {
68 value: ?[*:0]const u8 = null,71 value: ?[*:0]const u8,
72
73 pub const empty: Argv0 = .{ .value = null };
74
75 pub fn init(args: process.Args) Argv0 {
76 return .{ .value = args.value[0] };
77 }
78 },
79 else => struct {
80 pub const empty: Argv0 = .{};
81
82 pub fn init(args: process.Args) Argv0 {
83 _ = args;
84 return .{};
85 }
69 },86 },
70 else => struct {},
71};87};
7288
73pub const Environ = struct {89const Environ = struct {
74 /// Unmodified data directly from the OS.90 /// Unmodified data directly from the OS.
75 block: Block = &.{},91 process_environ: process.Environ = .empty,
76 /// Protected by `mutex`. Determines whether the other fields have been92 /// Protected by `mutex`. Determines whether the other fields have been
77 /// memoized based on `block`.93 /// memoized based on `process_environ`.
78 initialized: bool = false,94 initialized: bool = false,
79 /// Protected by `mutex`. Memoized based on `block`. Tracks whether the95 /// Protected by `mutex`. Memoized based on `process_environ`. Tracks whether the
80 /// environment variables are present, ignoring their value.96 /// environment variables are present, ignoring their value.
81 exist: Exist = .{},97 exist: Exist = .{},
82 /// Protected by `mutex`. Memoized based on `block`.98 /// Protected by `mutex`. Memoized based on `process_environ`.
83 string: String = .{},99 string: String = .{},
100 /// ZIG_PROGRESS
101 zig_progress_handle: std.Progress.ParentFileError!u31 = error.EnvironmentVariableMissing,
84 /// Protected by `mutex`. Tracks the problem, if any, that occurred when102 /// Protected by `mutex`. Tracks the problem, if any, that occurred when
85 /// trying to scan environment variables.103 /// trying to scan environment variables.
86 ///104 ///
...@@ -89,21 +107,50 @@ pub const Environ = struct {...@@ -89,21 +107,50 @@ pub const Environ = struct {
89107
90 pub const Error = Allocator.Error || Io.UnexpectedError;108 pub const Error = Allocator.Error || Io.UnexpectedError;
91109
92 pub const Block = []const [*:0]const u8;
93
94 pub const Exist = struct {110 pub const Exist = struct {
95 NO_COLOR: bool = false,111 NO_COLOR: bool = false,
96 CLICOLOR_FORCE: bool = false,112 CLICOLOR_FORCE: bool = false,
97 };113 };
98114
99 pub const String = switch (native_os) {115 pub const String = switch (native_os) {
100 .openbsd, .haiku => struct {116 .windows, .wasi => struct {},
117 else => struct {
101 PATH: ?[:0]const u8 = null,118 PATH: ?[:0]const u8 = null,
119 DEBUGINFOD_CACHE_PATH: ?[:0]const u8 = null,
120 XDG_CACHE_HOME: ?[:0]const u8 = null,
121 HOME: ?[:0]const u8 = null,
102 },122 },
103 else => struct {},
104 };123 };
105};124};
106125
126pub const NullFile = switch (native_os) {
127 .windows => struct {
128 handle: ?windows.HANDLE = null,
129
130 fn deinit(this: *@This()) void {
131 if (this.handle) |handle| {
132 windows.CloseHandle(handle);
133 this.handle = null;
134 }
135 }
136 },
137 .wasi, .ios, .tvos, .visionos, .watchos => struct {
138 fn deinit(this: @This()) void {
139 _ = this;
140 }
141 },
142 else => struct {
143 fd: posix.fd_t = -1,
144
145 fn deinit(this: *@This()) void {
146 if (this.fd >= 0) {
147 posix.close(this.fd);
148 this.fd = -1;
149 }
150 }
151 },
152};
153
107pub const Pid = if (native_os == .linux) enum(posix.pid_t) {154pub const Pid = if (native_os == .linux) enum(posix.pid_t) {
108 unknown = 0,155 unknown = 0,
109 _,156 _,
...@@ -1105,6 +1152,7 @@ const Syscall = struct {...@@ -1105,6 +1152,7 @@ const Syscall = struct {
11051152
1106const max_iovecs_len = 8;1153const max_iovecs_len = 8;
1107const splat_buffer_size = 64;1154const splat_buffer_size = 64;
1155const default_PATH = "/usr/local/bin:/bin/:/usr/bin";
11081156
1109comptime {1157comptime {
1110 if (@TypeOf(posix.IOV_MAX) != void) assert(max_iovecs_len <= posix.IOV_MAX);1158 if (@TypeOf(posix.IOV_MAX) != void) assert(max_iovecs_len <= posix.IOV_MAX);
...@@ -1137,7 +1185,8 @@ pub const InitOptions = struct {...@@ -1137,7 +1185,8 @@ pub const InitOptions = struct {
1137 /// Affects the following operations:1185 /// Affects the following operations:
1138 /// * `fileIsTty`1186 /// * `fileIsTty`
1139 /// * `processExecutablePath` on OpenBSD and Haiku (observes "PATH").1187 /// * `processExecutablePath` on OpenBSD and Haiku (observes "PATH").
1140 environ: Environ = .{},1188 /// * `processSpawn`, `processSpawnPath`, `processReplace`, `processReplacePath`
1189 environ: process.Environ,
1141};1190};
11421191
1143/// Related:1192/// Related:
...@@ -1167,8 +1216,8 @@ pub fn init(...@@ -1167,8 +1216,8 @@ pub fn init(
1167 .old_sig_pipe = undefined,1216 .old_sig_pipe = undefined,
1168 .have_signal_handler = false,1217 .have_signal_handler = false,
1169 .argv0 = options.argv0,1218 .argv0 = options.argv0,
1170 .environ = options.environ,
1171 .worker_threads = .init(null),1219 .worker_threads = .init(null),
1220 .environ = .{ .process_environ = options.environ },
1172 };1221 };
11731222
1174 if (posix.Sigaction != void) {1223 if (posix.Sigaction != void) {
...@@ -1235,6 +1284,7 @@ pub fn deinit(t: *Threaded) void {...@@ -1235,6 +1284,7 @@ pub fn deinit(t: *Threaded) void {
1235 if (have_sig_io) posix.sigaction(.IO, &t.old_sig_io, null);1284 if (have_sig_io) posix.sigaction(.IO, &t.old_sig_io, null);
1236 if (have_sig_pipe) posix.sigaction(.PIPE, &t.old_sig_pipe, null);1285 if (have_sig_pipe) posix.sigaction(.PIPE, &t.old_sig_pipe, null);
1237 }1286 }
1287 t.null_file.deinit();
1238 t.* = undefined;1288 t.* = undefined;
1239}1289}
12401290
...@@ -1402,6 +1452,14 @@ pub fn io(t: *Threaded) Io {...@@ -1402,6 +1452,14 @@ pub fn io(t: *Threaded) Io {
1402 .tryLockStderr = tryLockStderr,1452 .tryLockStderr = tryLockStderr,
1403 .unlockStderr = unlockStderr,1453 .unlockStderr = unlockStderr,
1404 .processSetCurrentDir = processSetCurrentDir,1454 .processSetCurrentDir = processSetCurrentDir,
1455 .processReplace = processReplace,
1456 .processReplacePath = processReplacePath,
1457 .processSpawn = processSpawn,
1458 .processSpawnPath = processSpawnPath,
1459 .childWait = childWait,
1460 .childKill = childKill,
1461
1462 .progressParentFile = progressParentFile,
14051463
1406 .now = now,1464 .now = now,
1407 .sleep = sleep,1465 .sleep = sleep,
...@@ -1540,6 +1598,14 @@ pub fn ioBasic(t: *Threaded) Io {...@@ -1540,6 +1598,14 @@ pub fn ioBasic(t: *Threaded) Io {
1540 .tryLockStderr = tryLockStderr,1598 .tryLockStderr = tryLockStderr,
1541 .unlockStderr = unlockStderr,1599 .unlockStderr = unlockStderr,
1542 .processSetCurrentDir = processSetCurrentDir,1600 .processSetCurrentDir = processSetCurrentDir,
1601 .processReplace = processReplace,
1602 .processReplacePath = processReplacePath,
1603 .processSpawn = processSpawn,
1604 .processSpawnPath = processSpawnPath,
1605 .childWait = childWait,
1606 .childKill = childKill,
1607
1608 .progressParentFile = progressParentFile,
15431609
1544 .now = now,1610 .now = now,
1545 .sleep = sleep,1611 .sleep = sleep,
...@@ -1603,6 +1669,18 @@ const have_fchmod = switch (native_os) {...@@ -1603,6 +1669,18 @@ const have_fchmod = switch (native_os) {
1603 else => true,1669 else => true,
1604};1670};
16051671
1672const have_waitid = switch (native_os) {
1673 .linux => @hasField(std.os.linux.SYS, "waitid"),
1674 else => false,
1675};
1676
1677const have_wait4 = switch (native_os) {
1678 .linux => @hasField(std.os.linux.SYS, "wait4"),
1679 .dragonfly, .freebsd, .netbsd, .openbsd, .illumos, .serenity, .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => true,
1680 else => false,
1681};
1682
1683const open_sym = if (posix.lfs64_abi) posix.system.open64 else posix.system.open;
1606const openat_sym = if (posix.lfs64_abi) posix.system.openat64 else posix.system.openat;1684const openat_sym = if (posix.lfs64_abi) posix.system.openat64 else posix.system.openat;
1607const fstat_sym = if (posix.lfs64_abi) posix.system.fstat64 else posix.system.fstat;1685const fstat_sym = if (posix.lfs64_abi) posix.system.fstat64 else posix.system.fstat;
1608const fstatat_sym = if (posix.lfs64_abi) posix.system.fstatat64 else posix.system.fstatat;1686const fstatat_sym = if (posix.lfs64_abi) posix.system.fstatat64 else posix.system.fstatat;
...@@ -2249,7 +2327,7 @@ fn dirCreateDirPath(...@@ -2249,7 +2327,7 @@ fn dirCreateDirPath(
2249) Dir.CreateDirPathError!Dir.CreatePathStatus {2327) Dir.CreateDirPathError!Dir.CreatePathStatus {
2250 const t: *Threaded = @ptrCast(@alignCast(userdata));2328 const t: *Threaded = @ptrCast(@alignCast(userdata));
22512329
2252 var it = std.fs.path.componentIterator(sub_path);2330 var it = Dir.path.componentIterator(sub_path);
2253 var status: Dir.CreatePathStatus = .existed;2331 var status: Dir.CreatePathStatus = .existed;
2254 var component = it.last() orelse return error.BadPathName;2332 var component = it.last() orelse return error.BadPathName;
2255 while (true) {2333 while (true) {
...@@ -2309,9 +2387,9 @@ fn dirCreateDirPathOpenWindows(...@@ -2309,9 +2387,9 @@ fn dirCreateDirPathOpenWindows(
23092387
2310 _ = permissions; // TODO apply these permissions2388 _ = permissions; // TODO apply these permissions
23112389
2312 var it = std.fs.path.componentIterator(sub_path);2390 var it = Dir.path.componentIterator(sub_path);
2313 // If there are no components in the path, then create a dummy component with the full path.2391 // If there are no components in the path, then create a dummy component with the full path.
2314 var component: std.fs.path.NativeComponentIterator.Component = it.last() orelse .{2392 var component: Dir.path.NativeComponentIterator.Component = it.last() orelse .{
2315 .name = "",2393 .name = "",
2316 .path = sub_path,2394 .path = sub_path,
2317 };2395 };
...@@ -2349,7 +2427,7 @@ fn dirCreateDirPathOpenWindows(...@@ -2349,7 +2427,7 @@ fn dirCreateDirPathOpenWindows(
2349 },2427 },
2350 &.{2428 &.{
2351 .Length = @sizeOf(w.OBJECT_ATTRIBUTES),2429 .Length = @sizeOf(w.OBJECT_ATTRIBUTES),
2352 .RootDirectory = if (std.fs.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle,2430 .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle,
2353 .Attributes = .{},2431 .Attributes = .{},
2354 .ObjectName = &nt_name,2432 .ObjectName = &nt_name,
2355 .SecurityDescriptor = null,2433 .SecurityDescriptor = null,
...@@ -2988,7 +3066,7 @@ fn dirAccessWindows(...@@ -2988,7 +3066,7 @@ fn dirAccessWindows(
2988 };3066 };
2989 var attr: windows.OBJECT_ATTRIBUTES = .{3067 var attr: windows.OBJECT_ATTRIBUTES = .{
2990 .Length = @sizeOf(windows.OBJECT_ATTRIBUTES),3068 .Length = @sizeOf(windows.OBJECT_ATTRIBUTES),
2991 .RootDirectory = if (std.fs.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle,3069 .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle,
2992 .Attributes = .{},3070 .Attributes = .{},
2993 .ObjectName = &nt_name,3071 .ObjectName = &nt_name,
2994 .SecurityDescriptor = null,3072 .SecurityDescriptor = null,
...@@ -3537,7 +3615,7 @@ fn dirOpenFileWindows(...@@ -3537,7 +3615,7 @@ fn dirOpenFileWindows(
3537 _ = t;3615 _ = t;
3538 const sub_path_w_array = try windows.sliceToPrefixedFileW(dir.handle, sub_path);3616 const sub_path_w_array = try windows.sliceToPrefixedFileW(dir.handle, sub_path);
3539 const sub_path_w = sub_path_w_array.span();3617 const sub_path_w = sub_path_w_array.span();
3540 const dir_handle = if (std.fs.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle;3618 const dir_handle = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle;
3541 return dirOpenFileWtf16(dir_handle, sub_path_w, flags);3619 return dirOpenFileWtf16(dir_handle, sub_path_w, flags);
3542}3620}
35433621
...@@ -3620,7 +3698,10 @@ pub fn dirOpenFileWtf16(...@@ -3620,7 +3698,10 @@ pub fn dirOpenFileWtf16(
3620 // kernel bug with retry attempts.3698 // kernel bug with retry attempts.
3621 syscall.finish();3699 syscall.finish();
3622 if (max_attempts - attempt == 0) return error.SharingViolation;3700 if (max_attempts - attempt == 0) return error.SharingViolation;
3623 _ = w.kernel32.SleepEx((@as(u32, 1) << attempt) >> 1, w.TRUE);3701 try parking_sleep.sleep(.{ .duration = .{
3702 .raw = .fromMilliseconds((@as(u32, 1) << attempt) >> 1),
3703 .clock = .awake,
3704 } });
3624 attempt += 1;3705 attempt += 1;
3625 syscall = try .start();3706 syscall = try .start();
3626 continue;3707 continue;
...@@ -3642,7 +3723,10 @@ pub fn dirOpenFileWtf16(...@@ -3642,7 +3723,10 @@ pub fn dirOpenFileWtf16(
3642 // fixed by sleeping and retrying until the error goes away.3723 // fixed by sleeping and retrying until the error goes away.
3643 syscall.finish();3724 syscall.finish();
3644 if (max_attempts - attempt == 0) return error.SharingViolation;3725 if (max_attempts - attempt == 0) return error.SharingViolation;
3645 _ = w.kernel32.SleepEx((@as(u32, 1) << attempt) >> 1, w.TRUE);3726 try parking_sleep.sleep(.{ .duration = .{
3727 .raw = .fromMilliseconds((@as(u32, 1) << attempt) >> 1),
3728 .clock = .awake,
3729 } });
3646 attempt += 1;3730 attempt += 1;
3647 syscall = try .start();3731 syscall = try .start();
3648 continue;3732 continue;
...@@ -3935,7 +4019,7 @@ pub fn dirOpenDirWindows(...@@ -3935,7 +4019,7 @@ pub fn dirOpenDirWindows(
3935 },4019 },
3936 &.{4020 &.{
3937 .Length = @sizeOf(w.OBJECT_ATTRIBUTES),4021 .Length = @sizeOf(w.OBJECT_ATTRIBUTES),
3938 .RootDirectory = if (std.fs.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle,4022 .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle,
3939 .Attributes = .{},4023 .Attributes = .{},
3940 .ObjectName = &nt_name,4024 .ObjectName = &nt_name,
3941 .SecurityDescriptor = null,4025 .SecurityDescriptor = null,
...@@ -5083,7 +5167,7 @@ fn dirDeleteWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, remov...@@ -5083,7 +5167,7 @@ fn dirDeleteWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, remov
5083 } },5167 } },
5084 &.{5168 &.{
5085 .Length = @sizeOf(w.OBJECT_ATTRIBUTES),5169 .Length = @sizeOf(w.OBJECT_ATTRIBUTES),
5086 .RootDirectory = if (std.fs.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle,5170 .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle,
5087 .Attributes = .{},5171 .Attributes = .{},
5088 .ObjectName = &nt_name,5172 .ObjectName = &nt_name,
5089 .SecurityDescriptor = null,5173 .SecurityDescriptor = null,
...@@ -5360,7 +5444,7 @@ fn dirRenameWindows(...@@ -5360,7 +5444,7 @@ fn dirRenameWindows(
5360 .POSIX_SEMANTICS = true,5444 .POSIX_SEMANTICS = true,
5361 .IGNORE_READONLY_ATTRIBUTE = true,5445 .IGNORE_READONLY_ATTRIBUTE = true,
5362 },5446 },
5363 .RootDirectory = if (std.fs.path.isAbsoluteWindowsWtf16(new_path_w)) null else new_dir.handle,5447 .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(new_path_w)) null else new_dir.handle,
5364 .FileName = new_path_w,5448 .FileName = new_path_w,
5365 });5449 });
5366 var io_status_block: w.IO_STATUS_BLOCK = undefined;5450 var io_status_block: w.IO_STATUS_BLOCK = undefined;
...@@ -5389,7 +5473,7 @@ fn dirRenameWindows(...@@ -5389,7 +5473,7 @@ fn dirRenameWindows(
5389 if (need_fallback) {5473 if (need_fallback) {
5390 const rename_info: w.FILE.RENAME_INFORMATION = .init(.{5474 const rename_info: w.FILE.RENAME_INFORMATION = .init(.{
5391 .Flags = .{ .REPLACE_IF_EXISTS = replace_if_exists },5475 .Flags = .{ .REPLACE_IF_EXISTS = replace_if_exists },
5392 .RootDirectory = if (std.fs.path.isAbsoluteWindowsWtf16(new_path_w)) null else new_dir.handle,5476 .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(new_path_w)) null else new_dir.handle,
5393 .FileName = new_path_w,5477 .FileName = new_path_w,
5394 });5478 });
5395 var io_status_block: w.IO_STATUS_BLOCK = undefined;5479 var io_status_block: w.IO_STATUS_BLOCK = undefined;
...@@ -5614,7 +5698,7 @@ fn dirSymLinkWindows(...@@ -5614,7 +5698,7 @@ fn dirSymLinkWindows(
5614 // Already an NT path, no need to do anything to it5698 // Already an NT path, no need to do anything to it
5615 break :target_path target_path_w.span();5699 break :target_path target_path_w.span();
5616 } else {5700 } else {
5617 switch (w.getWin32PathType(u16, target_path_w.span())) {5701 switch (Dir.path.getWin32PathType(u16, target_path_w.span())) {
5618 // Rooted paths need to avoid getting put through wToPrefixedFileW5702 // Rooted paths need to avoid getting put through wToPrefixedFileW
5619 // (and they are treated as relative in this context)5703 // (and they are treated as relative in this context)
5620 // Note: It seems that rooted paths in symbolic links are relative to5704 // Note: It seems that rooted paths in symbolic links are relative to
...@@ -5624,13 +5708,13 @@ fn dirSymLinkWindows(...@@ -5624,13 +5708,13 @@ fn dirSymLinkWindows(
5624 // the C:\ drive.5708 // the C:\ drive.
5625 .rooted => break :target_path target_path_w.span(),5709 .rooted => break :target_path target_path_w.span(),
5626 // Keep relative paths relative, but anything else needs to get NT-prefixed.5710 // Keep relative paths relative, but anything else needs to get NT-prefixed.
5627 else => if (!std.fs.path.isAbsoluteWindowsWtf16(target_path_w.span()))5711 else => if (!Dir.path.isAbsoluteWindowsWtf16(target_path_w.span()))
5628 break :target_path target_path_w.span(),5712 break :target_path target_path_w.span(),
5629 }5713 }
5630 }5714 }
5631 var prefixed_target_path = try w.wToPrefixedFileW(dir.handle, target_path_w.span());5715 var prefixed_target_path = try w.wToPrefixedFileW(dir.handle, target_path_w.span());
5632 // We do this after prefixing to ensure that drive-relative paths are treated as absolute5716 // We do this after prefixing to ensure that drive-relative paths are treated as absolute
5633 is_target_absolute = std.fs.path.isAbsoluteWindowsWtf16(prefixed_target_path.span());5717 is_target_absolute = Dir.path.isAbsoluteWindowsWtf16(prefixed_target_path.span());
5634 break :target_path prefixed_target_path.span();5718 break :target_path prefixed_target_path.span();
5635 };5719 };
56365720
...@@ -5638,8 +5722,8 @@ fn dirSymLinkWindows(...@@ -5638,8 +5722,8 @@ fn dirSymLinkWindows(
5638 var buffer: [w.MAXIMUM_REPARSE_DATA_BUFFER_SIZE]u8 = undefined;5722 var buffer: [w.MAXIMUM_REPARSE_DATA_BUFFER_SIZE]u8 = undefined;
5639 const buf_len = @sizeOf(SYMLINK_DATA) + final_target_path.len * 4;5723 const buf_len = @sizeOf(SYMLINK_DATA) + final_target_path.len * 4;
5640 const header_len = @sizeOf(w.ULONG) + @sizeOf(w.USHORT) * 2;5724 const header_len = @sizeOf(w.ULONG) + @sizeOf(w.USHORT) * 2;
5641 const target_is_absolute = std.fs.path.isAbsoluteWindowsWtf16(final_target_path);5725 const target_is_absolute = Dir.path.isAbsoluteWindowsWtf16(final_target_path);
5642 const symlink_data = SYMLINK_DATA{5726 const symlink_data: SYMLINK_DATA = .{
5643 .ReparseTag = .SYMLINK,5727 .ReparseTag = .SYMLINK,
5644 .ReparseDataLength = @intCast(buf_len - header_len),5728 .ReparseDataLength = @intCast(buf_len - header_len),
5645 .Reserved = 0,5729 .Reserved = 0,
...@@ -7892,7 +7976,7 @@ fn posixSeekTo(fd: posix.fd_t, offset: u64) File.SeekError!void {...@@ -7892,7 +7976,7 @@ fn posixSeekTo(fd: posix.fd_t, offset: u64) File.SeekError!void {
7892 }7976 }
7893}7977}
78947978
7895fn processExecutableOpen(userdata: ?*anyopaque, flags: File.OpenFlags) std.process.OpenExecutableError!File {7979fn processExecutableOpen(userdata: ?*anyopaque, flags: File.OpenFlags) process.OpenExecutableError!File {
7896 const t: *Threaded = @ptrCast(@alignCast(userdata));7980 const t: *Threaded = @ptrCast(@alignCast(userdata));
7897 switch (native_os) {7981 switch (native_os) {
7898 .wasi => return error.OperationUnsupported,7982 .wasi => return error.OperationUnsupported,
...@@ -7933,7 +8017,7 @@ fn processExecutableOpen(userdata: ?*anyopaque, flags: File.OpenFlags) std.proce...@@ -7933,7 +8017,7 @@ fn processExecutableOpen(userdata: ?*anyopaque, flags: File.OpenFlags) std.proce
7933 }8017 }
7934}8018}
79358019
7936fn processExecutablePath(userdata: ?*anyopaque, out_buffer: []u8) std.process.ExecutablePathError!usize {8020fn processExecutablePath(userdata: ?*anyopaque, out_buffer: []u8) process.ExecutablePathError!usize {
7937 const t: *Threaded = @ptrCast(@alignCast(userdata));8021 const t: *Threaded = @ptrCast(@alignCast(userdata));
79388022
7939 switch (native_os) {8023 switch (native_os) {
...@@ -11693,14 +11777,14 @@ fn netLookupFallible(...@@ -11693,14 +11777,14 @@ fn netLookupFallible(
11693fn lockStderr(userdata: ?*anyopaque, terminal_mode: ?Io.Terminal.Mode) Io.Cancelable!Io.LockedStderr {11777fn lockStderr(userdata: ?*anyopaque, terminal_mode: ?Io.Terminal.Mode) Io.Cancelable!Io.LockedStderr {
11694 const t: *Threaded = @ptrCast(@alignCast(userdata));11778 const t: *Threaded = @ptrCast(@alignCast(userdata));
11695 // Only global mutex since this is Threaded.11779 // Only global mutex since this is Threaded.
11696 std.process.stderr_thread_mutex.lock();11780 process.stderr_thread_mutex.lock();
11697 return initLockedStderr(t, terminal_mode);11781 return initLockedStderr(t, terminal_mode);
11698}11782}
1169911783
11700fn tryLockStderr(userdata: ?*anyopaque, terminal_mode: ?Io.Terminal.Mode) Io.Cancelable!?Io.LockedStderr {11784fn tryLockStderr(userdata: ?*anyopaque, terminal_mode: ?Io.Terminal.Mode) Io.Cancelable!?Io.LockedStderr {
11701 const t: *Threaded = @ptrCast(@alignCast(userdata));11785 const t: *Threaded = @ptrCast(@alignCast(userdata));
11702 // Only global mutex since this is Threaded.11786 // Only global mutex since this is Threaded.
11703 if (!std.process.stderr_thread_mutex.tryLock()) return null;11787 if (!process.stderr_thread_mutex.tryLock()) return null;
11704 return try initLockedStderr(t, terminal_mode);11788 return try initLockedStderr(t, terminal_mode);
11705}11789}
1170611790
...@@ -11731,10 +11815,10 @@ fn unlockStderr(userdata: ?*anyopaque) void {...@@ -11731,10 +11815,10 @@ fn unlockStderr(userdata: ?*anyopaque) void {
11731 };11815 };
11732 t.stderr_writer.interface.end = 0;11816 t.stderr_writer.interface.end = 0;
11733 t.stderr_writer.interface.buffer = &.{};11817 t.stderr_writer.interface.buffer = &.{};
11734 std.process.stderr_thread_mutex.unlock();11818 process.stderr_thread_mutex.unlock();
11735}11819}
1173611820
11737fn processSetCurrentDir(userdata: ?*anyopaque, dir: Dir) std.process.SetCurrentDirError!void {11821fn processSetCurrentDir(userdata: ?*anyopaque, dir: Dir) process.SetCurrentDirError!void {
11738 if (native_os == .wasi) return error.OperationUnsupported;11822 if (native_os == .wasi) return error.OperationUnsupported;
11739 const t: *Threaded = @ptrCast(@alignCast(userdata));11823 const t: *Threaded = @ptrCast(@alignCast(userdata));
11740 _ = t;11824 _ = t;
...@@ -11769,38 +11853,7 @@ fn processSetCurrentDir(userdata: ?*anyopaque, dir: Dir) std.process.SetCurrentD...@@ -11769,38 +11853,7 @@ fn processSetCurrentDir(userdata: ?*anyopaque, dir: Dir) std.process.SetCurrentD
11769 };11853 };
11770 }11854 }
1177111855
11772 if (dir.handle == posix.AT.FDCWD) return;11856 return fchdir(dir.handle);
11773
11774 const syscall: Syscall = try .start();
11775 while (true) {
11776 switch (posix.errno(posix.system.fchdir(dir.handle))) {
11777 .SUCCESS => return syscall.finish(),
11778 .INTR => {
11779 try syscall.checkCancel();
11780 continue;
11781 },
11782 .ACCES => {
11783 syscall.finish();
11784 return error.AccessDenied;
11785 },
11786 .BADF => |err| {
11787 syscall.finish();
11788 return errnoBug(err);
11789 },
11790 .NOTDIR => {
11791 syscall.finish();
11792 return error.NotDir;
11793 },
11794 .IO => {
11795 syscall.finish();
11796 return error.FileSystem;
11797 },
11798 else => |err| {
11799 syscall.finish();
11800 return posix.unexpectedErrno(err);
11801 },
11802 }
11803 }
11804}11857}
1180511858
11806pub const PosixAddress = extern union {11859pub const PosixAddress = extern union {
...@@ -12577,6 +12630,45 @@ fn initializeWsa(t: *Threaded) error{ NetworkDown, Canceled }!void {...@@ -12577,6 +12630,45 @@ fn initializeWsa(t: *Threaded) error{ NetworkDown, Canceled }!void {
1257712630
12578fn doNothingSignalHandler(_: posix.SIG) callconv(.c) void {}12631fn doNothingSignalHandler(_: posix.SIG) callconv(.c) void {}
1257912632
12633const WindowsEnvironStrings = struct {
12634 PATH: ?[:0]const u16 = null,
12635 PATHEXT: ?[:0]const u16 = null,
12636
12637 fn scan() WindowsEnvironStrings {
12638 const ptr = windows.peb().ProcessParameters.Environment;
12639
12640 var result: WindowsEnvironStrings = .{};
12641 var i: usize = 0;
12642 while (ptr[i] != 0) {
12643 const key_start = i;
12644
12645 // There are some special environment variables that start with =,
12646 // so we need a special case to not treat = as a key/value separator
12647 // if it's the first character.
12648 // https://devblogs.microsoft.com/oldnewthing/20100506-00/?p=14133
12649 if (ptr[key_start] == '=') i += 1;
12650
12651 while (ptr[i] != 0 and ptr[i] != '=') : (i += 1) {}
12652 const key_w = ptr[key_start..i];
12653
12654 if (ptr[i] == '=') i += 1;
12655
12656 const value_start = i;
12657 while (ptr[i] != 0) : (i += 1) {}
12658 const value_w = ptr[value_start..i :0];
12659
12660 i += 1; // skip over null byte
12661
12662 inline for (@typeInfo(WindowsEnvironStrings).@"struct".fields) |field| {
12663 const field_name_w = comptime std.unicode.wtf8ToWtf16LeStringLiteral(field.name);
12664 if (std.mem.eql(u16, key_w, field_name_w)) @field(result, field.name) = value_w;
12665 }
12666 }
12667
12668 return result;
12669 }
12670};
12671
12580fn scanEnviron(t: *Threaded) void {12672fn scanEnviron(t: *Threaded) void {
12581 t.mutex.lock();12673 t.mutex.lock();
12582 defer t.mutex.unlock();12674 defer t.mutex.unlock();
...@@ -12585,6 +12677,9 @@ fn scanEnviron(t: *Threaded) void {...@@ -12585,6 +12677,9 @@ fn scanEnviron(t: *Threaded) void {
12585 t.environ.initialized = true;12677 t.environ.initialized = true;
1258612678
12587 if (is_windows) {12679 if (is_windows) {
12680 // This value expires with any call that modifies the environment,
12681 // which is outside of this Io implementation's control, so references
12682 // must be short-lived.
12588 const ptr = windows.peb().ProcessParameters.Environment;12683 const ptr = windows.peb().ProcessParameters.Environment;
1258912684
12590 var i: usize = 0;12685 var i: usize = 0;
...@@ -12652,9 +12747,9 @@ fn scanEnviron(t: *Threaded) void {...@@ -12652,9 +12747,9 @@ fn scanEnviron(t: *Threaded) void {
12652 }12747 }
12653 comptime assert(@sizeOf(Environ.String) == 0);12748 comptime assert(@sizeOf(Environ.String) == 0);
12654 }12749 }
12655 } else if (builtin.link_libc) {12750 } else {
12656 var ptr = std.c.environ;12751 for (t.environ.process_environ.block) |opt_line| {
12657 while (ptr[0]) |line| : (ptr += 1) {12752 const line = opt_line.?;
12658 var line_i: usize = 0;12753 var line_i: usize = 0;
12659 while (line[line_i] != 0 and line[line_i] != '=') : (line_i += 1) {}12754 while (line[line_i] != 0 and line[line_i] != '=') : (line_i += 1) {}
12660 const key = line[0..line_i];12755 const key = line[0..line_i];
...@@ -12667,190 +12762,2150 @@ fn scanEnviron(t: *Threaded) void {...@@ -12667,190 +12762,2150 @@ fn scanEnviron(t: *Threaded) void {
12667 t.environ.exist.NO_COLOR = true;12762 t.environ.exist.NO_COLOR = true;
12668 } else if (std.mem.eql(u8, key, "CLICOLOR_FORCE")) {12763 } else if (std.mem.eql(u8, key, "CLICOLOR_FORCE")) {
12669 t.environ.exist.CLICOLOR_FORCE = true;12764 t.environ.exist.CLICOLOR_FORCE = true;
12670 } else if (@hasField(Environ.String, "PATH") and std.mem.eql(u8, key, "PATH")) {12765 } else if (std.mem.eql(u8, key, "ZIG_PROGRESS")) {
12671 t.environ.string.PATH = value;12766 t.environ.zig_progress_handle = std.fmt.parseInt(u31, value, 10) catch error.UnrecognizedFormat;
12767 } else inline for (@typeInfo(Environ.String).@"struct".fields) |field| {
12768 if (std.mem.eql(u8, key, field.name)) @field(t.environ.string, field.name) = value;
12672 }12769 }
12673 }12770 }
12674 } else {12771 }
12675 for (t.environ.block) |line| {12772}
12676 var line_i: usize = 0;
12677 while (line[line_i] != 0 and line[line_i] != '=') : (line_i += 1) {}
12678 const key = line[0..line_i];
1267912773
12680 var end_i: usize = line_i;12774fn processReplace(userdata: ?*anyopaque, options: process.ReplaceOptions) process.ReplaceError {
12681 while (line[end_i] != 0) : (end_i += 1) {}12775 const t: *Threaded = @ptrCast(@alignCast(userdata));
12682 const value = line[line_i + 1 .. end_i :0];
1268312776
12684 if (std.mem.eql(u8, key, "NO_COLOR")) {12777 if (!process.can_replace) return error.OperationUnsupported;
12685 t.environ.exist.NO_COLOR = true;12778
12686 } else if (std.mem.eql(u8, key, "CLICOLOR_FORCE")) {12779 t.scanEnviron(); // for PATH
12687 t.environ.exist.CLICOLOR_FORCE = true;12780 const PATH = t.environ.string.PATH orelse default_PATH;
12688 } else if (@hasField(Environ.String, "PATH") and std.mem.eql(u8, key, "PATH")) {12781
12689 t.environ.string.PATH = value;12782 var arena_allocator = std.heap.ArenaAllocator.init(t.allocator);
12690 }12783 defer arena_allocator.deinit();
12784 const arena = arena_allocator.allocator();
12785
12786 const argv_buf = try arena.allocSentinel(?[*:0]const u8, options.argv.len, null);
12787 for (options.argv, 0..) |arg, i| argv_buf[i] = (try arena.dupeZ(u8, arg)).ptr;
12788
12789 const envp: [*:null]const ?[*:0]const u8 = m: {
12790 const prog_fd: i32 = -1;
12791 if (options.environ_map) |environ_map| {
12792 break :m (try environ_map.createBlockPosix(arena, .{
12793 .zig_progress_fd = prog_fd,
12794 })).ptr;
12691 }12795 }
12692 }12796 break :m (try process.Environ.createBlockPosix(t.environ.process_environ, arena, .{
12797 .zig_progress_fd = prog_fd,
12798 })).ptr;
12799 };
12800
12801 return posixExecv(options.expand_arg0, argv_buf.ptr[0].?, argv_buf.ptr, envp, PATH);
12693}12802}
1269412803
12695test {12804fn processReplacePath(userdata: ?*anyopaque, dir: Dir, options: process.ReplaceOptions) process.ReplaceError {
12696 _ = @import("Threaded/test.zig");12805 if (!process.can_replace) return error.OperationUnsupported;
12806 _ = userdata;
12807 _ = dir;
12808 _ = options;
12809 @panic("TODO processReplacePath");
12697}12810}
1269812811
12699const use_parking_futex = switch (builtin.target.os.tag) {12812fn processSpawnPath(userdata: ?*anyopaque, dir: Dir, options: process.SpawnOptions) process.SpawnError!process.Child {
12700 .windows => true, // RtlWaitOnAddress is a userland implementation anyway12813 if (!process.can_spawn) return error.OperationUnsupported;
12701 .netbsd => true, // NetBSD has `futex(2)`, but it's historically been quite buggy. TODO: evaluate whether it's okay to use now.12814 _ = userdata;
12702 .illumos => true, // Illumos has no futex mechanism12815 _ = dir;
12703 else => false,12816 _ = options;
12817 @panic("TODO processSpawnPath");
12818}
12819
12820const processSpawn = switch (native_os) {
12821 .wasi, .ios, .tvos, .visionos, .watchos => processSpawnUnsupported,
12822 .windows => processSpawnWindows,
12823 else => processSpawnPosix,
12704};12824};
12705const use_parking_sleep = switch (builtin.target.os.tag) {
12706 // On Windows, we can implement sleep either with `NtDelayExecution` (which is how `SleepEx` in
12707 // kernel32 works) or `NtWaitForAlertByThreadId` (thread parking). We're already using the
12708 // latter for futex, so we may as well use it for sleeping too, to maximise code reuse. I'm
12709 // also more confident that it will always correctly handle the cancelation race (so "unpark"
12710 // before "park" causes "park" to return immediately): it *seems* like alertable sleeps paired
12711 // with `NtAlertThread` do actually do this too, but there could be some caveat (e.g. it might
12712 // fail under some specific condition), whereas `NtWaitForAlertByThreadId` must reliably trigger
12713 // this behavior because `RtlWaitOnAddress` relies on it.
12714 .windows => true,
1271512825
12716 // These targets have `_lwp_park`, which is superior to POSIX nanosleep because it has a better12826fn processSpawnUnsupported(userdata: ?*anyopaque, options: process.SpawnOptions) process.SpawnError!process.Child {
12717 // cancelation mechanism.12827 _ = userdata;
12718 .netbsd,12828 _ = options;
12719 .illumos,12829 return error.OperationUnsupported;
12720 => true,12830}
1272112831
12722 else => false,12832const Spawned = struct {
12833 pid: posix.pid_t,
12834 err_fd: posix.fd_t,
12835 stdin: ?File,
12836 stdout: ?File,
12837 stderr: ?File,
12723};12838};
1272412839
12725const parking_futex = struct {12840fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Spawned {
12726 comptime {12841 // The child process does need to access (one end of) these pipes. However,
12727 assert(use_parking_futex);12842 // we must initially set CLOEXEC to avoid a race condition. If another thread
12728 }12843 // is racing to spawn a different child process, we don't want it to inherit
12844 // these FDs in any scenario; that would mean that, for instance, calls to
12845 // `poll` from the parent would not report the child's stdout as closing when
12846 // expected, since the other child may retain a reference to the write end of
12847 // the pipe. So, we create the pipes with CLOEXEC initially. After fork, we
12848 // need to do something in the new child to make sure we preserve the reference
12849 // we want. We could use `fcntl` to remove CLOEXEC from the FD, but as it
12850 // turns out, we `dup2` everything anyway, so there's no need!
12851 const pipe_flags: posix.O = .{ .CLOEXEC = true };
1272912852
12730 const Bucket = struct {12853 const stdin_pipe = if (options.stdin == .pipe) try pipe2(pipe_flags) else undefined;
12731 /// Used as a fast check for `wake` to avoid having to acquire `mutex` to discover there are no12854 errdefer if (options.stdin == .pipe) {
12732 /// waiters. It is important for `wait` to increment this *before* checking the futex value to12855 destroyPipe(stdin_pipe);
12733 /// avoid a race.12856 };
12734 num_waiters: std.atomic.Value(u32),
12735 /// Protects `waiters`.
12736 mutex: std.Thread.Mutex,
12737 waiters: std.DoublyLinkedList,
1273812857
12739 /// Prevent false sharing between buckets.12858 const stdout_pipe = if (options.stdout == .pipe) try pipe2(pipe_flags) else undefined;
12740 _: void align(std.atomic.cache_line) = {},12859 errdefer if (options.stdout == .pipe) {
12860 destroyPipe(stdout_pipe);
12861 };
1274112862
12742 const init: Bucket = .{ .num_waiters = .init(0), .mutex = .{}, .waiters = .{} };12863 const stderr_pipe = if (options.stderr == .pipe) try pipe2(pipe_flags) else undefined;
12864 errdefer if (options.stderr == .pipe) {
12865 destroyPipe(stderr_pipe);
12743 };12866 };
1274412867
12745 const Waiter = struct {12868 const any_ignore = (options.stdin == .ignore or options.stdout == .ignore or options.stderr == .ignore);
12746 node: std.DoublyLinkedList.Node,12869 const dev_null_fd = if (any_ignore) try getDevNullFd(t) else undefined;
12747 address: usize,12870
12748 tid: std.Thread.Id,12871 const prog_pipe: [2]posix.fd_t = p: {
12749 /// `thread_status.cancelation` is `.parked` while the thread is waiting. The single thread12872 if (options.progress_node.index == .none) {
12750 /// which atomically updates it (to `.none` or `.canceling`) is responsible for:12873 break :p .{ -1, -1 };
12751 ///12874 } else {
12752 /// * Removing the `Waiter` from `Bucket.waiters`12875 // We use CLOEXEC for the same reason as in `pipe_flags`.
12753 /// * Decrementing `Bucket.num_waiters`12876 break :p try pipe2(.{ .NONBLOCK = true, .CLOEXEC = true });
12754 /// * Unparking the thread (*after* the above, so that the `Waiter` does not go out of scope12877 }
12755 /// while it is still in the `Bucket`).
12756 thread_status: *std.atomic.Value(Thread.Status),
12757 };12878 };
12879 errdefer destroyPipe(prog_pipe);
1275812880
12759 fn bucketForAddress(address: usize) *Bucket {12881 var arena_allocator = std.heap.ArenaAllocator.init(t.allocator);
12760 const global = struct {12882 defer arena_allocator.deinit();
12761 /// Length must be a power of two. The longer this array, the less likely contention is12883 const arena = arena_allocator.allocator();
12762 /// between different futexes. This length seems like it'll provide a reasonable balance
12763 /// between contention and memory usage: assuming a 128-byte `Bucket` (due to cache line
12764 /// alignment), this uses 32 KiB of memory.
12765 var buckets: [256]Bucket = @splat(.init);
12766 };
1276712884
12768 // Here we use Fibonacci hashing: the golden ratio can be used to evenly redistribute input12885 // The POSIX standard does not allow malloc() between fork() and execve(),
12769 // values across a range, giving a poor, but extremely quick to compute, hash.12886 // and this allocator may be a libc allocator.
12887 // I have personally observed the child process deadlocking when it tries
12888 // to call malloc() due to a heap allocation between fork() and execve(),
12889 // in musl v1.1.24.
12890 // Additionally, we want to reduce the number of possible ways things
12891 // can fail between fork() and execve().
12892 // Therefore, we do all the allocation for the execve() before the fork().
12893 // This means we must do the null-termination of argv and env vars here.
12894 const argv_buf = try arena.allocSentinel(?[*:0]const u8, options.argv.len, null);
12895 for (options.argv, 0..) |arg, i| argv_buf[i] = (try arena.dupeZ(u8, arg)).ptr;
1277012896
12771 // This literal is the rounded value of '2^64 / phi' (where 'phi' is the golden ratio). The12897 const prog_fileno = 3;
12772 // shift then converts it to '2^b / phi', where 'b' is the pointer bit width.12898 comptime assert(@max(posix.STDIN_FILENO, posix.STDOUT_FILENO, posix.STDERR_FILENO) + 1 == prog_fileno);
12773 const fibonacci_multiplier = 0x9E3779B97F4A7C15 >> (64 - @bitSizeOf(usize));
12774 const hashed = address *% fibonacci_multiplier;
1277512899
12776 comptime assert(std.math.isPowerOfTwo(global.buckets.len));12900 const envp: [*:null]const ?[*:0]const u8 = m: {
12777 // The high bits of `hashed` have better entropy than the low bits.12901 const prog_fd: i32 = if (prog_pipe[1] == -1) -1 else prog_fileno;
12778 const index = hashed >> (@bitSizeOf(usize) - @ctz(global.buckets.len));12902 if (options.environ_map) |environ_map| {
12903 break :m (try environ_map.createBlockPosix(arena, .{
12904 .zig_progress_fd = prog_fd,
12905 })).ptr;
12906 }
12907 break :m (try process.Environ.createBlockPosix(t.environ.process_environ, arena, .{
12908 .zig_progress_fd = prog_fd,
12909 })).ptr;
12910 };
1277912911
12780 return &global.buckets[index];12912 // This pipe communicates to the parent errors in the child between `fork` and `execvpe`.
12781 }12913 // It is closed by the child (via CLOEXEC) without writing if `execvpe` succeeds.
12914 const err_pipe: [2]posix.fd_t = try pipe2(.{ .CLOEXEC = true });
12915 errdefer destroyPipe(err_pipe);
1278212916
12783 fn wait(ptr: *const u32, expect: u32, uncancelable: bool, timeout: Io.Timeout) Io.Cancelable!void {12917 t.scanEnviron(); // for PATH
12784 const bucket = bucketForAddress(@intFromPtr(ptr));12918 const PATH = t.environ.string.PATH orelse default_PATH;
1278512919
12786 // Put the threadlocal access outside of the critical section.12920 const pid_result: posix.pid_t = fork: {
12787 const opt_thread = Thread.current;12921 const rc = posix.system.fork();
12788 const self_tid = if (opt_thread) |thread| thread.id else std.Thread.getCurrentId();12922 switch (posix.errno(rc)) {
12923 .SUCCESS => break :fork @intCast(rc),
12924 .AGAIN => return error.SystemResources,
12925 .NOMEM => return error.SystemResources,
12926 .NOSYS => return error.OperationUnsupported,
12927 else => |err| return posix.unexpectedErrno(err),
12928 }
12929 };
1278912930
12790 var waiter: Waiter = .{12931 if (pid_result == 0) {
12791 .node = undefined, // populated by list append12932 defer comptime unreachable; // We are the child.
12792 .address = @intFromPtr(ptr),12933 if (Thread.current) |current_thread| current_thread.cancel_protection = .blocked;
12793 .tid = self_tid,12934 const ep1 = err_pipe[1];
12794 .thread_status = undefined, // populated in critical section
12795 };
1279612935
12797 var status_buf: std.atomic.Value(Thread.Status) = undefined;12936 setUpChildIo(options.stdin, stdin_pipe[0], posix.STDIN_FILENO, dev_null_fd) catch |err| forkBail(ep1, err);
12937 setUpChildIo(options.stdout, stdout_pipe[1], posix.STDOUT_FILENO, dev_null_fd) catch |err| forkBail(ep1, err);
12938 setUpChildIo(options.stderr, stderr_pipe[1], posix.STDERR_FILENO, dev_null_fd) catch |err| forkBail(ep1, err);
1279812939
12799 {12940 if (options.cwd_dir) |cwd| {
12800 bucket.mutex.lock();12941 fchdir(cwd.handle) catch |err| forkBail(ep1, err);
12801 defer bucket.mutex.unlock();12942 } else if (options.cwd) |cwd| {
12943 chdir(cwd) catch |err| forkBail(ep1, err);
12944 }
1280212945
12803 _ = bucket.num_waiters.fetchAdd(1, .acquire);12946 // Must happen after fchdir above, the cwd file descriptor might be
12947 // equal to prog_fileno and be clobbered by this dup2 call.
12948 if (prog_pipe[1] != -1) dup2(prog_pipe[1], prog_fileno) catch |err| forkBail(ep1, err);
1280412949
12805 if (@atomicLoad(u32, ptr, .monotonic) != expect) {12950 if (options.gid) |gid| {
12806 assert(bucket.num_waiters.fetchSub(1, .monotonic) > 0);12951 switch (posix.errno(posix.system.setregid(gid, gid))) {
12807 return;12952 .SUCCESS => {},
12953 .AGAIN => forkBail(ep1, error.ResourceLimitReached),
12954 .INVAL => forkBail(ep1, error.InvalidUserId),
12955 .PERM => forkBail(ep1, error.PermissionDenied),
12956 else => forkBail(ep1, error.Unexpected),
12808 }12957 }
12958 }
1280912959
12810 // This is in the critical section to avoid marking the thread as parked until we're12960 if (options.uid) |uid| {
12811 // certain that we're actually going to park.12961 switch (posix.errno(posix.system.setreuid(uid, uid))) {
12812 waiter.thread_status = status: {12962 .SUCCESS => {},
12813 cancelable: {12963 .AGAIN => forkBail(ep1, error.ResourceLimitReached),
12814 if (uncancelable) break :cancelable;12964 .INVAL => forkBail(ep1, error.InvalidUserId),
12815 const thread = opt_thread orelse break :cancelable;12965 .PERM => forkBail(ep1, error.PermissionDenied),
12816 switch (thread.cancel_protection) {12966 else => forkBail(ep1, error.Unexpected),
12817 .blocked => break :cancelable,12967 }
12818 .unblocked => {},12968 }
12819 }
12820 thread.futex_waiter = &waiter;
12821 const old_status = thread.status.fetchOr(
12822 .{ .cancelation = @enumFromInt(0b001), .awaitable = .null },
12823 .release, // release `thread.futex_waiter`
12824 );
12825 switch (old_status.cancelation) {
12826 .none => {}, // status is now `.parked`
12827 .canceling => {
12828 // status is now `.canceled`
12829 assert(bucket.num_waiters.fetchSub(1, .monotonic) > 0);
12830 return error.Canceled;
12831 },
12832 .canceled => break :cancelable, // status is still `.canceled`
12833 .parked => unreachable,
12834 .blocked => unreachable,
12835 .blocked_windows_dns => unreachable,
12836 .blocked_canceling => unreachable,
12837 }
12838 // We could now be unparked for a cancelation at any time!
12839 break :status &thread.status;
12840 }
12841 // This is an uncancelable wait, so just use `status_buf`. Note that the value of
12842 // `status_buf.awaitable` is irrelevant because this is only visible to futex code,
12843 // while only cancelation cares about `awaitable`.
12844 status_buf.raw = .{ .cancelation = .parked, .awaitable = .null };
12845 break :status &status_buf;
12846 };
1284712969
12848 bucket.waiters.append(&waiter.node);12970 if (options.pgid) |pid| {
12971 switch (posix.errno(posix.system.setpgid(0, pid))) {
12972 .SUCCESS => {},
12973 .ACCES => forkBail(ep1, error.ProcessAlreadyExec),
12974 .INVAL => forkBail(ep1, error.InvalidProcessGroupId),
12975 .PERM => forkBail(ep1, error.PermissionDenied),
12976 else => forkBail(ep1, error.Unexpected),
12977 }
12849 }12978 }
1285012979
12851 if (park(timeout, ptr)) {12980 if (options.start_suspended) {
12852 // We were unparked by either `wake` or cancelation, so our current status is either12981 switch (posix.errno(posix.system.kill(posix.system.getpid(), .STOP))) {
12853 // `.none` or `.canceling`. In either case, they've already removed `waiter` from12982 .SUCCESS => {},
12983 .PERM => forkBail(ep1, error.PermissionDenied),
12984 else => forkBail(ep1, error.Unexpected),
12985 }
12986 }
12987
12988 const err = posixExecv(options.expand_arg0, argv_buf.ptr[0].?, argv_buf.ptr, envp, PATH);
12989 forkBail(ep1, err);
12990 }
12991
12992 const pid: posix.pid_t = @intCast(pid_result); // We are the parent.
12993 errdefer comptime unreachable; // The child is forked; we must not error from now on
12994
12995 posix.close(err_pipe[1]); // make sure only the child holds the write end open
12996
12997 if (options.stdin == .pipe) posix.close(stdin_pipe[0]);
12998 if (options.stdout == .pipe) posix.close(stdout_pipe[1]);
12999 if (options.stderr == .pipe) posix.close(stderr_pipe[1]);
13000
13001 if (prog_pipe[1] != -1) posix.close(prog_pipe[1]);
13002
13003 options.progress_node.setIpcFd(prog_pipe[0]);
13004
13005 return .{
13006 .pid = pid,
13007 .err_fd = err_pipe[0],
13008 .stdin = switch (options.stdin) {
13009 .pipe => .{ .handle = stdin_pipe[1] },
13010 else => null,
13011 },
13012 .stdout = switch (options.stdout) {
13013 .pipe => .{ .handle = stdout_pipe[0] },
13014 else => null,
13015 },
13016 .stderr = switch (options.stderr) {
13017 .pipe => .{ .handle = stderr_pipe[0] },
13018 else => null,
13019 },
13020 };
13021}
13022
13023fn getDevNullFd(t: *Threaded) !posix.fd_t {
13024 {
13025 t.mutex.lock();
13026 defer t.mutex.unlock();
13027 if (t.null_file.fd != -1) return t.null_file.fd;
13028 }
13029 const mode: u32 = 0;
13030 const syscall: Syscall = try .start();
13031 while (true) {
13032 const rc = open_sym("/dev/null", .{ .ACCMODE = .RDWR }, mode);
13033 switch (posix.errno(rc)) {
13034 .SUCCESS => {
13035 syscall.finish();
13036 const fresh_fd: posix.fd_t = @intCast(rc);
13037 t.mutex.lock(); // Another thread might have won the race.
13038 defer t.mutex.unlock();
13039 if (t.null_file.fd != -1) {
13040 posix.close(fresh_fd);
13041 return t.null_file.fd;
13042 } else {
13043 t.null_file.fd = fresh_fd;
13044 return fresh_fd;
13045 }
13046 },
13047 .INTR => {
13048 try syscall.checkCancel();
13049 continue;
13050 },
13051 .ACCES => return syscall.fail(error.AccessDenied),
13052 .MFILE => return syscall.fail(error.ProcessFdQuotaExceeded),
13053 .NFILE => return syscall.fail(error.SystemFdQuotaExceeded),
13054 .NODEV => return syscall.fail(error.NoDevice),
13055 .NOENT => return syscall.fail(error.FileNotFound),
13056 .NOMEM => return syscall.fail(error.SystemResources),
13057 .PERM => return syscall.fail(error.PermissionDenied),
13058 else => |err| return syscall.unexpectedErrno(err),
13059 }
13060 }
13061}
13062
13063fn processSpawnPosix(userdata: ?*anyopaque, options: process.SpawnOptions) process.SpawnError!process.Child {
13064 const t: *Threaded = @ptrCast(@alignCast(userdata));
13065 const spawned = try spawnPosix(t, options);
13066 defer posix.close(spawned.err_fd);
13067
13068 // Wait for the child to report any errors in or before `execvpe`.
13069 if (readIntFd(spawned.err_fd)) |child_err_int| {
13070 const child_err: process.SpawnError = @errorCast(@errorFromInt(child_err_int));
13071 return child_err;
13072 } else |read_err| switch (read_err) {
13073 error.EndOfStream => {
13074 // Write end closed by CLOEXEC at the time of the `execvpe` call,
13075 // indicating success.
13076 },
13077 else => {
13078 // Problem reading the error from the error reporting pipe. We
13079 // don't know if the child is alive or dead. Better to assume it is
13080 // alive so the resource does not risk being leaked.
13081 },
13082 }
13083
13084 return .{
13085 .id = spawned.pid,
13086 .thread_handle = {},
13087 .stdin = spawned.stdin,
13088 .stdout = spawned.stdout,
13089 .stderr = spawned.stderr,
13090 .request_resource_usage_statistics = options.request_resource_usage_statistics,
13091 };
13092}
13093
13094fn childWait(userdata: ?*anyopaque, child: *process.Child) process.Child.WaitError!process.Child.Term {
13095 if (native_os == .wasi) unreachable;
13096 const t: *Threaded = @ptrCast(@alignCast(userdata));
13097 _ = t;
13098 switch (native_os) {
13099 .windows => return childWaitWindows(child),
13100 else => return childWaitPosix(child),
13101 }
13102}
13103
13104fn childKill(userdata: ?*anyopaque, child: *process.Child) void {
13105 if (native_os == .wasi) unreachable;
13106 const t: *Threaded = @ptrCast(@alignCast(userdata));
13107 if (is_windows) {
13108 childKillWindows(t, child, 1) catch childCleanupWindows(child);
13109 } else {
13110 childKillPosix(child) catch {};
13111 childCleanupPosix(child);
13112 }
13113}
13114
13115fn childKillWindows(t: *Threaded, child: *process.Child, exit_code: windows.UINT) !void {
13116 _ = t; // TODO cancelation
13117 const handle = child.id.?;
13118 if (windows.kernel32.TerminateProcess(handle, exit_code) == 0) {
13119 switch (windows.GetLastError()) {
13120 .ACCESS_DENIED => {
13121 // Usually when TerminateProcess triggers a ACCESS_DENIED error, it
13122 // indicates that the process has already exited, but there may be
13123 // some rare edge cases where our process handle no longer has the
13124 // PROCESS_TERMINATE access right, so let's do another check to make
13125 // sure the process is really no longer running:
13126 windows.WaitForSingleObjectEx(handle, 0, false) catch return error.AccessDenied;
13127 return error.AlreadyTerminated;
13128 },
13129 else => |err| return windows.unexpectedError(err),
13130 }
13131 }
13132 _ = windows.kernel32.WaitForSingleObjectEx(handle, windows.INFINITE, windows.FALSE);
13133 childCleanupWindows(child);
13134}
13135
13136fn childWaitWindows(child: *process.Child) process.Child.WaitError!process.Child.Term {
13137 const handle = child.id.?;
13138
13139 const syscall: Syscall = try .start();
13140 while (true) switch (windows.kernel32.WaitForSingleObjectEx(handle, windows.INFINITE, windows.FALSE)) {
13141 windows.WAIT_OBJECT_0 => break syscall.finish(),
13142 windows.WAIT_ABANDONED, windows.WAIT_TIMEOUT => {
13143 try syscall.checkCancel();
13144 continue;
13145 },
13146 windows.WAIT_FAILED => {
13147 syscall.finish();
13148 switch (windows.GetLastError()) {
13149 else => |err| return windows.unexpectedError(err),
13150 }
13151 },
13152 else => return syscall.fail(error.Unexpected),
13153 };
13154
13155 const term: process.Child.Term = x: {
13156 var exit_code: windows.DWORD = undefined;
13157 if (windows.kernel32.GetExitCodeProcess(handle, &exit_code) == 0) {
13158 break :x .{ .unknown = 0 };
13159 } else {
13160 break :x .{ .exited = @as(u8, @truncate(exit_code)) };
13161 }
13162 };
13163
13164 childCleanupWindows(child);
13165 return term;
13166}
13167
13168fn childCleanupWindows(child: *process.Child) void {
13169 const handle = child.id orelse return;
13170
13171 if (child.request_resource_usage_statistics)
13172 child.resource_usage_statistics.rusage = windows.GetProcessMemoryInfo(handle) catch null;
13173
13174 windows.CloseHandle(handle);
13175 child.id = null;
13176
13177 windows.CloseHandle(child.thread_handle);
13178 child.thread_handle = undefined;
13179
13180 if (child.stdin) |*stdin| {
13181 windows.CloseHandle(stdin.handle);
13182 child.stdin = null;
13183 }
13184 if (child.stdout) |*stdout| {
13185 windows.CloseHandle(stdout.handle);
13186 child.stdout = null;
13187 }
13188 if (child.stderr) |*stderr| {
13189 windows.CloseHandle(stderr.handle);
13190 child.stderr = null;
13191 }
13192}
13193
13194fn childWaitPosix(child: *process.Child) process.Child.WaitError!process.Child.Term {
13195 defer childCleanupPosix(child);
13196
13197 const pid = child.id.?;
13198
13199 var ru: posix.rusage = undefined;
13200 const ru_ptr = if (child.request_resource_usage_statistics) &ru else null;
13201
13202 if (have_wait4) {
13203 var status: if (builtin.link_libc) c_int else u32 = undefined;
13204 const syscall: Syscall = try .start();
13205 while (true) switch (posix.errno(posix.system.wait4(pid, &status, 0, ru_ptr))) {
13206 .SUCCESS => {
13207 syscall.finish();
13208 if (ru_ptr) |p| child.resource_usage_statistics.rusage = p.*;
13209 return statusToTerm(@bitCast(status));
13210 },
13211 .INTR => {
13212 try syscall.checkCancel();
13213 continue;
13214 },
13215 .CHILD => |err| return syscall.errnoBug(err), // Double-free.
13216 else => |err| return syscall.unexpectedErrno(err),
13217 };
13218 }
13219
13220 if (have_waitid) {
13221 const linux = std.os.linux; // Bypass libc which has the wrong signature.
13222 var info: linux.siginfo_t = undefined;
13223 const syscall: Syscall = try .start();
13224 while (true) switch (linux.errno(linux.waitid(.PID, pid, &info, linux.W.EXITED, ru_ptr))) {
13225 .SUCCESS => {
13226 syscall.finish();
13227 if (ru_ptr) |p| child.resource_usage_statistics.rusage = p.*;
13228 const status: u32 = @bitCast(info.fields.common.second.sigchld.status);
13229 const code: linux.CLD = @enumFromInt(info.code);
13230 return switch (code) {
13231 .EXITED => .{ .exited = @truncate(status) },
13232 .KILLED, .DUMPED => .{ .signal = @enumFromInt(status) },
13233 .TRAPPED, .STOPPED => .{ .stopped = status },
13234 _, .CONTINUED => .{ .unknown = status },
13235 };
13236 },
13237 .INTR => {
13238 try syscall.checkCancel();
13239 continue;
13240 },
13241 .CHILD => |err| return syscall.errnoBug(err), // Double-free.
13242 else => |err| return syscall.unexpectedErrno(err),
13243 };
13244 }
13245
13246 var status: if (builtin.link_libc) c_int else u32 = undefined;
13247 const syscall: Syscall = try .start();
13248 while (true) switch (posix.errno(posix.system.waitpid(pid, &status, 0))) {
13249 .SUCCESS => {
13250 syscall.finish();
13251 return statusToTerm(@bitCast(status));
13252 },
13253 .INTR => {
13254 try syscall.checkCancel();
13255 continue;
13256 },
13257 .CHILD => |err| return syscall.errnoBug(err), // Double-free.
13258 else => |err| return syscall.unexpectedErrno(err),
13259 };
13260}
13261
13262fn statusToTerm(status: u32) process.Child.Term {
13263 return if (posix.W.IFEXITED(status))
13264 .{ .exited = posix.W.EXITSTATUS(status) }
13265 else if (posix.W.IFSIGNALED(status))
13266 .{ .signal = posix.W.TERMSIG(status) }
13267 else if (posix.W.IFSTOPPED(status))
13268 .{ .stopped = posix.W.STOPSIG(status) }
13269 else
13270 .{ .unknown = status };
13271}
13272
13273fn childKillPosix(child: *process.Child) !void {
13274 // Entire function body is intentionally uncancelable.
13275
13276 const pid = child.id.?;
13277
13278 while (true) switch (posix.errno(posix.system.kill(pid, .TERM))) {
13279 .SUCCESS => break,
13280 .INTR => continue,
13281 .PERM => return error.PermissionDenied,
13282 .INVAL => |err| return errnoBug(err),
13283 .SRCH => |err| return errnoBug(err),
13284 else => |err| return posix.unexpectedErrno(err),
13285 };
13286
13287 if (have_wait4) {
13288 var status: if (builtin.link_libc) c_int else u32 = undefined;
13289 while (true) switch (posix.errno(posix.system.wait4(pid, &status, 0, null))) {
13290 .SUCCESS => return,
13291 .INTR => continue,
13292 .CHILD => |err| return errnoBug(err), // Double-free.
13293 else => |err| return posix.unexpectedErrno(err),
13294 };
13295 }
13296
13297 if (have_waitid) {
13298 const linux = std.os.linux; // Bypass libc which has the wrong signature.
13299 var info: linux.siginfo_t = undefined;
13300 while (true) switch (linux.errno(linux.waitid(.PID, pid, &info, linux.W.EXITED, null))) {
13301 .SUCCESS => return,
13302 .INTR => continue,
13303 .CHILD => |err| return errnoBug(err), // Double-free.
13304 else => |err| return posix.unexpectedErrno(err),
13305 };
13306 }
13307
13308 var status: if (builtin.link_libc) c_int else u32 = undefined;
13309 while (true) switch (posix.errno(posix.system.waitpid(pid, &status, 0))) {
13310 .SUCCESS => return,
13311 .INTR => continue,
13312 .CHILD => |err| return errnoBug(err), // Double-free.
13313 else => |err| return posix.unexpectedErrno(err),
13314 };
13315}
13316
13317fn childCleanupPosix(child: *process.Child) void {
13318 if (child.stdin) |*stdin| {
13319 posix.close(stdin.handle);
13320 child.stdin = null;
13321 }
13322 if (child.stdout) |*stdout| {
13323 posix.close(stdout.handle);
13324 child.stdout = null;
13325 }
13326 if (child.stderr) |*stderr| {
13327 posix.close(stderr.handle);
13328 child.stderr = null;
13329 }
13330 child.id = null;
13331}
13332
13333/// Errors that can occur between fork() and execv()
13334const ForkBailError = process.SpawnError || process.ReplaceError;
13335
13336/// Child of fork calls this to report an error to the fork parent. Then the
13337/// child exits.
13338fn forkBail(fd: posix.fd_t, err: ForkBailError) noreturn {
13339 writeIntFd(fd, @as(ErrInt, @intFromError(err))) catch {};
13340 // If we're linking libc, some naughty applications may have registered atexit handlers
13341 // which we really do not want to run in the fork child. I caught LLVM doing this and
13342 // it caused a deadlock instead of doing an exit syscall. In the words of Avril Lavigne,
13343 // "Why'd you have to go and make things so complicated?"
13344 if (builtin.link_libc) {
13345 // The `_exit` function does nothing but make the exit syscall, unlike `exit`.
13346 std.c._exit(1);
13347 } else if (native_os == .linux and !builtin.single_threaded) {
13348 std.os.linux.exit_group(1);
13349 } else {
13350 posix.system.exit(1);
13351 }
13352}
13353
13354fn writeIntFd(fd: posix.fd_t, value: ErrInt) !void {
13355 var buffer: [8]u8 = undefined;
13356 std.mem.writeInt(u64, &buffer, value, .little);
13357 // Skip the cancel mechanism.
13358 var i: usize = 0;
13359 while (true) {
13360 const rc = posix.system.write(fd, buffer[i..].ptr, buffer.len - i);
13361 switch (posix.errno(rc)) {
13362 .SUCCESS => {
13363 const n: usize = @intCast(rc);
13364 i += n;
13365 if (buffer.len - i == 0) return;
13366 },
13367 .INTR => continue,
13368 else => return error.SystemResources,
13369 }
13370 }
13371}
13372
13373fn readIntFd(fd: posix.fd_t) !ErrInt {
13374 var buffer: [8]u8 = undefined;
13375 var i: usize = 0;
13376 while (true) {
13377 const rc = posix.system.read(fd, buffer[i..].ptr, buffer.len - i);
13378 switch (posix.errno(rc)) {
13379 .SUCCESS => {
13380 const n: usize = @intCast(rc);
13381 if (n == 0) break;
13382 i += n;
13383 continue;
13384 },
13385 .INTR => continue,
13386 else => |err| return posix.unexpectedErrno(err),
13387 }
13388 }
13389 if (buffer.len - i != 0) return error.EndOfStream;
13390 return @intCast(std.mem.readInt(u64, &buffer, .little));
13391}
13392
13393const ErrInt = std.meta.Int(.unsigned, @sizeOf(anyerror) * 8);
13394
13395fn destroyPipe(pipe: [2]posix.fd_t) void {
13396 if (pipe[0] != -1) posix.close(pipe[0]);
13397 if (pipe[0] != pipe[1]) posix.close(pipe[1]);
13398}
13399
13400fn setUpChildIo(stdio: process.SpawnOptions.StdIo, pipe_fd: i32, std_fileno: i32, dev_null_fd: i32) !void {
13401 switch (stdio) {
13402 .pipe => try dup2(pipe_fd, std_fileno),
13403 .close => posix.close(std_fileno),
13404 .inherit => {},
13405 .ignore => try dup2(dev_null_fd, std_fileno),
13406 .file => @panic("TODO implement setUpChildIo when file is used"),
13407 }
13408}
13409
13410fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) process.SpawnError!process.Child {
13411 const t: *Threaded = @ptrCast(@alignCast(userdata));
13412
13413 var saAttr: windows.SECURITY_ATTRIBUTES = .{
13414 .nLength = @sizeOf(windows.SECURITY_ATTRIBUTES),
13415 .bInheritHandle = windows.TRUE,
13416 .lpSecurityDescriptor = null,
13417 };
13418
13419 const any_ignore =
13420 options.stdin == .ignore or
13421 options.stdout == .ignore or
13422 options.stderr == .ignore;
13423
13424 const nul_handle = if (any_ignore) try getNulHandle(t) else undefined;
13425
13426 var g_hChildStd_IN_Rd: ?windows.HANDLE = null;
13427 var g_hChildStd_IN_Wr: ?windows.HANDLE = null;
13428 switch (options.stdin) {
13429 .pipe => {
13430 try windowsMakePipeIn(&g_hChildStd_IN_Rd, &g_hChildStd_IN_Wr, &saAttr);
13431 },
13432 .ignore => {
13433 g_hChildStd_IN_Rd = nul_handle;
13434 },
13435 .inherit => {
13436 g_hChildStd_IN_Rd = windows.GetStdHandle(windows.STD_INPUT_HANDLE) catch null;
13437 },
13438 .close => {
13439 g_hChildStd_IN_Rd = null;
13440 },
13441 .file => @panic("TODO implement passing file stdio in processSpawnWindows"),
13442 }
13443 errdefer if (options.stdin == .pipe) {
13444 windowsDestroyPipe(g_hChildStd_IN_Rd, g_hChildStd_IN_Wr);
13445 };
13446
13447 var g_hChildStd_OUT_Rd: ?windows.HANDLE = null;
13448 var g_hChildStd_OUT_Wr: ?windows.HANDLE = null;
13449 switch (options.stdout) {
13450 .pipe => {
13451 try windowsMakeAsyncPipe(&g_hChildStd_OUT_Rd, &g_hChildStd_OUT_Wr, &saAttr);
13452 },
13453 .ignore => {
13454 g_hChildStd_OUT_Wr = nul_handle;
13455 },
13456 .inherit => {
13457 g_hChildStd_OUT_Wr = windows.GetStdHandle(windows.STD_OUTPUT_HANDLE) catch null;
13458 },
13459 .close => {
13460 g_hChildStd_OUT_Wr = null;
13461 },
13462 .file => @panic("TODO implement passing file stdio in processSpawnWindows"),
13463 }
13464 errdefer if (options.stdout == .pipe) {
13465 windowsDestroyPipe(g_hChildStd_OUT_Rd, g_hChildStd_OUT_Wr);
13466 };
13467
13468 var g_hChildStd_ERR_Rd: ?windows.HANDLE = null;
13469 var g_hChildStd_ERR_Wr: ?windows.HANDLE = null;
13470 switch (options.stderr) {
13471 .pipe => {
13472 try windowsMakeAsyncPipe(&g_hChildStd_ERR_Rd, &g_hChildStd_ERR_Wr, &saAttr);
13473 },
13474 .ignore => {
13475 g_hChildStd_ERR_Wr = nul_handle;
13476 },
13477 .inherit => {
13478 g_hChildStd_ERR_Wr = windows.GetStdHandle(windows.STD_ERROR_HANDLE) catch null;
13479 },
13480 .close => {
13481 g_hChildStd_ERR_Wr = null;
13482 },
13483 .file => @panic("TODO implement passing file stdio in processSpawnWindows"),
13484 }
13485 errdefer if (options.stderr == .pipe) {
13486 windowsDestroyPipe(g_hChildStd_ERR_Rd, g_hChildStd_ERR_Wr);
13487 };
13488
13489 var siStartInfo: windows.STARTUPINFOW = .{
13490 .cb = @sizeOf(windows.STARTUPINFOW),
13491 .hStdError = g_hChildStd_ERR_Wr,
13492 .hStdOutput = g_hChildStd_OUT_Wr,
13493 .hStdInput = g_hChildStd_IN_Rd,
13494 .dwFlags = windows.STARTF_USESTDHANDLES,
13495
13496 .lpReserved = null,
13497 .lpDesktop = null,
13498 .lpTitle = null,
13499 .dwX = 0,
13500 .dwY = 0,
13501 .dwXSize = 0,
13502 .dwYSize = 0,
13503 .dwXCountChars = 0,
13504 .dwYCountChars = 0,
13505 .dwFillAttribute = 0,
13506 .wShowWindow = 0,
13507 .cbReserved2 = 0,
13508 .lpReserved2 = null,
13509 };
13510 var piProcInfo: windows.PROCESS_INFORMATION = undefined;
13511
13512 var arena_allocator = std.heap.ArenaAllocator.init(t.allocator);
13513 defer arena_allocator.deinit();
13514 const arena = arena_allocator.allocator();
13515
13516 const cwd_w = if (options.cwd) |cwd| try std.unicode.wtf8ToWtf16LeAllocZ(arena, cwd) else null;
13517 const cwd_w_ptr = if (cwd_w) |cwd| cwd.ptr else null;
13518
13519 const maybe_envp_buf = if (options.environ_map) |environ_map| try environ_map.createBlockWindows(arena) else null;
13520 const envp_ptr = if (maybe_envp_buf) |envp_buf| envp_buf.ptr else null;
13521
13522 const app_name_wtf8 = options.argv[0];
13523 const app_name_is_absolute = Dir.path.isAbsolute(app_name_wtf8);
13524
13525 // The cwd provided by options is in effect when choosing the executable
13526 // path to match POSIX semantics.
13527 var cwd_path_w_needs_free = false;
13528 const cwd_path_w = x: {
13529 // If the app name is absolute, then we need to use its dirname as the cwd
13530 if (app_name_is_absolute) {
13531 cwd_path_w_needs_free = true;
13532 const dir = Dir.path.dirname(app_name_wtf8).?;
13533 break :x try std.unicode.wtf8ToWtf16LeAllocZ(arena, dir);
13534 } else if (options.cwd) |cwd| {
13535 cwd_path_w_needs_free = true;
13536 break :x try std.unicode.wtf8ToWtf16LeAllocZ(arena, cwd);
13537 } else {
13538 break :x &[_:0]u16{}; // empty for cwd
13539 }
13540 };
13541
13542 // If the app name has more than just a filename, then we need to separate
13543 // that into the basename and dirname and use the dirname as an addition to
13544 // the cwd path. This is because NtQueryDirectoryFile cannot accept
13545 // FileName params with path separators.
13546 const app_basename_wtf8 = Dir.path.basename(app_name_wtf8);
13547 // If the app name is absolute, then the cwd will already have the app's dirname in it,
13548 // so only populate app_dirname if app name is a relative path with > 0 path separators.
13549 const maybe_app_dirname_wtf8 = if (!app_name_is_absolute) Dir.path.dirname(app_name_wtf8) else null;
13550 const app_dirname_w: ?[:0]u16 = x: {
13551 if (maybe_app_dirname_wtf8) |app_dirname_wtf8| {
13552 break :x try std.unicode.wtf8ToWtf16LeAllocZ(arena, app_dirname_wtf8);
13553 }
13554 break :x null;
13555 };
13556 const app_name_w = try std.unicode.wtf8ToWtf16LeAllocZ(arena, app_basename_wtf8);
13557
13558 const flags: windows.CreateProcessFlags = .{
13559 .create_suspended = options.start_suspended,
13560 .create_unicode_environment = true,
13561 .create_no_window = options.create_no_window,
13562 };
13563
13564 run: {
13565 // We have to scan each time because the PEB environment pointer is not stable.
13566 const env_strings: WindowsEnvironStrings = .scan();
13567 const PATH = env_strings.PATH orelse &[_:0]u16{};
13568 const PATHEXT = env_strings.PATHEXT orelse &[_:0]u16{};
13569
13570 // In case the command ends up being a .bat/.cmd script, we need to escape things using the cmd.exe rules
13571 // and invoke cmd.exe ourselves in order to mitigate arbitrary command execution from maliciously
13572 // constructed arguments.
13573 //
13574 // We'll need to wait until we're actually trying to run the command to know for sure
13575 // if the resolved command has the `.bat` or `.cmd` extension, so we defer actually
13576 // serializing the command line until we determine how it should be serialized.
13577 var cmd_line_cache = WindowsCommandLineCache.init(arena, options.argv);
13578
13579 var app_buf: std.ArrayList(u16) = .empty;
13580 try app_buf.appendSlice(arena, app_name_w);
13581
13582 var dir_buf: std.ArrayList(u16) = .empty;
13583
13584 if (cwd_path_w.len > 0) {
13585 try dir_buf.appendSlice(arena, cwd_path_w);
13586 }
13587 if (app_dirname_w) |app_dir| {
13588 if (dir_buf.items.len > 0) try dir_buf.append(arena, Dir.path.sep);
13589 try dir_buf.appendSlice(arena, app_dir);
13590 }
13591
13592 windowsCreateProcessPathExt(
13593 arena,
13594 &dir_buf,
13595 &app_buf,
13596 PATHEXT,
13597 &cmd_line_cache,
13598 envp_ptr,
13599 cwd_w_ptr,
13600 flags,
13601 &siStartInfo,
13602 &piProcInfo,
13603 ) catch |no_path_err| {
13604 const original_err = switch (no_path_err) {
13605 // argv[0] contains unsupported characters that will never resolve to a valid exe.
13606 error.InvalidArg0 => return error.FileNotFound,
13607 error.FileNotFound, error.InvalidExe, error.AccessDenied => |e| e,
13608 error.UnrecoverableInvalidExe => return error.InvalidExe,
13609 else => |e| return e,
13610 };
13611
13612 // If the app name had path separators, that disallows PATH searching,
13613 // and there's no need to search the PATH if the app name is absolute.
13614 // We still search the path if the cwd is absolute because of the
13615 // "cwd provided by options is in effect when choosing the executable path
13616 // to match posix semantics" behavior--we don't want to skip searching
13617 // the PATH just because we were trying to set the cwd of the child process.
13618 if (app_dirname_w != null or app_name_is_absolute) {
13619 return original_err;
13620 }
13621
13622 var it = std.mem.tokenizeScalar(u16, PATH, ';');
13623 while (it.next()) |search_path| {
13624 dir_buf.clearRetainingCapacity();
13625 try dir_buf.appendSlice(arena, search_path);
13626
13627 if (windowsCreateProcessPathExt(
13628 arena,
13629 &dir_buf,
13630 &app_buf,
13631 PATHEXT,
13632 &cmd_line_cache,
13633 envp_ptr,
13634 cwd_w_ptr,
13635 flags,
13636 &siStartInfo,
13637 &piProcInfo,
13638 )) {
13639 break :run;
13640 } else |err| switch (err) {
13641 // argv[0] contains unsupported characters that will never resolve to a valid exe.
13642 error.InvalidArg0 => return error.FileNotFound,
13643 error.FileNotFound, error.AccessDenied, error.InvalidExe => continue,
13644 error.UnrecoverableInvalidExe => return error.InvalidExe,
13645 else => |e| return e,
13646 }
13647 } else {
13648 return original_err;
13649 }
13650 };
13651 }
13652
13653 if (options.stdin == .pipe) windows.CloseHandle(g_hChildStd_IN_Rd.?);
13654 if (options.stderr == .pipe) windows.CloseHandle(g_hChildStd_ERR_Wr.?);
13655 if (options.stdout == .pipe) windows.CloseHandle(g_hChildStd_OUT_Wr.?);
13656
13657 return .{
13658 .id = piProcInfo.hProcess,
13659 .thread_handle = piProcInfo.hThread,
13660 .stdin = if (g_hChildStd_IN_Wr) |h| .{ .handle = h } else null,
13661 .stdout = if (g_hChildStd_OUT_Rd) |h| .{ .handle = h } else null,
13662 .stderr = if (g_hChildStd_ERR_Rd) |h| .{ .handle = h } else null,
13663 .request_resource_usage_statistics = options.request_resource_usage_statistics,
13664 };
13665}
13666
13667fn getNulHandle(t: *Threaded) !windows.HANDLE {
13668 {
13669 t.mutex.lock();
13670 defer t.mutex.unlock();
13671 if (t.null_file.handle) |handle| return handle;
13672 }
13673
13674 const device_path = [_]u16{ '\\', 'D', 'e', 'v', 'i', 'c', 'e', '\\', 'N', 'u', 'l', 'l' };
13675 var nt_name: windows.UNICODE_STRING = .{
13676 .Length = device_path.len * 2,
13677 .MaximumLength = device_path.len * 2,
13678 .Buffer = @constCast(&device_path),
13679 };
13680 const attr: windows.OBJECT_ATTRIBUTES = .{
13681 .Length = @sizeOf(windows.OBJECT_ATTRIBUTES),
13682 .RootDirectory = null,
13683 .Attributes = .{
13684 .INHERIT = true,
13685 },
13686 .ObjectName = &nt_name,
13687 .SecurityDescriptor = null,
13688 .SecurityQualityOfService = null,
13689 };
13690 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
13691 var fresh_handle: windows.HANDLE = undefined;
13692 var syscall: Syscall = try .start();
13693 while (true) switch (windows.ntdll.NtCreateFile(
13694 &fresh_handle,
13695 .{
13696 .STANDARD = .{ .SYNCHRONIZE = true },
13697 .GENERIC = .{ .WRITE = true, .READ = true },
13698 },
13699 &attr,
13700 &io_status_block,
13701 null,
13702 .{ .NORMAL = true },
13703 .VALID_FLAGS,
13704 .OPEN,
13705 .{
13706 .DIRECTORY_FILE = false,
13707 .NON_DIRECTORY_FILE = true,
13708 .IO = .SYNCHRONOUS_NONALERT,
13709 .OPEN_REPARSE_POINT = false,
13710 },
13711 null,
13712 0,
13713 )) {
13714 .SUCCESS => {
13715 syscall.finish();
13716 t.mutex.lock(); // Another thread might have won the race.
13717 defer t.mutex.unlock();
13718 if (t.null_file.handle) |prev_handle| {
13719 windows.CloseHandle(fresh_handle);
13720 return prev_handle;
13721 } else {
13722 t.null_file.handle = fresh_handle;
13723 return fresh_handle;
13724 }
13725 },
13726 .DELETE_PENDING => {
13727 // This error means that there *was* a file in this location on
13728 // the file system, but it was deleted. However, the OS is not
13729 // finished with the deletion operation, and so this CreateFile
13730 // call has failed. There is not really a sane way to handle
13731 // this other than retrying the creation after the OS finishes
13732 // the deletion.
13733 syscall.finish();
13734 try parking_sleep.sleep(.{ .duration = .{
13735 .raw = .fromMilliseconds(1),
13736 .clock = .awake,
13737 } });
13738 syscall = try .start();
13739 continue;
13740 },
13741 .CANCELLED => {
13742 try syscall.checkCancel();
13743 continue;
13744 },
13745 .INVALID_PARAMETER => |status| return syscall.ntstatusBug(status),
13746 .OBJECT_PATH_SYNTAX_BAD => |status| return syscall.ntstatusBug(status),
13747 .INVALID_HANDLE => |status| return syscall.ntstatusBug(status),
13748 .OBJECT_NAME_INVALID => return syscall.fail(error.BadPathName),
13749 .OBJECT_NAME_NOT_FOUND => return syscall.fail(error.FileNotFound),
13750 .OBJECT_PATH_NOT_FOUND => return syscall.fail(error.FileNotFound),
13751 .NO_MEDIA_IN_DEVICE => return syscall.fail(error.NoDevice),
13752 .SHARING_VIOLATION => return syscall.fail(error.AccessDenied),
13753 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
13754 .PIPE_NOT_AVAILABLE => return syscall.fail(error.NoDevice),
13755 .FILE_IS_A_DIRECTORY => return syscall.fail(error.IsDir),
13756 .NOT_A_DIRECTORY => return syscall.fail(error.NotDir),
13757 .USER_MAPPED_FILE => return syscall.fail(error.AccessDenied),
13758 else => |status| return syscall.unexpectedNtstatus(status),
13759 };
13760}
13761
13762/// Expects `app_buf` to contain exactly the app name, and `dir_buf` to contain exactly the dir path.
13763/// After return, `app_buf` will always contain exactly the app name and `dir_buf` will always contain exactly the dir path.
13764/// Note: `app_buf` should not contain any leading path separators.
13765/// Note: If the dir is the cwd, dir_buf should be empty (len = 0).
13766fn windowsCreateProcessPathExt(
13767 arena: Allocator,
13768 dir_buf: *std.ArrayList(u16),
13769 app_buf: *std.ArrayList(u16),
13770 pathext: [:0]const u16,
13771 cmd_line_cache: *WindowsCommandLineCache,
13772 envp_ptr: ?[*:0]const u16,
13773 cwd_ptr: ?[*:0]u16,
13774 flags: windows.CreateProcessFlags,
13775 lpStartupInfo: *windows.STARTUPINFOW,
13776 lpProcessInformation: *windows.PROCESS_INFORMATION,
13777) !void {
13778 const app_name_len = app_buf.items.len;
13779 const dir_path_len = dir_buf.items.len;
13780
13781 if (app_name_len == 0) return error.FileNotFound;
13782
13783 defer app_buf.shrinkRetainingCapacity(app_name_len);
13784 defer dir_buf.shrinkRetainingCapacity(dir_path_len);
13785
13786 // The name of the game here is to avoid CreateProcessW calls at all costs,
13787 // and only ever try calling it when we have a real candidate for execution.
13788 // Secondarily, we want to minimize the number of syscalls used when checking
13789 // for each PATHEXT-appended version of the app name.
13790 //
13791 // An overview of the technique used:
13792 // - Open the search directory for iteration (either cwd or a path from PATH)
13793 // - Use NtQueryDirectoryFile with a wildcard filename of `<app name>*` to
13794 // check if anything that could possibly match either the unappended version
13795 // of the app name or any of the versions with a PATHEXT value appended exists.
13796 // - If the wildcard NtQueryDirectoryFile call found nothing, we can exit early
13797 // without needing to use PATHEXT at all.
13798 //
13799 // This allows us to use a <open dir, NtQueryDirectoryFile, close dir> sequence
13800 // for any directory that doesn't contain any possible matches, instead of having
13801 // to use a separate look up for each individual filename combination (unappended +
13802 // each PATHEXT appended). For directories where the wildcard *does* match something,
13803 // we iterate the matches and take note of any that are either the unappended version,
13804 // or a version with a supported PATHEXT appended. We then try calling CreateProcessW
13805 // with the found versions in the appropriate order.
13806 var dir = dir: {
13807 // needs to be null-terminated
13808 try dir_buf.append(arena, 0);
13809 defer dir_buf.shrinkRetainingCapacity(dir_path_len);
13810 const dir_path_z = dir_buf.items[0 .. dir_buf.items.len - 1 :0];
13811 const prefixed_path = try windows.wToPrefixedFileW(null, dir_path_z);
13812 break :dir dirOpenDirWindows(.cwd(), prefixed_path.span(), .{
13813 .iterate = true,
13814 }) catch |err| switch (err) {
13815 // These errors must not be ignored because they should not be able
13816 // to affect which file is chosen to execute. Also `error.Canceled`
13817 // must never be swallowed.
13818 error.Canceled,
13819 error.SystemResources,
13820 error.Unexpected,
13821 error.ProcessFdQuotaExceeded,
13822 error.SystemFdQuotaExceeded,
13823 => |e| return e,
13824
13825 error.AccessDenied,
13826 error.PermissionDenied,
13827 error.SymLinkLoop,
13828 error.FileNotFound,
13829 error.NotDir,
13830 error.NoDevice,
13831 error.NetworkNotFound,
13832 error.NameTooLong,
13833 error.BadPathName,
13834 error.DeviceBusy,
13835 => return error.FileNotFound,
13836 };
13837 };
13838 defer windows.CloseHandle(dir.handle);
13839
13840 // Add wildcard and null-terminator
13841 try app_buf.append(arena, '*');
13842 try app_buf.append(arena, 0);
13843 const app_name_wildcard = app_buf.items[0 .. app_buf.items.len - 1 :0];
13844
13845 // This 2048 is arbitrary, we just want it to be large enough to get multiple FILE_DIRECTORY_INFORMATION entries
13846 // returned per NtQueryDirectoryFile call.
13847 var file_information_buf: [2048]u8 align(@alignOf(windows.FILE_DIRECTORY_INFORMATION)) = undefined;
13848 const file_info_maximum_single_entry_size = @sizeOf(windows.FILE_DIRECTORY_INFORMATION) + (windows.NAME_MAX * 2);
13849 if (file_information_buf.len < file_info_maximum_single_entry_size) {
13850 @compileError("file_information_buf must be large enough to contain at least one maximum size FILE_DIRECTORY_INFORMATION entry");
13851 }
13852 var io_status: windows.IO_STATUS_BLOCK = undefined;
13853
13854 const num_supported_pathext = @typeInfo(process.WindowsExtension).@"enum".fields.len;
13855 var pathext_seen = [_]bool{false} ** num_supported_pathext;
13856 var any_pathext_seen = false;
13857 var unappended_exists = false;
13858
13859 // Fully iterate the wildcard matches via NtQueryDirectoryFile and take note of all versions
13860 // of the app_name we should try to spawn.
13861 // Note: This is necessary because the order of the files returned is filesystem-dependent:
13862 // On NTFS, `blah.exe*` will always return `blah.exe` first if it exists.
13863 // On FAT32, it's possible for something like `blah.exe.obj` to be returned first.
13864 while (true) {
13865 const app_name_len_bytes = std.math.cast(u16, app_name_wildcard.len * 2) orelse return error.NameTooLong;
13866 var app_name_unicode_string = windows.UNICODE_STRING{
13867 .Length = app_name_len_bytes,
13868 .MaximumLength = app_name_len_bytes,
13869 .Buffer = @constCast(app_name_wildcard.ptr),
13870 };
13871 const rc = windows.ntdll.NtQueryDirectoryFile(
13872 dir.handle,
13873 null,
13874 null,
13875 null,
13876 &io_status,
13877 &file_information_buf,
13878 file_information_buf.len,
13879 .Directory,
13880 windows.FALSE, // single result
13881 &app_name_unicode_string,
13882 windows.FALSE, // restart iteration
13883 );
13884
13885 // If we get nothing with the wildcard, then we can just bail out
13886 // as we know appending PATHEXT will not yield anything.
13887 switch (rc) {
13888 .SUCCESS => {},
13889 .NO_SUCH_FILE => return error.FileNotFound,
13890 .NO_MORE_FILES => break,
13891 .ACCESS_DENIED => return error.AccessDenied,
13892 else => return windows.unexpectedStatus(rc),
13893 }
13894
13895 // According to the docs, this can only happen if there is not enough room in the
13896 // buffer to write at least one complete FILE_DIRECTORY_INFORMATION entry.
13897 // Therefore, this condition should not be possible to hit with the buffer size we use.
13898 std.debug.assert(io_status.Information != 0);
13899
13900 var it = windows.FileInformationIterator(windows.FILE_DIRECTORY_INFORMATION){ .buf = &file_information_buf };
13901 while (it.next()) |info| {
13902 // Skip directories
13903 if (info.FileAttributes.DIRECTORY) continue;
13904 const filename = @as([*]u16, @ptrCast(&info.FileName))[0 .. info.FileNameLength / 2];
13905 // Because all results start with the app_name since we're using the wildcard `app_name*`,
13906 // if the length is equal to app_name then this is an exact match
13907 if (filename.len == app_name_len) {
13908 // Note: We can't break early here because it's possible that the unappended version
13909 // fails to spawn, in which case we still want to try the PATHEXT appended versions.
13910 unappended_exists = true;
13911 } else if (windowsCreateProcessSupportsExtension(filename[app_name_len..])) |pathext_ext| {
13912 pathext_seen[@intFromEnum(pathext_ext)] = true;
13913 any_pathext_seen = true;
13914 }
13915 }
13916 }
13917
13918 const unappended_err = unappended: {
13919 if (unappended_exists) {
13920 if (dir_path_len != 0) switch (dir_buf.items[dir_buf.items.len - 1]) {
13921 '/', '\\' => {},
13922 else => try dir_buf.append(arena, Dir.path.sep),
13923 };
13924 try dir_buf.appendSlice(arena, app_buf.items[0..app_name_len]);
13925 try dir_buf.append(arena, 0);
13926 const full_app_name = dir_buf.items[0 .. dir_buf.items.len - 1 :0];
13927
13928 const is_bat_or_cmd = bat_or_cmd: {
13929 const app_name = app_buf.items[0..app_name_len];
13930 const ext_start = std.mem.lastIndexOfScalar(u16, app_name, '.') orelse break :bat_or_cmd false;
13931 const ext = app_name[ext_start..];
13932 const ext_enum = windowsCreateProcessSupportsExtension(ext) orelse break :bat_or_cmd false;
13933 switch (ext_enum) {
13934 .cmd, .bat => break :bat_or_cmd true,
13935 else => break :bat_or_cmd false,
13936 }
13937 };
13938 const cmd_line_w = if (is_bat_or_cmd)
13939 try cmd_line_cache.scriptCommandLine(full_app_name)
13940 else
13941 try cmd_line_cache.commandLine();
13942 const app_name_w = if (is_bat_or_cmd)
13943 try cmd_line_cache.cmdExePath()
13944 else
13945 full_app_name;
13946
13947 if (windowsCreateProcess(
13948 app_name_w.ptr,
13949 cmd_line_w.ptr,
13950 envp_ptr,
13951 cwd_ptr,
13952 flags,
13953 lpStartupInfo,
13954 lpProcessInformation,
13955 )) |_| {
13956 return;
13957 } else |err| switch (err) {
13958 error.FileNotFound,
13959 error.AccessDenied,
13960 => break :unappended err,
13961 error.InvalidExe => {
13962 // On InvalidExe, if the extension of the app name is .exe then
13963 // it's treated as an unrecoverable error. Otherwise, it'll be
13964 // skipped as normal.
13965 const app_name = app_buf.items[0..app_name_len];
13966 const ext_start = std.mem.lastIndexOfScalar(u16, app_name, '.') orelse break :unappended err;
13967 const ext = app_name[ext_start..];
13968 if (windows.eqlIgnoreCaseWtf16(ext, std.unicode.utf8ToUtf16LeStringLiteral(".EXE"))) {
13969 return error.UnrecoverableInvalidExe;
13970 }
13971 break :unappended err;
13972 },
13973 else => return err,
13974 }
13975 }
13976 break :unappended error.FileNotFound;
13977 };
13978
13979 if (!any_pathext_seen) return unappended_err;
13980
13981 // Now try any PATHEXT appended versions that we've seen
13982 var ext_it = std.mem.tokenizeScalar(u16, pathext, ';');
13983 while (ext_it.next()) |ext| {
13984 const ext_enum = windowsCreateProcessSupportsExtension(ext) orelse continue;
13985 if (!pathext_seen[@intFromEnum(ext_enum)]) continue;
13986
13987 dir_buf.shrinkRetainingCapacity(dir_path_len);
13988 if (dir_path_len != 0) switch (dir_buf.items[dir_buf.items.len - 1]) {
13989 '/', '\\' => {},
13990 else => try dir_buf.append(arena, Dir.path.sep),
13991 };
13992 try dir_buf.appendSlice(arena, app_buf.items[0..app_name_len]);
13993 try dir_buf.appendSlice(arena, ext);
13994 try dir_buf.append(arena, 0);
13995 const full_app_name = dir_buf.items[0 .. dir_buf.items.len - 1 :0];
13996
13997 const is_bat_or_cmd = switch (ext_enum) {
13998 .cmd, .bat => true,
13999 else => false,
14000 };
14001 const cmd_line_w = if (is_bat_or_cmd)
14002 try cmd_line_cache.scriptCommandLine(full_app_name)
14003 else
14004 try cmd_line_cache.commandLine();
14005 const app_name_w = if (is_bat_or_cmd)
14006 try cmd_line_cache.cmdExePath()
14007 else
14008 full_app_name;
14009
14010 if (windowsCreateProcess(app_name_w.ptr, cmd_line_w.ptr, envp_ptr, cwd_ptr, flags, lpStartupInfo, lpProcessInformation)) |_| {
14011 return;
14012 } else |err| switch (err) {
14013 error.FileNotFound => continue,
14014 error.AccessDenied => continue,
14015 error.InvalidExe => {
14016 // On InvalidExe, if the extension of the app name is .exe then
14017 // it's treated as an unrecoverable error. Otherwise, it'll be
14018 // skipped as normal.
14019 if (windows.eqlIgnoreCaseWtf16(ext, std.unicode.utf8ToUtf16LeStringLiteral(".EXE"))) {
14020 return error.UnrecoverableInvalidExe;
14021 }
14022 continue;
14023 },
14024 else => return err,
14025 }
14026 }
14027
14028 return unappended_err;
14029}
14030
14031fn windowsCreateProcess(
14032 app_name: [*:0]u16,
14033 cmd_line: [*:0]u16,
14034 env_ptr: ?[*:0]const u16,
14035 cwd_ptr: ?[*:0]u16,
14036 flags: windows.CreateProcessFlags,
14037 lpStartupInfo: *windows.STARTUPINFOW,
14038 lpProcessInformation: *windows.PROCESS_INFORMATION,
14039) !void {
14040 const syscall: Syscall = try .start();
14041 while (true) {
14042 if (windows.kernel32.CreateProcessW(
14043 app_name,
14044 cmd_line,
14045 null,
14046 null,
14047 windows.TRUE,
14048 flags,
14049 env_ptr,
14050 cwd_ptr,
14051 lpStartupInfo,
14052 lpProcessInformation,
14053 ) != 0) {
14054 return syscall.finish();
14055 } else switch (windows.GetLastError()) {
14056 .INVALID_PARAMETER => unreachable,
14057 .OPERATION_ABORTED => {
14058 try syscall.checkCancel();
14059 continue;
14060 },
14061 .FILE_NOT_FOUND => return syscall.fail(error.FileNotFound),
14062 .PATH_NOT_FOUND => return syscall.fail(error.FileNotFound),
14063 .DIRECTORY => return syscall.fail(error.FileNotFound),
14064 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
14065 .INVALID_NAME => return syscall.fail(error.InvalidName),
14066 .FILENAME_EXCED_RANGE => return syscall.fail(error.NameTooLong),
14067 .SHARING_VIOLATION => return syscall.fail(error.FileBusy),
14068 .COMMITMENT_LIMIT => return syscall.fail(error.SystemResources),
14069
14070 // These are all the system errors that are mapped to ENOEXEC by
14071 // the undocumented _dosmaperr (old CRT) or __acrt_errno_map_os_error
14072 // (newer CRT) functions. Their code can be found in crt/src/dosmap.c (old SDK)
14073 // or urt/misc/errno.cpp (newer SDK) in the Windows SDK.
14074 .BAD_FORMAT,
14075 .INVALID_STARTING_CODESEG, // MIN_EXEC_ERROR in errno.cpp
14076 .INVALID_STACKSEG,
14077 .INVALID_MODULETYPE,
14078 .INVALID_EXE_SIGNATURE,
14079 .EXE_MARKED_INVALID,
14080 .BAD_EXE_FORMAT,
14081 .ITERATED_DATA_EXCEEDS_64k,
14082 .INVALID_MINALLOCSIZE,
14083 .DYNLINK_FROM_INVALID_RING,
14084 .IOPL_NOT_ENABLED,
14085 .INVALID_SEGDPL,
14086 .AUTODATASEG_EXCEEDS_64k,
14087 .RING2SEG_MUST_BE_MOVABLE,
14088 .RELOC_CHAIN_XEEDS_SEGLIM,
14089 .INFLOOP_IN_RELOC_CHAIN, // MAX_EXEC_ERROR in errno.cpp
14090 // This one is not mapped to ENOEXEC but it is possible, for example
14091 // when calling CreateProcessW on a plain text file with a .exe extension
14092 .EXE_MACHINE_TYPE_MISMATCH,
14093 => return syscall.fail(error.InvalidExe),
14094
14095 else => |err| {
14096 syscall.finish();
14097 return windows.unexpectedError(err);
14098 },
14099 }
14100 }
14101}
14102
14103/// Case-insensitive WTF-16 lookup
14104fn windowsCreateProcessSupportsExtension(ext: []const u16) ?process.WindowsExtension {
14105 comptime {
14106 // Ensures keeping this function in sync with the enum.
14107 const fields = @typeInfo(process.WindowsExtension).@"enum".fields;
14108 assert(fields.len == 4);
14109 assert(@intFromEnum(process.WindowsExtension.bat) == 0);
14110 assert(@intFromEnum(process.WindowsExtension.cmd) == 1);
14111 assert(@intFromEnum(process.WindowsExtension.com) == 2);
14112 assert(@intFromEnum(process.WindowsExtension.exe) == 3);
14113 }
14114
14115 if (ext.len != 4) return null;
14116 const State = enum {
14117 start,
14118 dot,
14119 b,
14120 ba,
14121 c,
14122 cm,
14123 co,
14124 e,
14125 ex,
14126 };
14127 var state: State = .start;
14128 for (ext) |c| switch (state) {
14129 .start => switch (c) {
14130 '.' => state = .dot,
14131 else => return null,
14132 },
14133 .dot => switch (c) {
14134 'b', 'B' => state = .b,
14135 'c', 'C' => state = .c,
14136 'e', 'E' => state = .e,
14137 else => return null,
14138 },
14139 .b => switch (c) {
14140 'a', 'A' => state = .ba,
14141 else => return null,
14142 },
14143 .c => switch (c) {
14144 'm', 'M' => state = .cm,
14145 'o', 'O' => state = .co,
14146 else => return null,
14147 },
14148 .e => switch (c) {
14149 'x', 'X' => state = .ex,
14150 else => return null,
14151 },
14152 .ba => switch (c) {
14153 't', 'T' => return .bat,
14154 else => return null,
14155 },
14156 .cm => switch (c) {
14157 'd', 'D' => return .cmd,
14158 else => return null,
14159 },
14160 .co => switch (c) {
14161 'm', 'M' => return .com,
14162 else => return null,
14163 },
14164 .ex => switch (c) {
14165 'e', 'E' => return .exe,
14166 else => return null,
14167 },
14168 };
14169 return null;
14170}
14171
14172test windowsCreateProcessSupportsExtension {
14173 try std.testing.expectEqual(process.WindowsExtension.exe, windowsCreateProcessSupportsExtension(&[_]u16{ '.', 'e', 'X', 'e' }).?);
14174 try std.testing.expect(windowsCreateProcessSupportsExtension(&[_]u16{ '.', 'e', 'X', 'e', 'c' }) == null);
14175}
14176
14177/// Serializes argv into a WTF-16 encoded command-line string for use with CreateProcessW.
14178///
14179/// Serialization is done on-demand and the result is cached in order to allow for:
14180/// - Only serializing the particular type of command line needed (`.bat`/`.cmd`
14181/// command line serialization is different from `.exe`/etc)
14182/// - Reusing the serialized command lines if necessary (i.e. if the execution
14183/// of a command fails and the PATH is going to be continued to be searched
14184/// for more candidates)
14185const WindowsCommandLineCache = struct {
14186 cmd_line: ?[:0]u16 = null,
14187 script_cmd_line: ?[:0]u16 = null,
14188 cmd_exe_path: ?[:0]u16 = null,
14189 argv: []const []const u8,
14190 allocator: Allocator,
14191
14192 fn init(allocator: Allocator, argv: []const []const u8) WindowsCommandLineCache {
14193 return .{
14194 .allocator = allocator,
14195 .argv = argv,
14196 };
14197 }
14198
14199 fn deinit(self: *WindowsCommandLineCache) void {
14200 if (self.cmd_line) |cmd_line| self.allocator.free(cmd_line);
14201 if (self.script_cmd_line) |script_cmd_line| self.allocator.free(script_cmd_line);
14202 if (self.cmd_exe_path) |cmd_exe_path| self.allocator.free(cmd_exe_path);
14203 }
14204
14205 fn commandLine(self: *WindowsCommandLineCache) ![:0]u16 {
14206 if (self.cmd_line == null) {
14207 self.cmd_line = try argvToCommandLineWindows(self.allocator, self.argv);
14208 }
14209 return self.cmd_line.?;
14210 }
14211
14212 /// Not cached, since the path to the batch script will change during PATH searching.
14213 /// `script_path` should be as qualified as possible, e.g. if the PATH is being searched,
14214 /// then script_path should include both the search path and the script filename
14215 /// (this allows avoiding cmd.exe having to search the PATH again).
14216 fn scriptCommandLine(self: *WindowsCommandLineCache, script_path: []const u16) ![:0]u16 {
14217 if (self.script_cmd_line) |v| self.allocator.free(v);
14218 self.script_cmd_line = try argvToScriptCommandLineWindows(
14219 self.allocator,
14220 script_path,
14221 self.argv[1..],
14222 );
14223 return self.script_cmd_line.?;
14224 }
14225
14226 fn cmdExePath(self: *WindowsCommandLineCache) ![:0]u16 {
14227 if (self.cmd_exe_path == null) {
14228 self.cmd_exe_path = try windowsCmdExePath(self.allocator);
14229 }
14230 return self.cmd_exe_path.?;
14231 }
14232};
14233
14234/// Returns the absolute path of `cmd.exe` within the Windows system directory.
14235/// The caller owns the returned slice.
14236fn windowsCmdExePath(allocator: Allocator) error{ OutOfMemory, Unexpected }![:0]u16 {
14237 var buf = try std.ArrayList(u16).initCapacity(allocator, 128);
14238 errdefer buf.deinit(allocator);
14239 while (true) {
14240 const unused_slice = buf.unusedCapacitySlice();
14241 // TODO: Get the system directory from PEB.ReadOnlyStaticServerData
14242 const len = windows.kernel32.GetSystemDirectoryW(@ptrCast(unused_slice), @intCast(unused_slice.len));
14243 if (len == 0) {
14244 switch (windows.GetLastError()) {
14245 else => |err| return windows.unexpectedError(err),
14246 }
14247 }
14248 if (len > unused_slice.len) {
14249 try buf.ensureUnusedCapacity(allocator, len);
14250 } else {
14251 buf.items.len = len;
14252 break;
14253 }
14254 }
14255 switch (buf.items[buf.items.len - 1]) {
14256 '/', '\\' => {},
14257 else => try buf.append(allocator, Dir.path.sep),
14258 }
14259 try buf.appendSlice(allocator, std.unicode.utf8ToUtf16LeStringLiteral("cmd.exe"));
14260 return try buf.toOwnedSliceSentinel(allocator, 0);
14261}
14262
14263const ArgvToScriptCommandLineError = error{
14264 OutOfMemory,
14265 InvalidWtf8,
14266 /// NUL (U+0000), LF (U+000A), CR (U+000D) are not allowed
14267 /// within arguments when executing a `.bat`/`.cmd` script.
14268 /// - NUL/LF signifiies end of arguments, so anything afterwards
14269 /// would be lost after execution.
14270 /// - CR is stripped by `cmd.exe`, so any CR codepoints
14271 /// would be lost after execution.
14272 InvalidBatchScriptArg,
14273};
14274
14275/// Serializes `argv` to a Windows command-line string that uses `cmd.exe /c` and `cmd.exe`-specific
14276/// escaping rules. The caller owns the returned slice.
14277///
14278/// Escapes `argv` using the suggested mitigation against arbitrary command execution from:
14279/// https://flatt.tech/research/posts/batbadbut-you-cant-securely-execute-commands-on-windows/
14280///
14281/// The return of this function will look like
14282/// `cmd.exe /d /e:ON /v:OFF /c "<escaped command line>"`
14283/// and should be used as the `lpCommandLine` of `CreateProcessW`, while the
14284/// return of `windowsCmdExePath` should be used as `lpApplicationName`.
14285///
14286/// Should only be used when spawning `.bat`/`.cmd` scripts, see `argvToCommandLineWindows` otherwise.
14287/// The `.bat`/`.cmd` file must be known to both have the `.bat`/`.cmd` extension and exist on the filesystem.
14288fn argvToScriptCommandLineWindows(
14289 allocator: Allocator,
14290 /// Path to the `.bat`/`.cmd` script. If this path is relative, it is assumed to be relative to the CWD.
14291 /// The script must have been verified to exist at this path before calling this function.
14292 script_path: []const u16,
14293 /// Arguments, not including the script name itself. Expected to be encoded as WTF-8.
14294 script_args: []const []const u8,
14295) ArgvToScriptCommandLineError![:0]u16 {
14296 var buf = try std.array_list.Managed(u8).initCapacity(allocator, 64);
14297 defer buf.deinit();
14298
14299 // `/d` disables execution of AutoRun commands.
14300 // `/e:ON` and `/v:OFF` are needed for BatBadBut mitigation:
14301 // > If delayed expansion is enabled via the registry value DelayedExpansion,
14302 // > it must be disabled by explicitly calling cmd.exe with the /V:OFF option.
14303 // > Escaping for % requires the command extension to be enabled.
14304 // > If it’s disabled via the registry value EnableExtensions, it must be enabled with the /E:ON option.
14305 // https://flatt.tech/research/posts/batbadbut-you-cant-securely-execute-commands-on-windows/
14306 buf.appendSliceAssumeCapacity("cmd.exe /d /e:ON /v:OFF /c \"");
14307
14308 // Always quote the path to the script arg
14309 buf.appendAssumeCapacity('"');
14310 // We always want the path to the batch script to include a path separator in order to
14311 // avoid cmd.exe searching the PATH for the script. This is not part of the arbitrary
14312 // command execution mitigation, we just know exactly what script we want to execute
14313 // at this point, and potentially making cmd.exe re-find it is unnecessary.
14314 //
14315 // If the script path does not have a path separator, then we know its relative to CWD and
14316 // we can just put `.\` in the front.
14317 if (std.mem.findAny(u16, script_path, &[_]u16{
14318 std.mem.nativeToLittle(u16, '\\'), std.mem.nativeToLittle(u16, '/'),
14319 }) == null) {
14320 try buf.appendSlice(".\\");
14321 }
14322 // Note that we don't do any escaping/mitigations for this argument, since the relevant
14323 // characters (", %, etc) are illegal in file paths and this function should only be called
14324 // with script paths that have been verified to exist.
14325 try std.unicode.wtf16LeToWtf8ArrayList(&buf, script_path);
14326 buf.appendAssumeCapacity('"');
14327
14328 for (script_args) |arg| {
14329 // Literal carriage returns get stripped when run through cmd.exe
14330 // and NUL/newlines act as 'end of command.' Because of this, it's basically
14331 // always a mistake to include these characters in argv, so it's
14332 // an error condition in order to ensure that the return of this
14333 // function can always roundtrip through cmd.exe.
14334 if (std.mem.findAny(u8, arg, "\x00\r\n") != null) {
14335 return error.InvalidBatchScriptArg;
14336 }
14337
14338 // Separate args with a space.
14339 try buf.append(' ');
14340
14341 // Need to quote if the argument is empty (otherwise the arg would just be lost)
14342 // or if the last character is a `\`, since then something like "%~2" in a .bat
14343 // script would cause the closing " to be escaped which we don't want.
14344 var needs_quotes = arg.len == 0 or arg[arg.len - 1] == '\\';
14345 if (!needs_quotes) {
14346 for (arg) |c| {
14347 switch (c) {
14348 // Known good characters that don't need to be quoted
14349 'A'...'Z', 'a'...'z', '0'...'9', '#', '$', '*', '+', '-', '.', '/', ':', '?', '@', '\\', '_' => {},
14350 // When in doubt, quote
14351 else => {
14352 needs_quotes = true;
14353 break;
14354 },
14355 }
14356 }
14357 }
14358 if (needs_quotes) {
14359 try buf.append('"');
14360 }
14361 var backslashes: usize = 0;
14362 for (arg) |c| {
14363 switch (c) {
14364 '\\' => {
14365 backslashes += 1;
14366 },
14367 '"' => {
14368 try buf.appendNTimes('\\', backslashes);
14369 try buf.append('"');
14370 backslashes = 0;
14371 },
14372 // Replace `%` with `%%cd:~,%`.
14373 //
14374 // cmd.exe allows extracting a substring from an environment
14375 // variable with the syntax: `%foo:~<start_index>,<end_index>%`.
14376 // Therefore, `%cd:~,%` will always expand to an empty string
14377 // since both the start and end index are blank, and it is assumed
14378 // that `%cd%` is always available since it is a built-in variable
14379 // that corresponds to the current directory.
14380 //
14381 // This means that replacing `%foo%` with `%%cd:~,%foo%%cd:~,%`
14382 // will stop `%foo%` from being expanded and *after* expansion
14383 // we'll still be left with `%foo%` (the literal string).
14384 '%' => {
14385 // the trailing `%` is appended outside the switch
14386 try buf.appendSlice("%%cd:~,");
14387 backslashes = 0;
14388 },
14389 else => {
14390 backslashes = 0;
14391 },
14392 }
14393 try buf.append(c);
14394 }
14395 if (needs_quotes) {
14396 try buf.appendNTimes('\\', backslashes);
14397 try buf.append('"');
14398 }
14399 }
14400
14401 try buf.append('"');
14402
14403 return try std.unicode.wtf8ToWtf16LeAllocZ(allocator, buf.items);
14404}
14405
14406const ArgvToCommandLineError = error{ OutOfMemory, InvalidWtf8, InvalidArg0 };
14407
14408/// Serializes `argv` to a Windows command-line string suitable for passing to a child process and
14409/// parsing by the `CommandLineToArgvW` algorithm. The caller owns the returned slice.
14410///
14411/// To avoid arbitrary command execution, this function should not be used when spawning `.bat`/`.cmd` scripts.
14412/// https://flatt.tech/research/posts/batbadbut-you-cant-securely-execute-commands-on-windows/
14413///
14414/// When executing `.bat`/`.cmd` scripts, use `argvToScriptCommandLineWindows` instead.
14415fn argvToCommandLineWindows(
14416 allocator: Allocator,
14417 argv: []const []const u8,
14418) ArgvToCommandLineError![:0]u16 {
14419 var buf = std.array_list.Managed(u8).init(allocator);
14420 defer buf.deinit();
14421
14422 if (argv.len != 0) {
14423 const arg0 = argv[0];
14424
14425 // The first argument must be quoted if it contains spaces or ASCII control characters
14426 // (excluding DEL). It also follows special quoting rules where backslashes have no special
14427 // interpretation, which makes it impossible to pass certain first arguments containing
14428 // double quotes to a child process without characters from the first argument leaking into
14429 // subsequent ones (which could have security implications).
14430 //
14431 // Empty arguments technically don't need quotes, but we quote them anyway for maximum
14432 // compatibility with different implementations of the 'CommandLineToArgvW' algorithm.
14433 //
14434 // Double quotes are illegal in paths on Windows, so for the sake of simplicity we reject
14435 // all first arguments containing double quotes, even ones that we could theoretically
14436 // serialize in unquoted form.
14437 var needs_quotes = arg0.len == 0;
14438 for (arg0) |c| {
14439 if (c <= ' ') {
14440 needs_quotes = true;
14441 } else if (c == '"') {
14442 return error.InvalidArg0;
14443 }
14444 }
14445 if (needs_quotes) {
14446 try buf.append('"');
14447 try buf.appendSlice(arg0);
14448 try buf.append('"');
14449 } else {
14450 try buf.appendSlice(arg0);
14451 }
14452
14453 for (argv[1..]) |arg| {
14454 try buf.append(' ');
14455
14456 // Subsequent arguments must be quoted if they contain spaces, tabs or double quotes,
14457 // or if they are empty. For simplicity and for maximum compatibility with different
14458 // implementations of the 'CommandLineToArgvW' algorithm, we also quote all ASCII
14459 // control characters (again, excluding DEL).
14460 needs_quotes = for (arg) |c| {
14461 if (c <= ' ' or c == '"') {
14462 break true;
14463 }
14464 } else arg.len == 0;
14465 if (!needs_quotes) {
14466 try buf.appendSlice(arg);
14467 continue;
14468 }
14469
14470 try buf.append('"');
14471 var backslash_count: usize = 0;
14472 for (arg) |byte| {
14473 switch (byte) {
14474 '\\' => {
14475 backslash_count += 1;
14476 },
14477 '"' => {
14478 try buf.appendNTimes('\\', backslash_count * 2 + 1);
14479 try buf.append('"');
14480 backslash_count = 0;
14481 },
14482 else => {
14483 try buf.appendNTimes('\\', backslash_count);
14484 try buf.append(byte);
14485 backslash_count = 0;
14486 },
14487 }
14488 }
14489 try buf.appendNTimes('\\', backslash_count * 2);
14490 try buf.append('"');
14491 }
14492 }
14493
14494 return try std.unicode.wtf8ToWtf16LeAllocZ(allocator, buf.items);
14495}
14496
14497test argvToCommandLineWindows {
14498 const t = testArgvToCommandLineWindows;
14499
14500 try t(&.{
14501 \\C:\Program Files\zig\zig.exe
14502 ,
14503 \\run
14504 ,
14505 \\.\src\main.zig
14506 ,
14507 \\-target
14508 ,
14509 \\x86_64-windows-gnu
14510 ,
14511 \\-O
14512 ,
14513 \\ReleaseSafe
14514 ,
14515 \\--
14516 ,
14517 \\--emoji=🗿
14518 ,
14519 \\--eval=new Regex("Dwayne \"The Rock\" Johnson")
14520 ,
14521 },
14522 \\"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\")"
14523 );
14524
14525 try t(&.{}, "");
14526 try t(&.{""}, "\"\"");
14527 try t(&.{" "}, "\" \"");
14528 try t(&.{"\t"}, "\"\t\"");
14529 try t(&.{"\x07"}, "\"\x07\"");
14530 try t(&.{"🦎"}, "🦎");
14531
14532 try t(
14533 &.{ "zig", "aa aa", "bb\tbb", "cc\ncc", "dd\r\ndd", "ee\x7Fee" },
14534 "zig \"aa aa\" \"bb\tbb\" \"cc\ncc\" \"dd\r\ndd\" ee\x7Fee",
14535 );
14536
14537 try t(
14538 &.{ "\\\\foo bar\\foo bar\\", "\\\\zig zag\\zig zag\\" },
14539 "\"\\\\foo bar\\foo bar\\\" \"\\\\zig zag\\zig zag\\\\\"",
14540 );
14541
14542 try std.testing.expectError(
14543 error.InvalidArg0,
14544 argvToCommandLineWindows(std.testing.allocator, &.{"\"quotes\"quotes\""}),
14545 );
14546 try std.testing.expectError(
14547 error.InvalidArg0,
14548 argvToCommandLineWindows(std.testing.allocator, &.{"quotes\"quotes"}),
14549 );
14550 try std.testing.expectError(
14551 error.InvalidArg0,
14552 argvToCommandLineWindows(std.testing.allocator, &.{"q u o t e s \" q u o t e s"}),
14553 );
14554}
14555
14556fn testArgvToCommandLineWindows(argv: []const []const u8, expected_cmd_line: []const u8) !void {
14557 const cmd_line_w = try argvToCommandLineWindows(std.testing.allocator, argv);
14558 defer std.testing.allocator.free(cmd_line_w);
14559
14560 const cmd_line = try std.unicode.wtf16LeToWtf8Alloc(std.testing.allocator, cmd_line_w);
14561 defer std.testing.allocator.free(cmd_line);
14562
14563 try std.testing.expectEqualStrings(expected_cmd_line, cmd_line);
14564}
14565
14566fn posixExecv(
14567 arg0_expand: process.ArgExpansion,
14568 file: [*:0]const u8,
14569 child_argv: [*:null]?[*:0]const u8,
14570 envp: [*:null]const ?[*:0]const u8,
14571 PATH: []const u8,
14572) process.ReplaceError {
14573 const file_slice = std.mem.sliceTo(file, 0);
14574 if (std.mem.findScalar(u8, file_slice, '/') != null) return posixExecvPath(file, child_argv, envp);
14575
14576 // Use of PATH_MAX here is valid as the path_buf will be passed
14577 // directly to the operating system in posixExecvPath.
14578 var path_buf: [posix.PATH_MAX]u8 = undefined;
14579 var it = std.mem.tokenizeScalar(u8, PATH, ':');
14580 var seen_eacces = false;
14581 var err: process.ReplaceError = error.FileNotFound;
14582
14583 // In case of expanding arg0 we must put it back if we return with an error.
14584 const prev_arg0 = child_argv[0];
14585 defer switch (arg0_expand) {
14586 .expand => child_argv[0] = prev_arg0,
14587 .no_expand => {},
14588 };
14589
14590 while (it.next()) |search_path| {
14591 const path_len = search_path.len + file_slice.len + 1;
14592 if (path_buf.len < path_len + 1) return error.NameTooLong;
14593 @memcpy(path_buf[0..search_path.len], search_path);
14594 path_buf[search_path.len] = '/';
14595 @memcpy(path_buf[search_path.len + 1 ..][0..file_slice.len], file_slice);
14596 path_buf[path_len] = 0;
14597 const full_path = path_buf[0..path_len :0].ptr;
14598 switch (arg0_expand) {
14599 .expand => child_argv[0] = full_path,
14600 .no_expand => {},
14601 }
14602 err = posixExecvPath(full_path, child_argv, envp);
14603 switch (err) {
14604 error.AccessDenied => seen_eacces = true,
14605 error.FileNotFound, error.NotDir => {},
14606 else => |e| return e,
14607 }
14608 }
14609 if (seen_eacces) return error.AccessDenied;
14610 return err;
14611}
14612
14613/// This function ignores PATH environment variable.
14614pub fn posixExecvPath(
14615 path: [*:0]const u8,
14616 child_argv: [*:null]const ?[*:0]const u8,
14617 envp: [*:null]const ?[*:0]const u8,
14618) process.ReplaceError {
14619 try Thread.checkCancel();
14620 switch (posix.errno(posix.system.execve(path, child_argv, envp))) {
14621 .FAULT => |err| return errnoBug(err), // Bad pointer parameter.
14622 .@"2BIG" => return error.SystemResources,
14623 .MFILE => return error.ProcessFdQuotaExceeded,
14624 .NAMETOOLONG => return error.NameTooLong,
14625 .NFILE => return error.SystemFdQuotaExceeded,
14626 .NOMEM => return error.SystemResources,
14627 .ACCES => return error.AccessDenied,
14628 .PERM => return error.PermissionDenied,
14629 .INVAL => return error.InvalidExe,
14630 .NOEXEC => return error.InvalidExe,
14631 .IO => return error.FileSystem,
14632 .LOOP => return error.FileSystem,
14633 .ISDIR => return error.IsDir,
14634 .NOENT => return error.FileNotFound,
14635 .NOTDIR => return error.NotDir,
14636 .TXTBSY => return error.FileBusy,
14637 else => |err| switch (native_os) {
14638 .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => switch (err) {
14639 .BADEXEC => return error.InvalidExe,
14640 .BADARCH => return error.InvalidExe,
14641 else => return posix.unexpectedErrno(err),
14642 },
14643 .linux => switch (err) {
14644 .LIBBAD => return error.InvalidExe,
14645 else => return posix.unexpectedErrno(err),
14646 },
14647 else => return posix.unexpectedErrno(err),
14648 },
14649 }
14650}
14651
14652fn windowsMakePipeIn(rd: *?windows.HANDLE, wr: *?windows.HANDLE, sattr: *const windows.SECURITY_ATTRIBUTES) !void {
14653 var rd_h: windows.HANDLE = undefined;
14654 var wr_h: windows.HANDLE = undefined;
14655 try windows.CreatePipe(&rd_h, &wr_h, sattr);
14656 errdefer windowsDestroyPipe(rd_h, wr_h);
14657 try windows.SetHandleInformation(wr_h, windows.HANDLE_FLAG_INHERIT, 0);
14658 rd.* = rd_h;
14659 wr.* = wr_h;
14660}
14661
14662fn windowsDestroyPipe(rd: ?windows.HANDLE, wr: ?windows.HANDLE) void {
14663 if (rd) |h| posix.close(h);
14664 if (wr) |h| posix.close(h);
14665}
14666
14667fn windowsMakeAsyncPipe(rd: *?windows.HANDLE, wr: *?windows.HANDLE, sattr: *const windows.SECURITY_ATTRIBUTES) !void {
14668 var tmp_bufw: [128]u16 = undefined;
14669
14670 // Anonymous pipes are built upon Named pipes.
14671 // https://docs.microsoft.com/en-us/windows/win32/api/namedpipeapi/nf-namedpipeapi-createpipe
14672 // Asynchronous (overlapped) read and write operations are not supported by anonymous pipes.
14673 // https://docs.microsoft.com/en-us/windows/win32/ipc/anonymous-pipe-operations
14674 const pipe_path = blk: {
14675 var tmp_buf: [128]u8 = undefined;
14676 // Forge a random path for the pipe.
14677 const pipe_path = std.fmt.bufPrintSentinel(
14678 &tmp_buf,
14679 "\\\\.\\pipe\\zig-childprocess-{d}-{d}",
14680 .{ windows.GetCurrentProcessId(), pipe_name_counter.fetchAdd(1, .monotonic) },
14681 0,
14682 ) catch unreachable;
14683 const len = std.unicode.wtf8ToWtf16Le(&tmp_bufw, pipe_path) catch unreachable;
14684 tmp_bufw[len] = 0;
14685 break :blk tmp_bufw[0..len :0];
14686 };
14687
14688 // Create the read handle that can be used with overlapped IO ops.
14689 const read_handle = windows.kernel32.CreateNamedPipeW(
14690 pipe_path.ptr,
14691 windows.PIPE_ACCESS_INBOUND | windows.FILE_FLAG_OVERLAPPED,
14692 windows.PIPE_TYPE_BYTE,
14693 1,
14694 4096,
14695 4096,
14696 0,
14697 sattr,
14698 );
14699 if (read_handle == windows.INVALID_HANDLE_VALUE) {
14700 switch (windows.GetLastError()) {
14701 else => |err| return windows.unexpectedError(err),
14702 }
14703 }
14704 errdefer posix.close(read_handle);
14705
14706 var sattr_copy = sattr.*;
14707 const write_handle = windows.kernel32.CreateFileW(
14708 pipe_path.ptr,
14709 .{ .GENERIC = .{ .WRITE = true } },
14710 0,
14711 &sattr_copy,
14712 windows.OPEN_EXISTING,
14713 @bitCast(windows.FILE.ATTRIBUTE{ .NORMAL = true }),
14714 null,
14715 );
14716 if (write_handle == windows.INVALID_HANDLE_VALUE) {
14717 switch (windows.GetLastError()) {
14718 else => |err| return windows.unexpectedError(err),
14719 }
14720 }
14721 errdefer posix.close(write_handle);
14722
14723 try windows.SetHandleInformation(read_handle, windows.HANDLE_FLAG_INHERIT, 0);
14724
14725 rd.* = read_handle;
14726 wr.* = write_handle;
14727}
14728
14729var pipe_name_counter = std.atomic.Value(u32).init(1);
14730
14731fn progressParentFile(userdata: ?*anyopaque) std.Progress.ParentFileError!File {
14732 const t: *Threaded = @ptrCast(@alignCast(userdata));
14733
14734 t.scanEnviron();
14735
14736 const int = try t.environ.zig_progress_handle;
14737
14738 return .{ .handle = switch (@typeInfo(Io.File.Handle)) {
14739 .int => int,
14740 .pointer => @ptrFromInt(int),
14741 else => return error.UnsupportedOperation,
14742 } };
14743}
14744
14745pub fn environString(t: *Threaded, comptime name: []const u8) ?[:0]const u8 {
14746 t.scanEnviron();
14747 return @field(t.environ.string, name);
14748}
14749
14750test {
14751 _ = @import("Threaded/test.zig");
14752}
14753
14754const use_parking_futex = switch (builtin.target.os.tag) {
14755 .windows => true, // RtlWaitOnAddress is a userland implementation anyway
14756 .netbsd => true, // NetBSD has `futex(2)`, but it's historically been quite buggy. TODO: evaluate whether it's okay to use now.
14757 .illumos => true, // Illumos has no futex mechanism
14758 else => false,
14759};
14760const use_parking_sleep = switch (builtin.target.os.tag) {
14761 // On Windows, we can implement sleep either with `NtDelayExecution` (which is how `SleepEx` in
14762 // kernel32 works) or `NtWaitForAlertByThreadId` (thread parking). We're already using the
14763 // latter for futex, so we may as well use it for sleeping too, to maximise code reuse. I'm
14764 // also more confident that it will always correctly handle the cancelation race (so "unpark"
14765 // before "park" causes "park" to return immediately): it *seems* like alertable sleeps paired
14766 // with `NtAlertThread` do actually do this too, but there could be some caveat (e.g. it might
14767 // fail under some specific condition), whereas `NtWaitForAlertByThreadId` must reliably trigger
14768 // this behavior because `RtlWaitOnAddress` relies on it.
14769 .windows => true,
14770
14771 // These targets have `_lwp_park`, which is superior to POSIX nanosleep because it has a better
14772 // cancelation mechanism.
14773 .netbsd,
14774 .illumos,
14775 => true,
14776
14777 else => false,
14778};
14779
14780const parking_futex = struct {
14781 comptime {
14782 assert(use_parking_futex);
14783 }
14784
14785 const Bucket = struct {
14786 /// Used as a fast check for `wake` to avoid having to acquire `mutex` to discover there are no
14787 /// waiters. It is important for `wait` to increment this *before* checking the futex value to
14788 /// avoid a race.
14789 num_waiters: std.atomic.Value(u32),
14790 /// Protects `waiters`.
14791 mutex: std.Thread.Mutex,
14792 waiters: std.DoublyLinkedList,
14793
14794 /// Prevent false sharing between buckets.
14795 _: void align(std.atomic.cache_line) = {},
14796
14797 const init: Bucket = .{ .num_waiters = .init(0), .mutex = .{}, .waiters = .{} };
14798 };
14799
14800 const Waiter = struct {
14801 node: std.DoublyLinkedList.Node,
14802 address: usize,
14803 tid: std.Thread.Id,
14804 /// `thread_status.cancelation` is `.parked` while the thread is waiting. The single thread
14805 /// which atomically updates it (to `.none` or `.canceling`) is responsible for:
14806 ///
14807 /// * Removing the `Waiter` from `Bucket.waiters`
14808 /// * Decrementing `Bucket.num_waiters`
14809 /// * Unparking the thread (*after* the above, so that the `Waiter` does not go out of scope
14810 /// while it is still in the `Bucket`).
14811 thread_status: *std.atomic.Value(Thread.Status),
14812 };
14813
14814 fn bucketForAddress(address: usize) *Bucket {
14815 const global = struct {
14816 /// Length must be a power of two. The longer this array, the less likely contention is
14817 /// between different futexes. This length seems like it'll provide a reasonable balance
14818 /// between contention and memory usage: assuming a 128-byte `Bucket` (due to cache line
14819 /// alignment), this uses 32 KiB of memory.
14820 var buckets: [256]Bucket = @splat(.init);
14821 };
14822
14823 // Here we use Fibonacci hashing: the golden ratio can be used to evenly redistribute input
14824 // values across a range, giving a poor, but extremely quick to compute, hash.
14825
14826 // This literal is the rounded value of '2^64 / phi' (where 'phi' is the golden ratio). The
14827 // shift then converts it to '2^b / phi', where 'b' is the pointer bit width.
14828 const fibonacci_multiplier = 0x9E3779B97F4A7C15 >> (64 - @bitSizeOf(usize));
14829 const hashed = address *% fibonacci_multiplier;
14830
14831 comptime assert(std.math.isPowerOfTwo(global.buckets.len));
14832 // The high bits of `hashed` have better entropy than the low bits.
14833 const index = hashed >> (@bitSizeOf(usize) - @ctz(global.buckets.len));
14834
14835 return &global.buckets[index];
14836 }
14837
14838 fn wait(ptr: *const u32, expect: u32, uncancelable: bool, timeout: Io.Timeout) Io.Cancelable!void {
14839 const bucket = bucketForAddress(@intFromPtr(ptr));
14840
14841 // Put the threadlocal access outside of the critical section.
14842 const opt_thread = Thread.current;
14843 const self_tid = if (opt_thread) |thread| thread.id else std.Thread.getCurrentId();
14844
14845 var waiter: Waiter = .{
14846 .node = undefined, // populated by list append
14847 .address = @intFromPtr(ptr),
14848 .tid = self_tid,
14849 .thread_status = undefined, // populated in critical section
14850 };
14851
14852 var status_buf: std.atomic.Value(Thread.Status) = undefined;
14853
14854 {
14855 bucket.mutex.lock();
14856 defer bucket.mutex.unlock();
14857
14858 _ = bucket.num_waiters.fetchAdd(1, .acquire);
14859
14860 if (@atomicLoad(u32, ptr, .monotonic) != expect) {
14861 assert(bucket.num_waiters.fetchSub(1, .monotonic) > 0);
14862 return;
14863 }
14864
14865 // This is in the critical section to avoid marking the thread as parked until we're
14866 // certain that we're actually going to park.
14867 waiter.thread_status = status: {
14868 cancelable: {
14869 if (uncancelable) break :cancelable;
14870 const thread = opt_thread orelse break :cancelable;
14871 switch (thread.cancel_protection) {
14872 .blocked => break :cancelable,
14873 .unblocked => {},
14874 }
14875 thread.futex_waiter = &waiter;
14876 const old_status = thread.status.fetchOr(
14877 .{ .cancelation = @enumFromInt(0b001), .awaitable = .null },
14878 .release, // release `thread.futex_waiter`
14879 );
14880 switch (old_status.cancelation) {
14881 .none => {}, // status is now `.parked`
14882 .canceling => {
14883 // status is now `.canceled`
14884 assert(bucket.num_waiters.fetchSub(1, .monotonic) > 0);
14885 return error.Canceled;
14886 },
14887 .canceled => break :cancelable, // status is still `.canceled`
14888 .parked => unreachable,
14889 .blocked => unreachable,
14890 .blocked_windows_dns => unreachable,
14891 .blocked_canceling => unreachable,
14892 }
14893 // We could now be unparked for a cancelation at any time!
14894 break :status &thread.status;
14895 }
14896 // This is an uncancelable wait, so just use `status_buf`. Note that the value of
14897 // `status_buf.awaitable` is irrelevant because this is only visible to futex code,
14898 // while only cancelation cares about `awaitable`.
14899 status_buf.raw = .{ .cancelation = .parked, .awaitable = .null };
14900 break :status &status_buf;
14901 };
14902
14903 bucket.waiters.append(&waiter.node);
14904 }
14905
14906 if (park(timeout, ptr)) {
14907 // We were unparked by either `wake` or cancelation, so our current status is either
14908 // `.none` or `.canceling`. In either case, they've already removed `waiter` from
12854 // `bucket`, so we have nothing more to do!14909 // `bucket`, so we have nothing more to do!
12855 } else |err| switch (err) {14910 } else |err| switch (err) {
12856 error.Timeout => {14911 error.Timeout => {
...@@ -13151,3 +15206,140 @@ fn unpark(tids: []const UnparkTid, addr_hint: ?*const anyopaque) void {...@@ -13151,3 +15206,140 @@ fn unpark(tids: []const UnparkTid, addr_hint: ?*const anyopaque) void {
13151 else => comptime unreachable,15206 else => comptime unreachable,
13152 }15207 }
13153}15208}
15209
15210pub const PipeError = error{
15211 SystemFdQuotaExceeded,
15212 ProcessFdQuotaExceeded,
15213} || Io.UnexpectedError;
15214
15215pub fn pipe2(flags: posix.O) PipeError![2]posix.fd_t {
15216 var fds: [2]posix.fd_t = undefined;
15217
15218 if (@TypeOf(posix.system.pipe2) != void) {
15219 switch (posix.errno(posix.system.pipe2(&fds, flags))) {
15220 .SUCCESS => return fds,
15221 .INVAL => |err| return errnoBug(err), // Invalid flags
15222 .NFILE => return error.SystemFdQuotaExceeded,
15223 .MFILE => return error.ProcessFdQuotaExceeded,
15224 else => |err| return posix.unexpectedErrno(err),
15225 }
15226 }
15227
15228 switch (posix.errno(posix.system.pipe(&fds))) {
15229 .SUCCESS => {},
15230 .NFILE => return error.SystemFdQuotaExceeded,
15231 .MFILE => return error.ProcessFdQuotaExceeded,
15232 else => |err| return posix.unexpectedErrno(err),
15233 }
15234 errdefer {
15235 posix.close(fds[0]);
15236 posix.close(fds[1]);
15237 }
15238
15239 // https://github.com/ziglang/zig/issues/18882
15240 if (@as(u32, @bitCast(flags)) == 0) return fds;
15241
15242 // CLOEXEC is special, it's a file descriptor flag and must be set using
15243 // F.SETFD.
15244 if (flags.CLOEXEC) for (fds) |fd| {
15245 switch (posix.errno(posix.system.fcntl(fd, posix.F.SETFD, @as(u32, posix.FD_CLOEXEC)))) {
15246 .SUCCESS => {},
15247 else => |err| return posix.unexpectedErrno(err),
15248 }
15249 };
15250
15251 const new_flags: u32 = f: {
15252 var new_flags = flags;
15253 new_flags.CLOEXEC = false;
15254 break :f @bitCast(new_flags);
15255 };
15256
15257 // Set every other flag affecting the file status using F.SETFL.
15258 if (new_flags != 0) for (fds) |fd| {
15259 switch (posix.errno(posix.system.fcntl(fd, posix.F.SETFL, new_flags))) {
15260 .SUCCESS => {},
15261 .INVAL => |err| return errnoBug(err),
15262 else => |err| return posix.unexpectedErrno(err),
15263 }
15264 };
15265
15266 return fds;
15267}
15268
15269pub const DupError = error{
15270 ProcessFdQuotaExceeded,
15271 SystemResources,
15272} || Io.UnexpectedError || Io.Cancelable;
15273
15274pub fn dup2(old_fd: posix.fd_t, new_fd: posix.fd_t) DupError!void {
15275 const syscall: Syscall = try .start();
15276 while (true) switch (posix.errno(posix.system.dup2(old_fd, new_fd))) {
15277 .SUCCESS => return syscall.finish(),
15278 .BUSY, .INTR => {
15279 try syscall.checkCancel();
15280 continue;
15281 },
15282 .INVAL => |err| return syscall.errnoBug(err), // invalid parameters
15283 .BADF => |err| return syscall.errnoBug(err), // use after free
15284 .MFILE => return syscall.fail(error.ProcessFdQuotaExceeded),
15285 .NOMEM => return syscall.fail(error.SystemResources),
15286 else => |err| return syscall.unexpectedErrno(err),
15287 };
15288}
15289
15290pub const FchdirError = error{
15291 AccessDenied,
15292 NotDir,
15293 FileSystem,
15294} || Io.Cancelable || Io.UnexpectedError;
15295
15296pub fn fchdir(fd: posix.fd_t) FchdirError!void {
15297 if (fd == posix.AT.FDCWD) return;
15298 const syscall: Syscall = try .start();
15299 while (true) switch (posix.errno(posix.system.fchdir(fd))) {
15300 .SUCCESS => return syscall.finish(),
15301 .INTR => {
15302 try syscall.checkCancel();
15303 continue;
15304 },
15305 .ACCES => return syscall.fail(error.AccessDenied),
15306 .NOTDIR => return syscall.fail(error.NotDir),
15307 .IO => return syscall.fail(error.FileSystem),
15308 .BADF => |err| return syscall.errnoBug(err),
15309 else => |err| return syscall.unexpectedErrno(err),
15310 };
15311}
15312
15313pub const ChdirError = error{
15314 AccessDenied,
15315 FileSystem,
15316 SymLinkLoop,
15317 NameTooLong,
15318 FileNotFound,
15319 SystemResources,
15320 NotDir,
15321 BadPathName,
15322} || Io.Cancelable || Io.UnexpectedError;
15323
15324pub fn chdir(dir_path: []const u8) ChdirError!void {
15325 var path_buffer: [posix.PATH_MAX]u8 = undefined;
15326 const dir_path_posix = try pathToPosix(dir_path, &path_buffer);
15327 const syscall: Syscall = try .start();
15328 while (true) switch (posix.errno(posix.system.chdir(dir_path_posix))) {
15329 .SUCCESS => return syscall.finish(),
15330 .INTR => {
15331 try syscall.checkCancel();
15332 continue;
15333 },
15334 .ACCES => return syscall.fail(error.AccessDenied),
15335 .IO => return syscall.fail(error.FileSystem),
15336 .LOOP => return syscall.fail(error.SymLinkLoop),
15337 .NAMETOOLONG => return syscall.fail(error.NameTooLong),
15338 .NOENT => return syscall.fail(error.FileNotFound),
15339 .NOMEM => return syscall.fail(error.SystemResources),
15340 .NOTDIR => return syscall.fail(error.NotDir),
15341 .ILSEQ => return syscall.fail(error.BadPathName),
15342 .FAULT => |err| return syscall.errnoBug(err),
15343 else => |err| return syscall.unexpectedErrno(err),
15344 };
15345}
lib/std/Io/Threaded/test.zig+25-7
...@@ -13,7 +13,10 @@ test "concurrent vs main prevents deadlock via oversubscription" {...@@ -13,7 +13,10 @@ test "concurrent vs main prevents deadlock via oversubscription" {
13 return error.SkipZigTest;13 return error.SkipZigTest;
14 }14 }
1515
16 var threaded: Io.Threaded = .init(std.testing.allocator, .{});16 var threaded: Io.Threaded = .init(std.testing.allocator, .{
17 .argv0 = .empty,
18 .environ = .empty,
19 });
17 defer threaded.deinit();20 defer threaded.deinit();
18 const io = threaded.io();21 const io = threaded.io();
1922
...@@ -46,7 +49,10 @@ test "concurrent vs concurrent prevents deadlock via oversubscription" {...@@ -46,7 +49,10 @@ test "concurrent vs concurrent prevents deadlock via oversubscription" {
46 return error.SkipZigTest;49 return error.SkipZigTest;
47 }50 }
4851
49 var threaded: Io.Threaded = .init(std.testing.allocator, .{});52 var threaded: Io.Threaded = .init(std.testing.allocator, .{
53 .argv0 = .empty,
54 .environ = .empty,
55 });
50 defer threaded.deinit();56 defer threaded.deinit();
51 const io = threaded.io();57 const io = threaded.io();
5258
...@@ -80,7 +86,10 @@ test "async/concurrent context and result alignment" {...@@ -80,7 +86,10 @@ test "async/concurrent context and result alignment" {
80 var buffer: [2048]u8 align(@alignOf(ByteArray512)) = undefined;86 var buffer: [2048]u8 align(@alignOf(ByteArray512)) = undefined;
81 var fba: std.heap.FixedBufferAllocator = .init(&buffer);87 var fba: std.heap.FixedBufferAllocator = .init(&buffer);
8288
83 var threaded: std.Io.Threaded = .init(fba.allocator(), .{});89 var threaded: std.Io.Threaded = .init(fba.allocator(), .{
90 .argv0 = .empty,
91 .environ = .empty,
92 });
84 defer threaded.deinit();93 defer threaded.deinit();
85 const io = threaded.io();94 const io = threaded.io();
8695
...@@ -113,7 +122,10 @@ test "Group.async context alignment" {...@@ -113,7 +122,10 @@ test "Group.async context alignment" {
113 var buffer: [2048]u8 align(@alignOf(ByteArray512)) = undefined;122 var buffer: [2048]u8 align(@alignOf(ByteArray512)) = undefined;
114 var fba: std.heap.FixedBufferAllocator = .init(&buffer);123 var fba: std.heap.FixedBufferAllocator = .init(&buffer);
115124
116 var threaded: std.Io.Threaded = .init(fba.allocator(), .{});125 var threaded: std.Io.Threaded = .init(fba.allocator(), .{
126 .argv0 = .empty,
127 .environ = .empty,
128 });
117 defer threaded.deinit();129 defer threaded.deinit();
118 const io = threaded.io();130 const io = threaded.io();
119131
...@@ -133,7 +145,10 @@ fn returnArray() [32]u8 {...@@ -133,7 +145,10 @@ fn returnArray() [32]u8 {
133}145}
134146
135test "async with array return type" {147test "async with array return type" {
136 var threaded: std.Io.Threaded = .init(std.testing.allocator, .{});148 var threaded: std.Io.Threaded = .init(std.testing.allocator, .{
149 .argv0 = .empty,
150 .environ = .empty,
151 });
137 defer threaded.deinit();152 defer threaded.deinit();
138 const io = threaded.io();153 const io = threaded.io();
139154
...@@ -155,7 +170,10 @@ test "cancel blocked read from pipe" {...@@ -155,7 +170,10 @@ test "cancel blocked read from pipe" {
155 }170 }
156 };171 };
157172
158 var threaded: std.Io.Threaded = .init(std.testing.allocator, .{});173 var threaded: std.Io.Threaded = .init(std.testing.allocator, .{
174 .argv0 = .empty,
175 .environ = .empty,
176 });
159 defer threaded.deinit();177 defer threaded.deinit();
160 const io = threaded.io();178 const io = threaded.io();
161179
...@@ -169,7 +187,7 @@ test "cancel blocked read from pipe" {...@@ -169,7 +187,7 @@ test "cancel blocked read from pipe" {
169 .bInheritHandle = std.os.windows.FALSE,187 .bInheritHandle = std.os.windows.FALSE,
170 }),188 }),
171 else => {189 else => {
172 const pipe = try std.posix.pipe();190 const pipe = try std.Io.Threaded.pipe2(.{});
173 read_end = .{ .handle = pipe[0] };191 read_end = .{ .handle = pipe[0] };
174 write_end = .{ .handle = pipe[1] };192 write_end = .{ .handle = pipe[1] };
175 },193 },
lib/std/Progress.zig+11-12
...@@ -422,7 +422,7 @@ pub const StartFailure = union(enum) {...@@ -422,7 +422,7 @@ pub const StartFailure = union(enum) {
422 unstarted,422 unstarted,
423 spawn_ipc_worker: error{ConcurrencyUnavailable},423 spawn_ipc_worker: error{ConcurrencyUnavailable},
424 spawn_update_worker: error{ConcurrencyUnavailable},424 spawn_update_worker: error{ConcurrencyUnavailable},
425 parse_env_var: error{ InvalidCharacter, Overflow },425 parent_ipc: error{ UnsupportedOperation, UnrecognizedFormat },
426};426};
427427
428const node_storage_buffer_len = 83;428const node_storage_buffer_len = 83;
...@@ -446,6 +446,12 @@ const noop_impl = builtin.single_threaded or switch (builtin.os.tag) {...@@ -446,6 +446,12 @@ const noop_impl = builtin.single_threaded or switch (builtin.os.tag) {
446 else => false,446 else => false,
447};447};
448448
449pub const ParentFileError = error{
450 UnsupportedOperation,
451 EnvironmentVariableMissing,
452 UnrecognizedFormat,
453};
454
449/// Initializes a global Progress instance.455/// Initializes a global Progress instance.
450///456///
451/// Asserts there is only one global Progress instance.457/// Asserts there is only one global Progress instance.
...@@ -476,20 +482,13 @@ pub fn start(io: Io, options: Options) Node {...@@ -476,20 +482,13 @@ pub fn start(io: Io, options: Options) Node {
476482
477 global_progress.io = io;483 global_progress.io = io;
478484
479 if (std.process.parseEnvVarInt("ZIG_PROGRESS", u31, 10)) |ipc_fd| {485 if (io.vtable.progressParentFile(io.userdata)) |ipc_file| {
480 global_progress.update_worker = io.concurrent(ipcThreadRun, .{486 global_progress.update_worker = io.concurrent(ipcThreadRun, .{ io, ipc_file }) catch |err| {
481 io,
482 @as(Io.File, .{ .handle = switch (@typeInfo(Io.File.Handle)) {
483 .int => ipc_fd,
484 .pointer => @ptrFromInt(ipc_fd),
485 else => @compileError("unsupported fd_t of " ++ @typeName(Io.File.Handle)),
486 } }),
487 }) catch |err| {
488 global_progress.start_failure = .{ .spawn_ipc_worker = err };487 global_progress.start_failure = .{ .spawn_ipc_worker = err };
489 return Node.none;488 return Node.none;
490 };489 };
491 } else |env_err| switch (env_err) {490 } else |env_err| switch (env_err) {
492 error.EnvironmentVariableNotFound => {491 error.EnvironmentVariableMissing => {
493 if (options.disable_printing) {492 if (options.disable_printing) {
494 return Node.none;493 return Node.none;
495 }494 }
...@@ -535,7 +534,7 @@ pub fn start(io: Io, options: Options) Node {...@@ -535,7 +534,7 @@ pub fn start(io: Io, options: Options) Node {
535 }534 }
536 },535 },
537 else => |e| {536 else => |e| {
538 global_progress.start_failure = .{ .parse_env_var = e };537 global_progress.start_failure = .{ .parent_ipc = e };
539 return Node.none;538 return Node.none;
540 },539 },
541 }540 }
lib/std/Random/benchmark.zig+6-5
...@@ -123,14 +123,15 @@ fn mode(comptime x: comptime_int) comptime_int {...@@ -123,14 +123,15 @@ fn mode(comptime x: comptime_int) comptime_int {
123 return if (builtin.mode == .Debug) x / 64 else x;123 return if (builtin.mode == .Debug) x / 64 else x;
124}124}
125125
126pub fn main() !void {126pub fn main(init: std.process.Init) !void {
127 const io = init.io;
128 const arena = init.arena.allocator();
129
127 var stdout_buffer: [0x100]u8 = undefined;130 var stdout_buffer: [0x100]u8 = undefined;
128 var stdout_writer = Io.File.stdout().writer(&stdout_buffer);131 var stdout_writer = Io.File.stdout().writer(io, &stdout_buffer);
129 const stdout = &stdout_writer.interface;132 const stdout = &stdout_writer.interface;
130133
131 var buffer: [1024]u8 = undefined;134 const args = try init.minimal.args.toSlice(arena);
132 var fixed = std.heap.FixedBufferAllocator.init(buffer[0..]);
133 const args = try std.process.argsAlloc(fixed.allocator());
134135
135 var filter: ?[]u8 = "";136 var filter: ?[]u8 = "";
136 var count: usize = mode(128 * MiB);137 var count: usize = mode(128 * MiB);
lib/std/c.zig+22-22
...@@ -3764,8 +3764,8 @@ pub const W = switch (native_os) {...@@ -3764,8 +3764,8 @@ pub const W = switch (native_os) {
3764 pub fn EXITSTATUS(x: u32) u8 {3764 pub fn EXITSTATUS(x: u32) u8 {
3765 return @as(u8, @intCast(x >> 8));3765 return @as(u8, @intCast(x >> 8));
3766 }3766 }
3767 pub fn TERMSIG(x: u32) u32 {3767 pub fn TERMSIG(x: u32) SIG {
3768 return status(x);3768 return @enumFromInt(status(x));
3769 }3769 }
3770 pub fn STOPSIG(x: u32) u32 {3770 pub fn STOPSIG(x: u32) u32 {
3771 return x >> 8;3771 return x >> 8;
...@@ -3797,14 +3797,14 @@ pub const W = switch (native_os) {...@@ -3797,14 +3797,14 @@ pub const W = switch (native_os) {
3797 pub fn EXITSTATUS(s: u32) u8 {3797 pub fn EXITSTATUS(s: u32) u8 {
3798 return @as(u8, @intCast((s & 0xff00) >> 8));3798 return @as(u8, @intCast((s & 0xff00) >> 8));
3799 }3799 }
3800 pub fn TERMSIG(s: u32) u32 {3800 pub fn TERMSIG(s: u32) SIG {
3801 return s & 0x7f;3801 return @enumFromInt(s & 0x7f);
3802 }3802 }
3803 pub fn STOPSIG(s: u32) u32 {3803 pub fn STOPSIG(s: u32) u32 {
3804 return EXITSTATUS(s);3804 return EXITSTATUS(s);
3805 }3805 }
3806 pub fn IFEXITED(s: u32) bool {3806 pub fn IFEXITED(s: u32) bool {
3807 return TERMSIG(s) == 0;3807 return (s & 0x7f) == 0;
3808 }3808 }
3809 pub fn IFSTOPPED(s: u32) bool {3809 pub fn IFSTOPPED(s: u32) bool {
3810 return @as(u16, @truncate((((s & 0xffff) *% 0x10001) >> 8))) > 0x7f00;3810 return @as(u16, @truncate((((s & 0xffff) *% 0x10001) >> 8))) > 0x7f00;
...@@ -3825,14 +3825,14 @@ pub const W = switch (native_os) {...@@ -3825,14 +3825,14 @@ pub const W = switch (native_os) {
3825 pub fn EXITSTATUS(s: u32) u8 {3825 pub fn EXITSTATUS(s: u32) u8 {
3826 return @as(u8, @intCast((s >> 8) & 0xff));3826 return @as(u8, @intCast((s >> 8) & 0xff));
3827 }3827 }
3828 pub fn TERMSIG(s: u32) u32 {3828 pub fn TERMSIG(s: u32) SIG {
3829 return s & 0x7f;3829 return @enumFromInt(s & 0x7f);
3830 }3830 }
3831 pub fn STOPSIG(s: u32) u32 {3831 pub fn STOPSIG(s: u32) u32 {
3832 return EXITSTATUS(s);3832 return EXITSTATUS(s);
3833 }3833 }
3834 pub fn IFEXITED(s: u32) bool {3834 pub fn IFEXITED(s: u32) bool {
3835 return TERMSIG(s) == 0;3835 return (s & 0x7f) == 0;
3836 }3836 }
38373837
3838 pub fn IFCONTINUED(s: u32) bool {3838 pub fn IFCONTINUED(s: u32) bool {
...@@ -3859,14 +3859,14 @@ pub const W = switch (native_os) {...@@ -3859,14 +3859,14 @@ pub const W = switch (native_os) {
3859 pub fn EXITSTATUS(s: u32) u8 {3859 pub fn EXITSTATUS(s: u32) u8 {
3860 return @as(u8, @intCast((s >> 8) & 0xff));3860 return @as(u8, @intCast((s >> 8) & 0xff));
3861 }3861 }
3862 pub fn TERMSIG(s: u32) u32 {3862 pub fn TERMSIG(s: u32) SIG {
3863 return s & 0x7f;3863 return @enumFromInt(s & 0x7f);
3864 }3864 }
3865 pub fn STOPSIG(s: u32) u32 {3865 pub fn STOPSIG(s: u32) u32 {
3866 return EXITSTATUS(s);3866 return EXITSTATUS(s);
3867 }3867 }
3868 pub fn IFEXITED(s: u32) bool {3868 pub fn IFEXITED(s: u32) bool {
3869 return TERMSIG(s) == 0;3869 return (s & 0x7f) == 0;
3870 }3870 }
38713871
3872 pub fn IFCONTINUED(s: u32) bool {3872 pub fn IFCONTINUED(s: u32) bool {
...@@ -3893,14 +3893,14 @@ pub const W = switch (native_os) {...@@ -3893,14 +3893,14 @@ pub const W = switch (native_os) {
3893 pub fn EXITSTATUS(s: u32) u8 {3893 pub fn EXITSTATUS(s: u32) u8 {
3894 return @as(u8, @intCast((s & 0xff00) >> 8));3894 return @as(u8, @intCast((s & 0xff00) >> 8));
3895 }3895 }
3896 pub fn TERMSIG(s: u32) u32 {3896 pub fn TERMSIG(s: u32) SIG {
3897 return s & 0x7f;3897 return @enumFromInt(s & 0x7f);
3898 }3898 }
3899 pub fn STOPSIG(s: u32) u32 {3899 pub fn STOPSIG(s: u32) u32 {
3900 return EXITSTATUS(s);3900 return EXITSTATUS(s);
3901 }3901 }
3902 pub fn IFEXITED(s: u32) bool {3902 pub fn IFEXITED(s: u32) bool {
3903 return TERMSIG(s) == 0;3903 return (s & 0x7f) == 0;
3904 }3904 }
3905 pub fn IFSTOPPED(s: u32) bool {3905 pub fn IFSTOPPED(s: u32) bool {
3906 return @as(u16, @truncate((((s & 0xffff) *% 0x10001) >> 8))) > 0x7f00;3906 return @as(u16, @truncate((((s & 0xffff) *% 0x10001) >> 8))) > 0x7f00;
...@@ -3921,8 +3921,8 @@ pub const W = switch (native_os) {...@@ -3921,8 +3921,8 @@ pub const W = switch (native_os) {
3921 return @as(u8, @intCast(s & 0xff));3921 return @as(u8, @intCast(s & 0xff));
3922 }3922 }
39233923
3924 pub fn TERMSIG(s: u32) u32 {3924 pub fn TERMSIG(s: u32) SIG {
3925 return (s >> 8) & 0xff;3925 return @enumFromInt((s >> 8) & 0xff);
3926 }3926 }
39273927
3928 pub fn STOPSIG(s: u32) u32 {3928 pub fn STOPSIG(s: u32) u32 {
...@@ -3949,14 +3949,14 @@ pub const W = switch (native_os) {...@@ -3949,14 +3949,14 @@ pub const W = switch (native_os) {
3949 pub fn EXITSTATUS(s: u32) u8 {3949 pub fn EXITSTATUS(s: u32) u8 {
3950 return @as(u8, @intCast((s >> 8) & 0xff));3950 return @as(u8, @intCast((s >> 8) & 0xff));
3951 }3951 }
3952 pub fn TERMSIG(s: u32) u32 {3952 pub fn TERMSIG(s: u32) SIG {
3953 return (s & 0x7f);3953 return @enumFromInt(s & 0x7f);
3954 }3954 }
3955 pub fn STOPSIG(s: u32) u32 {3955 pub fn STOPSIG(s: u32) u32 {
3956 return EXITSTATUS(s);3956 return EXITSTATUS(s);
3957 }3957 }
3958 pub fn IFEXITED(s: u32) bool {3958 pub fn IFEXITED(s: u32) bool {
3959 return TERMSIG(s) == 0;3959 return (s & 0x7f) == 0;
3960 }3960 }
39613961
3962 pub fn IFCONTINUED(s: u32) bool {3962 pub fn IFCONTINUED(s: u32) bool {
...@@ -3988,12 +3988,12 @@ pub const W = switch (native_os) {...@@ -3988,12 +3988,12 @@ pub const W = switch (native_os) {
3988 return EXITSTATUS(s);3988 return EXITSTATUS(s);
3989 }3989 }
39903990
3991 pub fn TERMSIG(s: u32) u32 {3991 pub fn TERMSIG(s: u32) SIG {
3992 return s & 0x7f;3992 return @enumFromInt(s & 0x7f);
3993 }3993 }
39943994
3995 pub fn IFEXITED(s: u32) bool {3995 pub fn IFEXITED(s: u32) bool {
3996 return TERMSIG(s) == 0;3996 return (s & 0x7f) == 0;
3997 }3997 }
39983998
3999 pub fn IFSTOPPED(s: u32) bool {3999 pub fn IFSTOPPED(s: u32) bool {
lib/std/crypto/benchmark.zig+8-12
...@@ -503,16 +503,16 @@ fn mode(comptime x: comptime_int) comptime_int {...@@ -503,16 +503,16 @@ fn mode(comptime x: comptime_int) comptime_int {
503 return if (builtin.mode == .Debug) x / 64 else x;503 return if (builtin.mode == .Debug) x / 64 else x;
504}504}
505505
506pub fn main() !void {506pub fn main(init: std.process.Init) !void {
507 const io = init.io;
508 const arena = init.arena.allocator();
509
507 // Size of buffer is about size of printed message.510 // Size of buffer is about size of printed message.
508 var stdout_buffer: [0x100]u8 = undefined;511 var stdout_buffer: [0x100]u8 = undefined;
509 var stdout_writer = Io.File.stdout().writer(&stdout_buffer);512 var stdout_writer = Io.File.stdout().writer(io, &stdout_buffer);
510 const stdout = &stdout_writer.interface;513 const stdout = &stdout_writer.interface;
511514
512 var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);515 const args = try init.minimal.args.toSlice(arena);
513 defer arena.deinit();
514 const arena_allocator = arena.allocator();
515 const args = try std.process.argsAlloc(arena_allocator);
516516
517 var filter: ?[]u8 = "";517 var filter: ?[]u8 = "";
518518
...@@ -556,13 +556,9 @@ pub fn main() !void {...@@ -556,13 +556,9 @@ pub fn main() !void {
556 }556 }
557 }557 }
558558
559 var io_threaded = std.Io.Threaded.init(arena_allocator, .{});
560 defer io_threaded.deinit();
561 const io = io_threaded.io();
562
563 inline for (parallel_hashes) |H| {559 inline for (parallel_hashes) |H| {
564 if (filter == null or std.mem.find(u8, H.name, filter.?) != null) {560 if (filter == null or std.mem.find(u8, H.name, filter.?) != null) {
565 const throughput = try benchmarkHashParallel(H.ty, mode(128 * MiB), arena_allocator, io);561 const throughput = try benchmarkHashParallel(H.ty, mode(128 * MiB), arena, io);
566 try stdout.print("{s:>17}: {:10} MiB/s\n", .{ H.name, throughput / (1 * MiB) });562 try stdout.print("{s:>17}: {:10} MiB/s\n", .{ H.name, throughput / (1 * MiB) });
567 try stdout.flush();563 try stdout.flush();
568 }564 }
...@@ -634,7 +630,7 @@ pub fn main() !void {...@@ -634,7 +630,7 @@ pub fn main() !void {
634630
635 inline for (pwhashes) |H| {631 inline for (pwhashes) |H| {
636 if (filter == null or std.mem.find(u8, H.name, filter.?) != null) {632 if (filter == null or std.mem.find(u8, H.name, filter.?) != null) {
637 const throughput = try benchmarkPwhash(arena_allocator, H.ty, H.params, mode(64), io);633 const throughput = try benchmarkPwhash(arena, H.ty, H.params, mode(64), io);
638 try stdout.print("{s:>17}: {d:10.3} s/ops\n", .{ H.name, throughput });634 try stdout.print("{s:>17}: {d:10.3} s/ops\n", .{ H.name, throughput });
639 try stdout.flush();635 try stdout.flush();
640 }636 }
lib/std/debug.zig+1-1
...@@ -40,7 +40,7 @@ pub const cpu_context = @import("debug/cpu_context.zig");...@@ -40,7 +40,7 @@ pub const cpu_context = @import("debug/cpu_context.zig");
40/// pub fn deinit(si: *SelfInfo, gpa: Allocator) void;40/// pub fn deinit(si: *SelfInfo, gpa: Allocator) void;
41///41///
42/// /// Returns the symbol and source location of the instruction at `address`.42/// /// Returns the symbol and source location of the instruction at `address`.
43/// pub fn getSymbol(si: *SelfInfo, gpa: Allocator, address: usize) SelfInfoError!Symbol;43/// pub fn getSymbol(si: *SelfInfo, gpa: Allocator, io: Io, address: usize) SelfInfoError!Symbol;
44/// /// Returns a name for the "module" (e.g. shared library or executable image) containing `address`.44/// /// Returns a name for the "module" (e.g. shared library or executable image) containing `address`.
45/// pub fn getModuleName(si: *SelfInfo, gpa: Allocator, address: usize) SelfInfoError![]const u8;45/// pub fn getModuleName(si: *SelfInfo, gpa: Allocator, address: usize) SelfInfoError![]const u8;
46///46///
lib/std/debug/ElfFile.zig+6-4
...@@ -67,15 +67,16 @@ pub const DebugInfoSearchPaths = struct {...@@ -67,15 +67,16 @@ pub const DebugInfoSearchPaths = struct {
67 };67 };
6868
69 pub fn native(exe_path: []const u8) DebugInfoSearchPaths {69 pub fn native(exe_path: []const u8) DebugInfoSearchPaths {
70 return .{70 if (std.Options.elf_debug_info_search_paths) |f| return f(exe_path);
71 if (std.Options.debug_threaded_io) |t| return .{
71 .debuginfod_client = p: {72 .debuginfod_client = p: {
72 if (std.posix.getenv("DEBUGINFOD_CACHE_PATH")) |p| {73 if (t.environString("DEBUGINFOD_CACHE_PATH")) |p| {
73 break :p .{ p, "" };74 break :p .{ p, "" };
74 }75 }
75 if (std.posix.getenv("XDG_CACHE_HOME")) |cache_path| {76 if (t.environString("XDG_CACHE_HOME")) |cache_path| {
76 break :p .{ cache_path, "/debuginfod_client" };77 break :p .{ cache_path, "/debuginfod_client" };
77 }78 }
78 if (std.posix.getenv("HOME")) |home_path| {79 if (t.environString("HOME")) |home_path| {
79 break :p .{ home_path, "/.cache/debuginfod_client" };80 break :p .{ home_path, "/.cache/debuginfod_client" };
80 }81 }
81 break :p null;82 break :p null;
...@@ -85,6 +86,7 @@ pub const DebugInfoSearchPaths = struct {...@@ -85,6 +86,7 @@ pub const DebugInfoSearchPaths = struct {
85 },86 },
86 .exe_dir = std.fs.path.dirname(exe_path) orelse ".",87 .exe_dir = std.fs.path.dirname(exe_path) orelse ".",
87 };88 };
89 @compileError("std.Options.elf_debug_info_search_paths must be provided");
88 }90 }
89};91};
9092
lib/std/debug/SelfInfo/Elf.zig+1
...@@ -322,6 +322,7 @@ const Module = struct {...@@ -322,6 +322,7 @@ const Module = struct {
322 if (mod.loaded_elf == null) mod.loaded_elf = loadElf(mod, gpa, io);322 if (mod.loaded_elf == null) mod.loaded_elf = loadElf(mod, gpa, io);
323 return if (mod.loaded_elf.?) |*elf| elf else |err| err;323 return if (mod.loaded_elf.?) |*elf| elf else |err| err;
324 }324 }
325
325 fn loadElf(mod: *Module, gpa: Allocator, io: Io) Error!LoadedElf {326 fn loadElf(mod: *Module, gpa: Allocator, io: Io) Error!LoadedElf {
326 const load_result = if (mod.name.len > 0) res: {327 const load_result = if (mod.name.len > 0) res: {
327 var file = Io.Dir.cwd().openFile(io, mod.name, .{}) catch return error.MissingDebugInfo;328 var file = Io.Dir.cwd().openFile(io, mod.name, .{}) catch return error.MissingDebugInfo;
lib/std/dynamic_library.zig+18-10
...@@ -31,12 +31,20 @@ pub const DynLib = struct {...@@ -31,12 +31,20 @@ pub const DynLib = struct {
3131
32 /// Trusts the file. Malicious file will be able to execute arbitrary code.32 /// Trusts the file. Malicious file will be able to execute arbitrary code.
33 pub fn open(path: []const u8) Error!DynLib {33 pub fn open(path: []const u8) Error!DynLib {
34 return .{ .inner = try InnerType.open(path) };34 if (InnerType == ElfDynLib) {
35 return .{ .inner = try InnerType.open(path, null) };
36 } else {
37 return .{ .inner = try InnerType.open(path) };
38 }
35 }39 }
3640
37 /// Trusts the file. Malicious file will be able to execute arbitrary code.41 /// Trusts the file. Malicious file will be able to execute arbitrary code.
38 pub fn openZ(path_c: [*:0]const u8) Error!DynLib {42 pub fn openZ(path_c: [*:0]const u8) Error!DynLib {
39 return .{ .inner = try InnerType.openZ(path_c) };43 if (InnerType == ElfDynLib) {
44 return .{ .inner = try InnerType.openZ(path_c, null) };
45 } else {
46 return .{ .inner = try InnerType.openZ(path_c) };
47 }
40 }48 }
4149
42 /// Trusts the file.50 /// Trusts the file.
...@@ -197,7 +205,7 @@ pub const ElfDynLib = struct {...@@ -197,7 +205,7 @@ pub const ElfDynLib = struct {
197 // - DT_RPATH of the calling binary is not used as a search path205 // - DT_RPATH of the calling binary is not used as a search path
198 // - DT_RUNPATH of the calling binary is not used as a search path206 // - DT_RUNPATH of the calling binary is not used as a search path
199 // - /etc/ld.so.cache is not read207 // - /etc/ld.so.cache is not read
200 fn resolveFromName(io: Io, path_or_name: []const u8) !posix.fd_t {208 fn resolveFromName(io: Io, path_or_name: []const u8, LD_LIBRARY_PATH: ?[]const u8) !posix.fd_t {
201 // If filename contains a slash ("/"), then it is interpreted as a (relative or absolute) pathname209 // If filename contains a slash ("/"), then it is interpreted as a (relative or absolute) pathname
202 if (std.mem.findScalarPos(u8, path_or_name, 0, '/')) |_| {210 if (std.mem.findScalarPos(u8, path_or_name, 0, '/')) |_| {
203 return posix.open(path_or_name, .{ .ACCMODE = .RDONLY, .CLOEXEC = true }, 0);211 return posix.open(path_or_name, .{ .ACCMODE = .RDONLY, .CLOEXEC = true }, 0);
...@@ -207,7 +215,7 @@ pub const ElfDynLib = struct {...@@ -207,7 +215,7 @@ pub const ElfDynLib = struct {
207 if (std.os.linux.geteuid() == std.os.linux.getuid() and215 if (std.os.linux.geteuid() == std.os.linux.getuid() and
208 std.os.linux.getegid() == std.os.linux.getgid())216 std.os.linux.getegid() == std.os.linux.getgid())
209 {217 {
210 if (posix.getenvZ("LD_LIBRARY_PATH")) |ld_library_path| {218 if (LD_LIBRARY_PATH) |ld_library_path| {
211 if (resolveFromSearchPath(io, ld_library_path, path_or_name, ':')) |fd| {219 if (resolveFromSearchPath(io, ld_library_path, path_or_name, ':')) |fd| {
212 return fd;220 return fd;
213 }221 }
...@@ -221,10 +229,10 @@ pub const ElfDynLib = struct {...@@ -221,10 +229,10 @@ pub const ElfDynLib = struct {
221 }229 }
222230
223 /// Trusts the file. Malicious file will be able to execute arbitrary code.231 /// Trusts the file. Malicious file will be able to execute arbitrary code.
224 pub fn open(path: []const u8) Error!ElfDynLib {232 pub fn open(path: []const u8, LD_LIBRARY_PATH: ?[]const u8) Error!ElfDynLib {
225 const io = std.Options.debug_io;233 const io = std.Options.debug_io;
226234
227 const fd = try resolveFromName(io, path);235 const fd = try resolveFromName(io, path, LD_LIBRARY_PATH);
228 defer posix.close(fd);236 defer posix.close(fd);
229237
230 const file: Io.File = .{ .handle = fd };238 const file: Io.File = .{ .handle = fd };
...@@ -371,8 +379,8 @@ pub const ElfDynLib = struct {...@@ -371,8 +379,8 @@ pub const ElfDynLib = struct {
371 }379 }
372380
373 /// Trusts the file. Malicious file will be able to execute arbitrary code.381 /// Trusts the file. Malicious file will be able to execute arbitrary code.
374 pub fn openZ(path_c: [*:0]const u8) Error!ElfDynLib {382 pub fn openZ(path_c: [*:0]const u8, LD_LIBRARY_PATH: ?[]const u8) Error!ElfDynLib {
375 return open(mem.sliceTo(path_c, 0));383 return open(mem.sliceTo(path_c, 0), LD_LIBRARY_PATH);
376 }384 }
377385
378 /// Trusts the file386 /// Trusts the file
...@@ -554,8 +562,8 @@ fn checkver(def_arg: *elf.Verdef, vsym_arg: elf.Versym, vername: []const u8, str...@@ -554,8 +562,8 @@ fn checkver(def_arg: *elf.Verdef, vsym_arg: elf.Versym, vername: []const u8, str
554562
555test "ElfDynLib" {563test "ElfDynLib" {
556 if (native_os != .linux) return error.SkipZigTest;564 if (native_os != .linux) return error.SkipZigTest;
557 try testing.expectError(error.FileNotFound, ElfDynLib.open("invalid_so.so"));565 try testing.expectError(error.FileNotFound, ElfDynLib.open("invalid_so.so", null));
558 try testing.expectError(error.FileNotFound, ElfDynLib.openZ("invalid_so.so"));566 try testing.expectError(error.FileNotFound, ElfDynLib.openZ("invalid_so.so", null));
559}567}
560568
561/// Separated to avoid referencing `WindowsDynLib`, because its field types may not569/// Separated to avoid referencing `WindowsDynLib`, because its field types may not
lib/std/fs/path.zig+277-147
...@@ -13,16 +13,15 @@...@@ -13,16 +13,15 @@
13//! https://github.com/WebAssembly/wasi-filesystem/issues/17#issuecomment-143063935313//! https://github.com/WebAssembly/wasi-filesystem/issues/17#issuecomment-1430639353
1414
15const builtin = @import("builtin");15const builtin = @import("builtin");
16const native_os = builtin.target.os.tag;
17
16const std = @import("../std.zig");18const std = @import("../std.zig");
17const debug = std.debug;19const assert = std.debug.assert;
18const assert = debug.assert;
19const testing = std.testing;20const testing = std.testing;
20const mem = std.mem;21const mem = std.mem;
21const ascii = std.ascii;22const Allocator = std.mem.Allocator;
22const Allocator = mem.Allocator;23const eqlIgnoreCaseWtf8 = std.os.windows.eqlIgnoreCaseWtf8;
23const windows = std.os.windows;24const eqlIgnoreCaseWtf16 = std.os.windows.eqlIgnoreCaseWtf16;
24const process = std.process;
25const native_os = builtin.target.os.tag;
2625
27pub const sep_windows: u8 = '\\';26pub const sep_windows: u8 = '\\';
28pub const sep_posix: u8 = '/';27pub const sep_posix: u8 = '/';
...@@ -281,7 +280,7 @@ pub fn isAbsolute(path: []const u8) bool {...@@ -281,7 +280,7 @@ pub fn isAbsolute(path: []const u8) bool {
281}280}
282281
283fn isAbsoluteWindowsImpl(comptime T: type, path: []const T) bool {282fn isAbsoluteWindowsImpl(comptime T: type, path: []const T) bool {
284 return switch (windows.getWin32PathType(T, path)) {283 return switch (getWin32PathType(T, path)) {
285 // Unambiguously absolute284 // Unambiguously absolute
286 .drive_absolute, .unc_absolute, .local_device, .root_local_device => true,285 .drive_absolute, .unc_absolute, .local_device, .root_local_device => true,
287 // Unambiguously relative286 // Unambiguously relative
...@@ -515,13 +514,13 @@ test parsePathPosix {...@@ -515,13 +514,13 @@ test parsePathPosix {
515514
516pub fn WindowsPath2(comptime T: type) type {515pub fn WindowsPath2(comptime T: type) type {
517 return struct {516 return struct {
518 kind: windows.Win32PathType,517 kind: Win32PathType,
519 root: []const T,518 root: []const T,
520 };519 };
521}520}
522521
523pub fn parsePathWindows(comptime T: type, path: []const T) WindowsPath2(T) {522pub fn parsePathWindows(comptime T: type, path: []const T) WindowsPath2(T) {
524 const kind = windows.getWin32PathType(T, path);523 const kind = getWin32PathType(T, path);
525 const root = root: switch (kind) {524 const root = root: switch (kind) {
526 .drive_absolute, .drive_relative => {525 .drive_absolute, .drive_relative => {
527 const drive_letter_len = getDriveLetter(T, path).len;526 const drive_letter_len = getDriveLetter(T, path).len;
...@@ -731,7 +730,7 @@ fn parseUNC(comptime T: type, path: []const T) WindowsUNC(T) {...@@ -731,7 +730,7 @@ fn parseUNC(comptime T: type, path: []const T) WindowsUNC(T) {
731 // For the share, there can be any number of path separators between the server730 // For the share, there can be any number of path separators between the server
732 // and the share, so we want to skip over all of them instead of just looking for731 // and the share, so we want to skip over all of them instead of just looking for
733 // the first one.732 // the first one.
734 var it = std.mem.tokenizeAny(T, path[server_end + 1 ..], any_sep);733 var it = mem.tokenizeAny(T, path[server_end + 1 ..], any_sep);
735 const share = it.next() orelse return .{734 const share = it.next() orelse return .{
736 .server = path[2..server_end],735 .server = path[2..server_end],
737 .sep_after_server = true,736 .sep_after_server = true,
...@@ -803,8 +802,8 @@ const DiskDesignatorKind = enum { drive, unc };...@@ -803,8 +802,8 @@ const DiskDesignatorKind = enum { drive, unc };
803/// `p1` and `p2` are both assumed to be the `kind` provided.802/// `p1` and `p2` are both assumed to be the `kind` provided.
804fn compareDiskDesignators(comptime T: type, kind: DiskDesignatorKind, p1: []const T, p2: []const T) bool {803fn compareDiskDesignators(comptime T: type, kind: DiskDesignatorKind, p1: []const T, p2: []const T) bool {
805 const eql = switch (T) {804 const eql = switch (T) {
806 u8 => windows.eqlIgnoreCaseWtf8,805 u8 => eqlIgnoreCaseWtf8,
807 u16 => windows.eqlIgnoreCaseWtf16,806 u16 => eqlIgnoreCaseWtf16,
808 else => @compileError("only u8 (WTF-8) and u16 (WTF-16LE) is supported"),807 else => @compileError("only u8 (WTF-8) and u16 (WTF-16LE) is supported"),
809 };808 };
810 switch (kind) {809 switch (kind) {
...@@ -1094,10 +1093,14 @@ pub fn resolveWindows(allocator: Allocator, paths: []const []const u8) Allocator...@@ -1094,10 +1093,14 @@ pub fn resolveWindows(allocator: Allocator, paths: []const []const u8) Allocator
1094}1093}
10951094
1096/// This function is like a series of `cd` statements executed one after another.1095/// This function is like a series of `cd` statements executed one after another.
1096///
1097/// It resolves "." and ".." to the best of its ability, but will not convert relative paths to1097/// It resolves "." and ".." to the best of its ability, but will not convert relative paths to
1098/// an absolute path, use Io.Dir.realpath instead.1098/// an absolute path, use Io.Dir.realpath instead.
1099///
1099/// ".." components may persist in the resolved path if the resolved path is relative.1100/// ".." components may persist in the resolved path if the resolved path is relative.
1101///
1100/// The result does not have a trailing path separator.1102/// The result does not have a trailing path separator.
1103///
1101/// This function does not perform any syscalls. Executing this series of path1104/// This function does not perform any syscalls. Executing this series of path
1102/// lookups on the actual filesystem may produce different results due to1105/// lookups on the actual filesystem may produce different results due to
1103/// symlinks.1106/// symlinks.
...@@ -1494,25 +1497,54 @@ fn testBasenameWindows(input: []const u8, expected_output: []const u8) !void {...@@ -1494,25 +1497,54 @@ fn testBasenameWindows(input: []const u8, expected_output: []const u8) !void {
1494 try testing.expectEqualSlices(u8, expected_output, basenameWindows(input));1497 try testing.expectEqualSlices(u8, expected_output, basenameWindows(input));
1495}1498}
14961499
1497pub const RelativeError = std.process.GetCwdAllocError;1500/// Returns the non-absolute path from `from` to `to`.
14981501///
1499/// Returns the relative path from `from` to `to`. If `from` and `to` each1502/// Other than memory allocation, this is a pure function; the result solely
1500/// resolve to the same path (after calling `resolve` on each), a zero-length1503/// depends on the input parameters.
1501/// string is returned.1504///
1502/// On Windows, the result is not guaranteed to be relative, as the paths may be1505/// If `from` and `to` each resolve to the same path (after calling `resolve`
1503/// on different volumes. In that case, the result will be the canonicalized absolute1506/// on each), a zero-length string is returned.
1504/// path of `to`.1507///
1505pub fn relative(allocator: Allocator, from: []const u8, to: []const u8) RelativeError![]u8 {1508/// See `relativePosix` and `relativeWindows` for operating system specific
1509/// details and for how `environ_map` is used.
1510pub fn relative(
1511 gpa: Allocator,
1512 cwd: []const u8,
1513 environ_map: ?*const std.process.Environ.Map,
1514 from: []const u8,
1515 to: []const u8,
1516) Allocator.Error![]u8 {
1506 if (native_os == .windows) {1517 if (native_os == .windows) {
1507 return relativeWindows(allocator, from, to);1518 return relativeWindows(gpa, cwd, environ_map, from, to);
1508 } else {1519 } else {
1509 return relativePosix(allocator, from, to);1520 return relativePosix(gpa, cwd, from, to);
1510 }1521 }
1511}1522}
15121523
1513pub fn relativeWindows(allocator: Allocator, from: []const u8, to: []const u8) ![]u8 {1524/// Returns the non-absolute path from `from` to `to` according to Windows rules.
1514 if (native_os != .windows) @compileError("this function relies on Windows-specific semantics");1525///
15151526/// Other than memory allocation, this is a pure function; the result solely
1527/// depends on the input parameters.
1528///
1529/// If `from` and `to` each resolve to the same path (after calling `resolve`
1530/// on each), a zero-length string is returned.
1531///
1532/// The result is not guaranteed to be relative, as the paths may be on
1533/// different volumes. In that case, the result will be the canonicalized
1534/// absolute path of `to`.
1535///
1536/// Per-drive CWDs are stored in special semi-hidden environment variables of
1537/// the format `=<drive-letter>:`, e.g. `=C:`. This type of CWD is purely a
1538/// shell concept, so there's no guarantee that it'll be set or that it'll even
1539/// be accurate. This is the only reason for the `environ_map` parameter. `null` is
1540/// treated equivalent to the environment variable missing.
1541pub fn relativeWindows(
1542 gpa: Allocator,
1543 cwd: []const u8,
1544 environ_map: ?*const std.process.Environ.Map,
1545 from: []const u8,
1546 to: []const u8,
1547) Allocator.Error![]u8 {
1516 const parsed_from = parsePathWindows(u8, from);1548 const parsed_from = parsePathWindows(u8, from);
1517 const parsed_to = parsePathWindows(u8, to);1549 const parsed_to = parsePathWindows(u8, to);
15181550
...@@ -1533,14 +1565,14 @@ pub fn relativeWindows(allocator: Allocator, from: []const u8, to: []const u8) !...@@ -1533,14 +1565,14 @@ pub fn relativeWindows(allocator: Allocator, from: []const u8, to: []const u8) !
1533 };1565 };
15341566
1535 if (result_is_always_to) {1567 if (result_is_always_to) {
1536 return windowsResolveAgainstCwd(allocator, to, parsed_to);1568 return windowsResolveAgainstCwd(gpa, cwd, environ_map, to, parsed_to);
1537 }1569 }
15381570
1539 const resolved_from = try windowsResolveAgainstCwd(allocator, from, parsed_from);1571 const resolved_from = try windowsResolveAgainstCwd(gpa, cwd, environ_map, from, parsed_from);
1540 defer allocator.free(resolved_from);1572 defer gpa.free(resolved_from);
1541 var clean_up_resolved_to = true;1573 var clean_up_resolved_to = true;
1542 const resolved_to = try windowsResolveAgainstCwd(allocator, to, parsed_to);1574 const resolved_to = try windowsResolveAgainstCwd(gpa, cwd, environ_map, to, parsed_to);
1543 defer if (clean_up_resolved_to) allocator.free(resolved_to);1575 defer if (clean_up_resolved_to) gpa.free(resolved_to);
15441576
1545 const parsed_resolved_from = parsePathWindows(u8, resolved_from);1577 const parsed_resolved_from = parsePathWindows(u8, resolved_from);
1546 const parsed_resolved_to = parsePathWindows(u8, resolved_to);1578 const parsed_resolved_to = parsePathWindows(u8, resolved_to);
...@@ -1569,18 +1601,18 @@ pub fn relativeWindows(allocator: Allocator, from: []const u8, to: []const u8) !...@@ -1569,18 +1601,18 @@ pub fn relativeWindows(allocator: Allocator, from: []const u8, to: []const u8) !
1569 var from_it = mem.tokenizeAny(u8, resolved_from[parsed_resolved_from.root.len..], "/\\");1601 var from_it = mem.tokenizeAny(u8, resolved_from[parsed_resolved_from.root.len..], "/\\");
1570 var to_it = mem.tokenizeAny(u8, resolved_to[parsed_resolved_to.root.len..], "/\\");1602 var to_it = mem.tokenizeAny(u8, resolved_to[parsed_resolved_to.root.len..], "/\\");
1571 while (true) {1603 while (true) {
1572 const from_component = from_it.next() orelse return allocator.dupe(u8, to_it.rest());1604 const from_component = from_it.next() orelse return gpa.dupe(u8, to_it.rest());
1573 const to_rest = to_it.rest();1605 const to_rest = to_it.rest();
1574 if (to_it.next()) |to_component| {1606 if (to_it.next()) |to_component| {
1575 if (windows.eqlIgnoreCaseWtf8(from_component, to_component))1607 if (eqlIgnoreCaseWtf8(from_component, to_component))
1576 continue;1608 continue;
1577 }1609 }
1578 var up_index_end = "..".len;1610 var up_index_end = "..".len;
1579 while (from_it.next()) |_| {1611 while (from_it.next()) |_| {
1580 up_index_end += "\\..".len;1612 up_index_end += "\\..".len;
1581 }1613 }
1582 const result = try allocator.alloc(u8, up_index_end + @intFromBool(to_rest.len > 0) + to_rest.len);1614 const result = try gpa.alloc(u8, up_index_end + @intFromBool(to_rest.len > 0) + to_rest.len);
1583 errdefer allocator.free(result);1615 errdefer gpa.free(result);
15841616
1585 result[0..2].* = "..".*;1617 result[0..2].* = "..".*;
1586 var result_index: usize = 2;1618 var result_index: usize = 2;
...@@ -1597,85 +1629,60 @@ pub fn relativeWindows(allocator: Allocator, from: []const u8, to: []const u8) !...@@ -1597,85 +1629,60 @@ pub fn relativeWindows(allocator: Allocator, from: []const u8, to: []const u8) !
1597 result_index += to_component.len;1629 result_index += to_component.len;
1598 }1630 }
15991631
1600 return allocator.realloc(result, result_index);1632 return gpa.realloc(result, result_index);
1601 }1633 }
1602 return [_]u8{};1634 return [_]u8{};
1603}1635}
16041636
1605fn windowsResolveAgainstCwd(allocator: Allocator, path: []const u8, parsed: WindowsPath2(u8)) ![]u8 {1637fn windowsResolveAgainstCwd(
1638 gpa: Allocator,
1639 cwd: []const u8,
1640 environ_map: ?*const std.process.Environ.Map,
1641 path: []const u8,
1642 parsed: WindowsPath2(u8),
1643) ![]u8 {
1606 // Space for 256 WTF-16 code units; potentially 3 WTF-8 bytes per WTF-16 code unit1644 // Space for 256 WTF-16 code units; potentially 3 WTF-8 bytes per WTF-16 code unit
1607 var temp_allocator_state = std.heap.stackFallback(256 * 3, allocator);1645 var temp_allocator_state = std.heap.stackFallback(256 * 3, gpa);
1608 return switch (parsed.kind) {1646 return switch (parsed.kind) {
1609 .drive_absolute,1647 .drive_absolute,
1610 .unc_absolute,1648 .unc_absolute,
1611 .root_local_device,1649 .root_local_device,
1612 .local_device,1650 .local_device,
1613 => try resolveWindows(allocator, &.{path}),1651 => try resolveWindows(gpa, &.{path}),
1614 .relative => blk: {
1615 const temp_allocator = temp_allocator_state.get();
16161652
1617 const peb_cwd = windows.peb().ProcessParameters.CurrentDirectory.DosPath;1653 .relative => try resolveWindows(gpa, &.{ cwd, path }),
1618 const cwd_w = (peb_cwd.Buffer.?)[0 .. peb_cwd.Length / 2];
16191654
1620 const wtf8_len = std.unicode.calcWtf8Len(cwd_w);
1621 const wtf8_buf = try temp_allocator.alloc(u8, wtf8_len);
1622 defer temp_allocator.free(wtf8_buf);
1623 assert(std.unicode.wtf16LeToWtf8(wtf8_buf, cwd_w) == wtf8_len);
1624
1625 break :blk try resolveWindows(allocator, &.{ wtf8_buf, path });
1626 },
1627 .rooted => blk: {1655 .rooted => blk: {
1628 const peb_cwd = windows.peb().ProcessParameters.CurrentDirectory.DosPath;1656 const parsed_cwd = parsePathWindows(u8, cwd);
1629 const cwd_w = (peb_cwd.Buffer.?)[0 .. peb_cwd.Length / 2];
1630 const parsed_cwd = parsePathWindows(u16, cwd_w);
1631 switch (parsed_cwd.kind) {1657 switch (parsed_cwd.kind) {
1632 .drive_absolute => {1658 .drive_absolute => {
1633 var drive_buf = "_:\\".*;1659 var drive_buf = "_:\\".*;
1634 drive_buf[0] = @truncate(cwd_w[0]);1660 drive_buf[0] = cwd[0];
1635 break :blk try resolveWindows(allocator, &.{ &drive_buf, path });1661 break :blk try resolveWindows(gpa, &.{ &drive_buf, path });
1636 },1662 },
1637 .unc_absolute => {1663 .unc_absolute => {
1638 const temp_allocator = temp_allocator_state.get();1664 break :blk try resolveWindows(gpa, &.{ parsed_cwd.root, path });
1639 var root_buf = try temp_allocator.alloc(u8, parsed_cwd.root.len * 3);
1640 defer temp_allocator.free(root_buf);
1641
1642 const wtf8_len = std.unicode.wtf16LeToWtf8(root_buf, parsed_cwd.root);
1643 const root = root_buf[0..wtf8_len];
1644 break :blk try resolveWindows(allocator, &.{ root, path });
1645 },1665 },
1646 // Effectively a malformed CWD, give up and just return a normalized path1666 // Effectively a malformed CWD, give up and just return a normalized path
1647 else => break :blk try resolveWindows(allocator, &.{path}),1667 else => break :blk try resolveWindows(gpa, &.{path}),
1648 }1668 }
1649 },1669 },
1650 .drive_relative => blk: {1670 .drive_relative => blk: {
1651 const temp_allocator = temp_allocator_state.get();1671 const temp_allocator = temp_allocator_state.get();
1652 const drive_cwd = drive_cwd: {1672 const drive_cwd = drive_cwd: {
1653 const peb_cwd = windows.peb().ProcessParameters.CurrentDirectory.DosPath;1673 const parsed_cwd = parsePathWindows(u8, cwd);
1654 const cwd_w = (peb_cwd.Buffer.?)[0 .. peb_cwd.Length / 2];
1655 const parsed_cwd = parsePathWindows(u16, cwd_w);
16561674
1657 if (parsed_cwd.kind == .drive_absolute) {1675 if (parsed_cwd.kind == .drive_absolute) {
1658 const drive_letter_w = parsed_cwd.root[0];1676 const drive_letter_w = parsed_cwd.root[0];
1659 const drive_letters_match = drive_letter_w <= 0x7F and1677 const drive_letters_match = drive_letter_w <= 0x7F and
1660 ascii.toUpper(@intCast(drive_letter_w)) == ascii.toUpper(parsed.root[0]);1678 std.ascii.toUpper(@intCast(drive_letter_w)) == std.ascii.toUpper(parsed.root[0]);
1661 if (drive_letters_match) {1679 if (drive_letters_match)
1662 const wtf8_len = std.unicode.calcWtf8Len(cwd_w);1680 break :drive_cwd cwd;
1663 const wtf8_buf = try temp_allocator.alloc(u8, wtf8_len);1681
1664 assert(std.unicode.wtf16LeToWtf8(wtf8_buf, cwd_w) == wtf8_len);1682 if (environ_map) |m| {
1665 break :drive_cwd wtf8_buf[0..];1683 if (m.get(&.{ '=', parsed.root[0], ':' })) |v| {
1666 }1684 break :drive_cwd try temp_allocator.dupe(u8, v);
16671685 }
1668 // Per-drive CWD's are stored in special semi-hidden environment variables
1669 // of the format `=<drive-letter>:`, e.g. `=C:`. This type of CWD is
1670 // purely a shell concept, so there's no guarantee that it'll be set
1671 // or that it'll even be accurate.
1672 var key_buf = std.unicode.wtf8ToWtf16LeStringLiteral("=_:").*;
1673 key_buf[1] = parsed.root[0];
1674 if (std.process.getenvW(&key_buf)) |drive_cwd_w| {
1675 const wtf8_len = std.unicode.calcWtf8Len(drive_cwd_w);
1676 const wtf8_buf = try temp_allocator.alloc(u8, wtf8_len);
1677 assert(std.unicode.wtf16LeToWtf8(wtf8_buf, drive_cwd_w) == wtf8_len);
1678 break :drive_cwd wtf8_buf[0..];
1679 }1686 }
1680 }1687 }
16811688
...@@ -1686,16 +1693,20 @@ fn windowsResolveAgainstCwd(allocator: Allocator, path: []const u8, parsed: Wind...@@ -1686,16 +1693,20 @@ fn windowsResolveAgainstCwd(allocator: Allocator, path: []const u8, parsed: Wind
1686 break :drive_cwd drive_buf;1693 break :drive_cwd drive_buf;
1687 };1694 };
1688 defer temp_allocator.free(drive_cwd);1695 defer temp_allocator.free(drive_cwd);
1689 break :blk try resolveWindows(allocator, &.{ drive_cwd, path });1696 break :blk try resolveWindows(gpa, &.{ drive_cwd, path });
1690 },1697 },
1691 };1698 };
1692}1699}
16931700
1694pub fn relativePosix(allocator: Allocator, from: []const u8, to: []const u8) ![]u8 {1701/// Returns the non-absolute path from `from` to `to` according to Windows rules.
1695 if (native_os == .windows) @compileError("this function relies on semantics that do not apply to Windows");1702///
16961703/// Other than memory allocation, this is a pure function; the result solely
1697 const cwd = try process.getCwdAlloc(allocator);1704/// depends on the input parameters.
1698 defer allocator.free(cwd);1705///
1706/// If `from` and `to` each resolve to the same path (after calling `resolve`
1707/// on each), a zero-length string is returned.
1708///
1709pub fn relativePosix(allocator: Allocator, cwd: []const u8, from: []const u8, to: []const u8) Allocator.Error![]u8 {
1699 const resolved_from = try resolvePosix(allocator, &[_][]const u8{ cwd, from });1710 const resolved_from = try resolvePosix(allocator, &[_][]const u8{ cwd, from });
1700 defer allocator.free(resolved_from);1711 defer allocator.free(resolved_from);
1701 const resolved_to = try resolvePosix(allocator, &[_][]const u8{ cwd, to });1712 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) ![]...@@ -1736,69 +1747,67 @@ pub fn relativePosix(allocator: Allocator, from: []const u8, to: []const u8) ![]
1736}1747}
17371748
1738test relative {1749test relative {
1739 if (native_os == .windows) {1750 try testRelativeWindows("c:/blah\\blah", "d:/games", "D:\\games");
1740 try testRelativeWindows("c:/blah\\blah", "d:/games", "D:\\games");1751 try testRelativeWindows("c:/aaaa/bbbb", "c:/aaaa", "..");
1741 try testRelativeWindows("c:/aaaa/bbbb", "c:/aaaa", "..");1752 try testRelativeWindows("c:/aaaa/bbbb", "c:/cccc", "..\\..\\cccc");
1742 try testRelativeWindows("c:/aaaa/bbbb", "c:/cccc", "..\\..\\cccc");1753 try testRelativeWindows("c:/aaaa/bbbb", "C:/aaaa/bbbb", "");
1743 try testRelativeWindows("c:/aaaa/bbbb", "C:/aaaa/bbbb", "");1754 try testRelativeWindows("c:/aaaa/bbbb", "c:/aaaa/cccc", "..\\cccc");
1744 try testRelativeWindows("c:/aaaa/bbbb", "c:/aaaa/cccc", "..\\cccc");1755 try testRelativeWindows("c:/aaaa/", "c:/aaaa/cccc", "cccc");
1745 try testRelativeWindows("c:/aaaa/", "c:/aaaa/cccc", "cccc");1756 try testRelativeWindows("c:/", "c:\\aaaa\\bbbb", "aaaa\\bbbb");
1746 try testRelativeWindows("c:/", "c:\\aaaa\\bbbb", "aaaa\\bbbb");1757 try testRelativeWindows("c:/aaaa/bbbb", "d:\\", "D:\\");
1747 try testRelativeWindows("c:/aaaa/bbbb", "d:\\", "D:\\");1758 try testRelativeWindows("c:/AaAa/bbbb", "c:/aaaa/bbbb", "");
1748 try testRelativeWindows("c:/AaAa/bbbb", "c:/aaaa/bbbb", "");1759 try testRelativeWindows("c:/aaaaa/", "c:/aaaa/cccc", "..\\aaaa\\cccc");
1749 try testRelativeWindows("c:/aaaaa/", "c:/aaaa/cccc", "..\\aaaa\\cccc");1760 try testRelativeWindows("C:\\foo\\bar\\baz\\quux", "C:\\", "..\\..\\..\\..");
1750 try testRelativeWindows("C:\\foo\\bar\\baz\\quux", "C:\\", "..\\..\\..\\..");1761 try testRelativeWindows("C:\\foo\\test", "C:\\foo\\test\\bar\\package.json", "bar\\package.json");
1751 try testRelativeWindows("C:\\foo\\test", "C:\\foo\\test\\bar\\package.json", "bar\\package.json");1762 try testRelativeWindows("C:\\foo\\bar\\baz-quux", "C:\\foo\\bar\\baz", "..\\baz");
1752 try testRelativeWindows("C:\\foo\\bar\\baz-quux", "C:\\foo\\bar\\baz", "..\\baz");1763 try testRelativeWindows("C:\\foo\\bar\\baz", "C:\\foo\\bar\\baz-quux", "..\\baz-quux");
1753 try testRelativeWindows("C:\\foo\\bar\\baz", "C:\\foo\\bar\\baz-quux", "..\\baz-quux");1764 try testRelativeWindows("\\\\foo\\bar", "\\\\foo\\bar\\baz", "baz");
1754 try testRelativeWindows("\\\\foo\\bar", "\\\\foo\\bar\\baz", "baz");1765 try testRelativeWindows("\\\\foo\\bar\\baz", "\\\\foo\\bar", "..");
1755 try testRelativeWindows("\\\\foo\\bar\\baz", "\\\\foo\\bar", "..");1766 try testRelativeWindows("\\\\foo\\bar\\baz-quux", "\\\\foo\\bar\\baz", "..\\baz");
1756 try testRelativeWindows("\\\\foo\\bar\\baz-quux", "\\\\foo\\bar\\baz", "..\\baz");1767 try testRelativeWindows("\\\\foo/bar\\baz-quux", "//foo\\bar/baz", "..\\baz");
1757 try testRelativeWindows("\\\\foo/bar\\baz-quux", "//foo\\bar/baz", "..\\baz");1768 try testRelativeWindows("\\\\foo\\bar\\baz", "\\\\foo\\bar\\baz-quux", "..\\baz-quux");
1758 try testRelativeWindows("\\\\foo\\bar\\baz", "\\\\foo\\bar\\baz-quux", "..\\baz-quux");1769 try testRelativeWindows("C:\\baz-quux", "C:\\baz", "..\\baz");
1759 try testRelativeWindows("C:\\baz-quux", "C:\\baz", "..\\baz");1770 try testRelativeWindows("C:\\baz", "C:\\baz-quux", "..\\baz-quux");
1760 try testRelativeWindows("C:\\baz", "C:\\baz-quux", "..\\baz-quux");1771 try testRelativeWindows("\\\\foo\\baz-quux", "\\\\foo\\baz", "\\\\foo\\baz");
1761 try testRelativeWindows("\\\\foo\\baz-quux", "\\\\foo\\baz", "\\\\foo\\baz");1772 try testRelativeWindows("\\\\foo\\baz", "\\\\foo\\baz-quux", "\\\\foo\\baz-quux");
1762 try testRelativeWindows("\\\\foo\\baz", "\\\\foo\\baz-quux", "\\\\foo\\baz-quux");1773 try testRelativeWindows("C:\\baz", "\\\\foo\\bar\\baz", "\\\\foo\\bar\\baz");
1763 try testRelativeWindows("C:\\baz", "\\\\foo\\bar\\baz", "\\\\foo\\bar\\baz");1774 try testRelativeWindows("\\\\foo\\bar\\baz", "C:\\baz", "C:\\baz");
1764 try testRelativeWindows("\\\\foo\\bar\\baz", "C:\\baz", "C:\\baz");1775
17651776 try testRelativeWindows("c:blah\\blah", "c:foo", "..\\..\\foo");
1766 try testRelativeWindows("c:blah\\blah", "c:foo", "..\\..\\foo");1777 try testRelativeWindows("c:foo", "c:foo\\bar", "bar");
1767 try testRelativeWindows("c:foo", "c:foo\\bar", "bar");1778 try testRelativeWindows("\\blah\\blah", "\\foo", "..\\..\\foo");
1768 try testRelativeWindows("\\blah\\blah", "\\foo", "..\\..\\foo");1779 try testRelativeWindows("\\foo", "\\foo\\bar", "bar");
1769 try testRelativeWindows("\\foo", "\\foo\\bar", "bar");1780
17701781 try testRelativeWindows("a/b/c", "a\\b", "..");
1771 try testRelativeWindows("a/b/c", "a\\b", "..");1782 try testRelativeWindows("a/b/c", "a", "..\\..");
1772 try testRelativeWindows("a/b/c", "a", "..\\..");1783 try testRelativeWindows("a/b/c", "a\\b\\c\\d", "d");
1773 try testRelativeWindows("a/b/c", "a\\b\\c\\d", "d");1784
17741785 try testRelativeWindows("\\\\FOO\\bar\\baz", "\\\\foo\\BAR\\BAZ", "");
1775 try testRelativeWindows("\\\\FOO\\bar\\baz", "\\\\foo\\BAR\\BAZ", "");1786 // Unicode-aware case-insensitive path comparison
1776 // Unicode-aware case-insensitive path comparison1787 try testRelativeWindows("\\\\кириллица\\ελληνικά\\português", "\\\\КИРИЛЛИЦА\\ΕΛΛΗΝΙΚΆ\\PORTUGUÊS", "");
1777 try testRelativeWindows("\\\\кириллица\\ελληνικά\\português", "\\\\КИРИЛЛИЦА\\ΕΛΛΗΝΙΚΆ\\PORTUGUÊS", "");1788
1778 } else {1789 try testRelativePosix("/var/lib", "/var", "..");
1779 try testRelativePosix("/var/lib", "/var", "..");1790 try testRelativePosix("/var/lib", "/bin", "../../bin");
1780 try testRelativePosix("/var/lib", "/bin", "../../bin");1791 try testRelativePosix("/var/lib", "/var/lib", "");
1781 try testRelativePosix("/var/lib", "/var/lib", "");1792 try testRelativePosix("/var/lib", "/var/apache", "../apache");
1782 try testRelativePosix("/var/lib", "/var/apache", "../apache");1793 try testRelativePosix("/var/", "/var/lib", "lib");
1783 try testRelativePosix("/var/", "/var/lib", "lib");1794 try testRelativePosix("/", "/var/lib", "var/lib");
1784 try testRelativePosix("/", "/var/lib", "var/lib");1795 try testRelativePosix("/foo/test", "/foo/test/bar/package.json", "bar/package.json");
1785 try testRelativePosix("/foo/test", "/foo/test/bar/package.json", "bar/package.json");1796 try testRelativePosix("/Users/a/web/b/test/mails", "/Users/a/web/b", "../..");
1786 try testRelativePosix("/Users/a/web/b/test/mails", "/Users/a/web/b", "../..");1797 try testRelativePosix("/foo/bar/baz-quux", "/foo/bar/baz", "../baz");
1787 try testRelativePosix("/foo/bar/baz-quux", "/foo/bar/baz", "../baz");1798 try testRelativePosix("/foo/bar/baz", "/foo/bar/baz-quux", "../baz-quux");
1788 try testRelativePosix("/foo/bar/baz", "/foo/bar/baz-quux", "../baz-quux");1799 try testRelativePosix("/baz-quux", "/baz", "../baz");
1789 try testRelativePosix("/baz-quux", "/baz", "../baz");1800 try testRelativePosix("/baz", "/baz-quux", "../baz-quux");
1790 try testRelativePosix("/baz", "/baz-quux", "../baz-quux");
1791 }
1792}1801}
17931802
1794fn testRelativePosix(from: []const u8, to: []const u8, expected_output: []const u8) !void {1803fn testRelativePosix(from: []const u8, to: []const u8, expected_output: []const u8) !void {
1795 const result = try relativePosix(testing.allocator, from, to);1804 const result = try relativePosix(testing.allocator, ".", from, to);
1796 defer testing.allocator.free(result);1805 defer testing.allocator.free(result);
1797 try testing.expectEqualStrings(expected_output, result);1806 try testing.expectEqualStrings(expected_output, result);
1798}1807}
17991808
1800fn testRelativeWindows(from: []const u8, to: []const u8, expected_output: []const u8) !void {1809fn testRelativeWindows(from: []const u8, to: []const u8, expected_output: []const u8) !void {
1801 const result = try relativeWindows(testing.allocator, from, to);1810 const result = try relativeWindows(testing.allocator, ".", null, from, to);
1802 defer testing.allocator.free(result);1811 defer testing.allocator.free(result);
1803 try testing.expectEqualStrings(expected_output, result);1812 try testing.expectEqualStrings(expected_output, result);
1804}1813}
...@@ -2554,3 +2563,124 @@ pub const fmtAsUtf8Lossy = std.unicode.fmtUtf8;...@@ -2554,3 +2563,124 @@ pub const fmtAsUtf8Lossy = std.unicode.fmtUtf8;
2554/// a lossy conversion if the path contains any unpaired surrogates.2563/// a lossy conversion if the path contains any unpaired surrogates.
2555/// Unpaired surrogates are replaced by the replacement character (U+FFFD).2564/// Unpaired surrogates are replaced by the replacement character (U+FFFD).
2556pub const fmtWtf16LeAsUtf8Lossy = std.unicode.fmtUtf16Le;2565pub const fmtWtf16LeAsUtf8Lossy = std.unicode.fmtUtf16Le;
2566
2567/// Similar to `RTL_PATH_TYPE`, but without the `UNKNOWN` path type.
2568pub const Win32PathType = enum {
2569 /// `\\server\share\foo`
2570 unc_absolute,
2571 /// `C:\foo`
2572 drive_absolute,
2573 /// `C:foo`
2574 drive_relative,
2575 /// `\foo`
2576 rooted,
2577 /// `foo`
2578 relative,
2579 /// `\\.\foo`, `\\?\foo`
2580 local_device,
2581 /// `\\.`, `\\?`
2582 root_local_device,
2583};
2584
2585/// Get the path type of a Win32 namespace path.
2586/// Similar to `RtlDetermineDosPathNameType_U`.
2587/// If `T` is `u16`, then `path` should be encoded as WTF-16LE.
2588pub fn getWin32PathType(comptime T: type, path: []const T) Win32PathType {
2589 if (path.len < 1) return .relative;
2590
2591 const windows_path = std.fs.path.PathType.windows;
2592 if (windows_path.isSep(T, path[0])) {
2593 // \x
2594 if (path.len < 2 or !windows_path.isSep(T, path[1])) return .rooted;
2595 // \\. or \\?
2596 if (path.len > 2 and (path[2] == mem.nativeToLittle(T, '.') or path[2] == mem.nativeToLittle(T, '?'))) {
2597 // exactly \\. or \\? with nothing trailing
2598 if (path.len == 3) return .root_local_device;
2599 // \\.\x or \\?\x
2600 if (windows_path.isSep(T, path[3])) return .local_device;
2601 }
2602 // \\x
2603 return .unc_absolute;
2604 } else {
2605 // Some choice has to be made about how non-ASCII code points as drive-letters are handled, since
2606 // path[0] is a different size for WTF-16 vs WTF-8, leading to a potential mismatch in classification
2607 // for a WTF-8 path and its WTF-16 equivalent. For example, `€:\` encoded in WTF-16 is three code
2608 // units `<0x20AC>:\` whereas `€:\` encoded as WTF-8 is 6 code units `<0xE2><0x82><0xAC>:\` so
2609 // checking path[0], path[1] and path[2] would not behave the same between WTF-8/WTF-16.
2610 //
2611 // `RtlDetermineDosPathNameType_U` exclusively deals with WTF-16 and considers
2612 // `€:\` a drive-absolute path, but code points that take two WTF-16 code units to encode get
2613 // classified as a relative path (e.g. with U+20000 as the drive-letter that'd be encoded
2614 // in WTF-16 as `<0xD840><0xDC00>:\` and be considered a relative path).
2615 //
2616 // The choice made here is to emulate the behavior of `RtlDetermineDosPathNameType_U` for both
2617 // WTF-16 and WTF-8. This is because, while unlikely and not supported by the Disk Manager GUI,
2618 // drive letters are not actually restricted to A-Z. Using `SetVolumeMountPointW` will allow you
2619 // to set any byte value as a drive letter, and going through `IOCTL_MOUNTMGR_CREATE_POINT` will
2620 // allow you to set any WTF-16 code unit as a drive letter.
2621 //
2622 // Non-A-Z drive letters don't interact well with most of Windows, but certain things do work, e.g.
2623 // `cd /D €:\` will work, filesystem functions still work, etc.
2624 //
2625 // The unfortunate part of this is that this makes handling WTF-8 more complicated as we can't
2626 // just check path[0], path[1], path[2].
2627 const colon_i: usize = switch (T) {
2628 u8 => i: {
2629 const code_point_len = std.unicode.utf8ByteSequenceLength(path[0]) catch return .relative;
2630 // Conveniently, 4-byte sequences in WTF-8 have the same starting code point
2631 // as 2-code-unit sequences in WTF-16.
2632 if (code_point_len > 3) return .relative;
2633 break :i code_point_len;
2634 },
2635 u16 => 1,
2636 else => @compileError("unsupported type: " ++ @typeName(T)),
2637 };
2638 // x
2639 if (path.len < colon_i + 1 or path[colon_i] != mem.nativeToLittle(T, ':')) return .relative;
2640 // x:\
2641 if (path.len > colon_i + 1 and windows_path.isSep(T, path[colon_i + 1])) return .drive_absolute;
2642 // x:
2643 return .drive_relative;
2644 }
2645}
2646
2647test getWin32PathType {
2648 try std.testing.expectEqual(.relative, getWin32PathType(u8, ""));
2649 try std.testing.expectEqual(.relative, getWin32PathType(u8, "x"));
2650 try std.testing.expectEqual(.relative, getWin32PathType(u8, "x\\"));
2651
2652 try std.testing.expectEqual(.root_local_device, getWin32PathType(u8, "//."));
2653 try std.testing.expectEqual(.root_local_device, getWin32PathType(u8, "/\\?"));
2654 try std.testing.expectEqual(.root_local_device, getWin32PathType(u8, "\\\\?"));
2655
2656 try std.testing.expectEqual(.local_device, getWin32PathType(u8, "//./x"));
2657 try std.testing.expectEqual(.local_device, getWin32PathType(u8, "/\\?\\x"));
2658 try std.testing.expectEqual(.local_device, getWin32PathType(u8, "\\\\?\\x"));
2659 // local device paths require a path separator after the root, otherwise it is considered a UNC path
2660 try std.testing.expectEqual(.unc_absolute, getWin32PathType(u8, "\\\\?x"));
2661 try std.testing.expectEqual(.unc_absolute, getWin32PathType(u8, "//.x"));
2662
2663 try std.testing.expectEqual(.unc_absolute, getWin32PathType(u8, "//"));
2664 try std.testing.expectEqual(.unc_absolute, getWin32PathType(u8, "\\\\x"));
2665 try std.testing.expectEqual(.unc_absolute, getWin32PathType(u8, "//x"));
2666
2667 try std.testing.expectEqual(.rooted, getWin32PathType(u8, "\\x"));
2668 try std.testing.expectEqual(.rooted, getWin32PathType(u8, "/"));
2669
2670 try std.testing.expectEqual(.drive_relative, getWin32PathType(u8, "x:"));
2671 try std.testing.expectEqual(.drive_relative, getWin32PathType(u8, "x:abc"));
2672 try std.testing.expectEqual(.drive_relative, getWin32PathType(u8, "x:a/b/c"));
2673
2674 try std.testing.expectEqual(.drive_absolute, getWin32PathType(u8, "x:\\"));
2675 try std.testing.expectEqual(.drive_absolute, getWin32PathType(u8, "x:\\abc"));
2676 try std.testing.expectEqual(.drive_absolute, getWin32PathType(u8, "x:/a/b/c"));
2677
2678 // Non-ASCII code point that is encoded as one WTF-16 code unit is considered a valid drive letter
2679 try std.testing.expectEqual(.drive_absolute, getWin32PathType(u8, "€:\\"));
2680 try std.testing.expectEqual(.drive_absolute, getWin32PathType(u16, std.unicode.wtf8ToWtf16LeStringLiteral("€:\\")));
2681 try std.testing.expectEqual(.drive_relative, getWin32PathType(u8, "€:"));
2682 try std.testing.expectEqual(.drive_relative, getWin32PathType(u16, std.unicode.wtf8ToWtf16LeStringLiteral("€:")));
2683 // But code points that are encoded as two WTF-16 code units are not
2684 try std.testing.expectEqual(.relative, getWin32PathType(u8, "\u{10000}:\\"));
2685 try std.testing.expectEqual(.relative, getWin32PathType(u16, std.unicode.wtf8ToWtf16LeStringLiteral("\u{10000}:\\")));
2686}
lib/std/fs/test.zig+1-1
...@@ -79,7 +79,7 @@ const PathType = enum {...@@ -79,7 +79,7 @@ const PathType = enum {
79 // using '127.0.0.1' as the server name and '<drive letter>$' as the share name.79 // using '127.0.0.1' as the server name and '<drive letter>$' as the share name.
80 var fd_path_buf: [Dir.max_path_bytes]u8 = undefined;80 var fd_path_buf: [Dir.max_path_bytes]u8 = undefined;
81 const dir_path = fd_path_buf[0..try dir.realPath(io, &fd_path_buf)];81 const dir_path = fd_path_buf[0..try dir.realPath(io, &fd_path_buf)];
82 const windows_path_type = windows.getWin32PathType(u8, dir_path);82 const windows_path_type = Dir.path.getWin32PathType(u8, dir_path);
83 switch (windows_path_type) {83 switch (windows_path_type) {
84 .unc_absolute => return Dir.path.joinZ(allocator, &.{ dir_path, relative_path }),84 .unc_absolute => return Dir.path.joinZ(allocator, &.{ dir_path, relative_path }),
85 .drive_absolute => {85 .drive_absolute => {
lib/std/hash/benchmark.zig+6-5
...@@ -353,14 +353,15 @@ fn mode(comptime x: comptime_int) comptime_int {...@@ -353,14 +353,15 @@ fn mode(comptime x: comptime_int) comptime_int {
353 return if (builtin.mode == .Debug) x / 64 else x;353 return if (builtin.mode == .Debug) x / 64 else x;
354}354}
355355
356pub fn main() !void {356pub fn main(init: std.process.Init) !void {
357 const io = init.io;
358 const arena = init.arena.allocator();
359
357 var stdout_buffer: [0x100]u8 = undefined;360 var stdout_buffer: [0x100]u8 = undefined;
358 var stdout_writer = Io.File.stdout().writer(&stdout_buffer);361 var stdout_writer = Io.File.stdout().writer(io, &stdout_buffer);
359 const stdout = &stdout_writer.interface;362 const stdout = &stdout_writer.interface;
360363
361 var buffer: [1024]u8 = undefined;364 const args = try init.minimal.args.toSlice(arena);
362 var fixed = std.heap.FixedBufferAllocator.init(buffer[0..]);
363 const args = try std.process.argsAlloc(fixed.allocator());
364365
365 var filter: ?[]u8 = "";366 var filter: ?[]u8 = "";
366 var count: usize = mode(128 * MiB);367 var count: usize = mode(128 * MiB);
lib/std/heap/debug_allocator.zig+5-2
...@@ -1272,9 +1272,12 @@ test "shrink large object to large object" {...@@ -1272,9 +1272,12 @@ test "shrink large object to large object" {
1272}1272}
12731273
1274test "shrink large object to large object with larger alignment" {1274test "shrink large object to large object with larger alignment" {
1275 if (!builtin.link_libc and builtin.os.tag == .wasi) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/227311275 if (builtin.os.tag == .wasi) {
1276 // https://github.com/ziglang/zig/issues/22731
1277 return error.SkipZigTest;
1278 }
12761279
1277 var gpa = DebugAllocator(test_config){};1280 var gpa: DebugAllocator(test_config) = .{};
1278 defer std.testing.expect(gpa.deinit() == .ok) catch @panic("leak");1281 defer std.testing.expect(gpa.deinit() == .ok) catch @panic("leak");
1279 const allocator = gpa.allocator();1282 const allocator = gpa.allocator();
12801283
lib/std/http/Client.zig+9-10
...@@ -1307,7 +1307,7 @@ pub fn deinit(client: *Client) void {...@@ -1307,7 +1307,7 @@ pub fn deinit(client: *Client) void {
1307/// Asserts the client has no active connections.1307/// Asserts the client has no active connections.
1308/// Uses `arena` for a few small allocations that must outlive the client, or1308/// Uses `arena` for a few small allocations that must outlive the client, or
1309/// at least until those fields are set to different values.1309/// at least until those fields are set to different values.
1310pub fn initDefaultProxies(client: *Client, arena: Allocator) !void {1310pub fn initDefaultProxies(client: *Client, arena: Allocator, environ_map: *std.process.Environ.Map) !void {
1311 // Prevent any new connections from being created.1311 // Prevent any new connections from being created.
1312 client.connection_pool.mutex.lock();1312 client.connection_pool.mutex.lock();
1313 defer client.connection_pool.mutex.unlock();1313 defer client.connection_pool.mutex.unlock();
...@@ -1315,27 +1315,26 @@ pub fn initDefaultProxies(client: *Client, arena: Allocator) !void {...@@ -1315,27 +1315,26 @@ pub fn initDefaultProxies(client: *Client, arena: Allocator) !void {
1315 assert(client.connection_pool.used.first == null); // There are active requests.1315 assert(client.connection_pool.used.first == null); // There are active requests.
13161316
1317 if (client.http_proxy == null) {1317 if (client.http_proxy == null) {
1318 client.http_proxy = try createProxyFromEnvVar(arena, &.{1318 client.http_proxy = try createProxyFromEnvVar(arena, environ_map, &.{
1319 "http_proxy", "HTTP_PROXY", "all_proxy", "ALL_PROXY",1319 "http_proxy", "HTTP_PROXY", "all_proxy", "ALL_PROXY",
1320 });1320 });
1321 }1321 }
13221322
1323 if (client.https_proxy == null) {1323 if (client.https_proxy == null) {
1324 client.https_proxy = try createProxyFromEnvVar(arena, &.{1324 client.https_proxy = try createProxyFromEnvVar(arena, environ_map, &.{
1325 "https_proxy", "HTTPS_PROXY", "all_proxy", "ALL_PROXY",1325 "https_proxy", "HTTPS_PROXY", "all_proxy", "ALL_PROXY",
1326 });1326 });
1327 }1327 }
1328}1328}
13291329
1330fn createProxyFromEnvVar(arena: Allocator, env_var_names: []const []const u8) !?*Proxy {1330fn createProxyFromEnvVar(
1331 arena: Allocator,
1332 environ_map: *std.process.Environ.Map,
1333 env_var_names: []const []const u8,
1334) !?*Proxy {
1331 const content = for (env_var_names) |name| {1335 const content = for (env_var_names) |name| {
1332 const content = std.process.getEnvVarOwned(arena, name) catch |err| switch (err) {1336 const content = environ_map.get(name) orelse continue;
1333 error.EnvironmentVariableNotFound => continue,
1334 else => |e| return e,
1335 };
1336
1337 if (content.len == 0) continue;1337 if (content.len == 0) continue;
1338
1339 break content;1338 break content;
1340 } else return null;1339 } else return null;
13411340
lib/std/os.zig+1-64
...@@ -1,27 +1,4 @@...@@ -1,27 +1,4 @@
1//! This file contains thin wrappers around OS-specific APIs, with these
2//! specific goals in mind:
3//! * Convert "errno"-style error codes into Zig errors.
4//! * When null-terminated byte buffers are required, provide APIs which accept
5//! slices as well as APIs which accept null-terminated byte buffers. Same goes
6//! for WTF-16LE encoding.
7//! * Where operating systems share APIs, e.g. POSIX, these thin wrappers provide
8//! cross platform abstracting.
9//! * When there exists a corresponding libc function and linking libc, the libc
10//! implementation is used. Exceptions are made for known buggy areas of libc.
11//! On Linux libc can be side-stepped by using `std.os.linux` directly.
12//! * For Windows, this file represents the API that libc would provide for
13//! Windows. For thin wrappers around Windows-specific APIs, see `std.os.windows`.
14
15const root = @import("root");
16const std = @import("std.zig");
17const builtin = @import("builtin");1const builtin = @import("builtin");
18const assert = std.debug.assert;
19const math = std.math;
20const mem = std.mem;
21const elf = std.elf;
22const fs = std.fs;
23const dl = @import("dynamic_library.zig");
24const posix = std.posix;
25const native_os = builtin.os.tag;2const native_os = builtin.os.tag;
263
27pub const linux = @import("os/linux.zig");4pub const linux = @import("os/linux.zig");
...@@ -33,47 +10,7 @@ pub const windows = @import("os/windows.zig");...@@ -33,47 +10,7 @@ pub const windows = @import("os/windows.zig");
3310
34test {11test {
35 _ = linux;12 _ = linux;
36 if (native_os == .uefi) {13 if (native_os == .uefi) _ = uefi;
37 _ = uefi;
38 }
39 _ = wasi;14 _ = wasi;
40 _ = windows;15 _ = windows;
41}16}
42
43/// See also `getenv`. Populated by startup code before main().
44/// TODO this is a footgun because the value will be undefined when using `zig build-lib`.
45/// https://github.com/ziglang/zig/issues/4524
46pub var environ: [][*:0]u8 = undefined;
47
48/// Populated by startup code before main().
49/// Not available on WASI or Windows without libc. See `std.process.argsAlloc`
50/// or `std.process.argsWithAllocator` for a cross-platform alternative.
51pub var argv: [][*:0]u8 = if (builtin.link_libc) undefined else switch (native_os) {
52 .windows => @compileError("argv isn't supported on Windows: use std.process.argsAlloc instead"),
53 .wasi => @compileError("argv isn't supported on WASI: use std.process.argsAlloc instead"),
54 else => undefined,
55};
56
57pub const FstatError = error{
58 SystemResources,
59 AccessDenied,
60 Unexpected,
61};
62
63pub fn fstat_wasi(fd: posix.fd_t) FstatError!wasi.filestat_t {
64 var stat: wasi.filestat_t = undefined;
65 switch (wasi.fd_filestat_get(fd, &stat)) {
66 .SUCCESS => return stat,
67 .INVAL => unreachable,
68 .BADF => unreachable, // Always a race condition.
69 .NOMEM => return error.SystemResources,
70 .ACCES => return error.AccessDenied,
71 .NOTCAPABLE => return error.AccessDenied,
72 else => |err| return posix.unexpectedErrno(err),
73 }
74}
75
76pub fn defaultWasiCwd() std.os.wasi.fd_t {
77 // Expect the first preopen to be current working directory.
78 return 3;
79}
lib/std/os/emscripten.zig+3-3
...@@ -224,14 +224,14 @@ pub const W = struct {...@@ -224,14 +224,14 @@ pub const W = struct {
224 pub fn EXITSTATUS(s: u32) u8 {224 pub fn EXITSTATUS(s: u32) u8 {
225 return @as(u8, @intCast((s & 0xff00) >> 8));225 return @as(u8, @intCast((s & 0xff00) >> 8));
226 }226 }
227 pub fn TERMSIG(s: u32) u32 {227 pub fn TERMSIG(s: u32) SIG {
228 return s & 0x7f;228 return @enumFromInt(s & 0x7f);
229 }229 }
230 pub fn STOPSIG(s: u32) u32 {230 pub fn STOPSIG(s: u32) u32 {
231 return EXITSTATUS(s);231 return EXITSTATUS(s);
232 }232 }
233 pub fn IFEXITED(s: u32) bool {233 pub fn IFEXITED(s: u32) bool {
234 return TERMSIG(s) == 0;234 return (s & 0x7f) == 0;
235 }235 }
236 pub fn IFSTOPPED(s: u32) bool {236 pub fn IFSTOPPED(s: u32) bool {
237 return @as(u16, @truncate(((s & 0xffff) *% 0x10001) >> 8)) > 0x7f00;237 return @as(u16, @truncate(((s & 0xffff) *% 0x10001) >> 8)) > 0x7f00;
lib/std/os/linux.zig+22-5
...@@ -1598,8 +1598,15 @@ pub fn wait4(pid: pid_t, status: *u32, flags: u32, usage: ?*rusage) usize {...@@ -1598,8 +1598,15 @@ pub fn wait4(pid: pid_t, status: *u32, flags: u32, usage: ?*rusage) usize {
1598 );1598 );
1599}1599}
16001600
1601pub fn waitid(id_type: P, id: i32, infop: *siginfo_t, flags: u32) usize {1601pub fn waitid(id_type: P, id: i32, infop: *siginfo_t, flags: u32, usage: ?*rusage) usize {
1602 return syscall5(.waitid, @intFromEnum(id_type), @as(usize, @bitCast(@as(isize, id))), @intFromPtr(infop), flags, 0);1602 return syscall5(
1603 .waitid,
1604 @intFromEnum(id_type),
1605 @as(usize, @bitCast(@as(isize, id))),
1606 @intFromPtr(infop),
1607 flags,
1608 @intFromPtr(usage),
1609 );
1603}1610}
16041611
1605pub const F = struct {1612pub const F = struct {
...@@ -3616,14 +3623,14 @@ pub const W = struct {...@@ -3616,14 +3623,14 @@ pub const W = struct {
3616 pub fn EXITSTATUS(s: u32) u8 {3623 pub fn EXITSTATUS(s: u32) u8 {
3617 return @as(u8, @intCast((s & 0xff00) >> 8));3624 return @as(u8, @intCast((s & 0xff00) >> 8));
3618 }3625 }
3619 pub fn TERMSIG(s: u32) u32 {3626 pub fn TERMSIG(s: u32) SIG {
3620 return s & 0x7f;3627 return @enumFromInt(s & 0x7f);
3621 }3628 }
3622 pub fn STOPSIG(s: u32) u32 {3629 pub fn STOPSIG(s: u32) u32 {
3623 return EXITSTATUS(s);3630 return EXITSTATUS(s);
3624 }3631 }
3625 pub fn IFEXITED(s: u32) bool {3632 pub fn IFEXITED(s: u32) bool {
3626 return TERMSIG(s) == 0;3633 return (s & 0x7f) == 0;
3627 }3634 }
3628 pub fn IFSTOPPED(s: u32) bool {3635 pub fn IFSTOPPED(s: u32) bool {
3629 return @as(u16, @truncate(((s & 0xffff) *% 0x10001) >> 8)) > 0x7f00;3636 return @as(u16, @truncate(((s & 0xffff) *% 0x10001) >> 8)) > 0x7f00;
...@@ -6205,6 +6212,16 @@ const siginfo_fields_union = extern union {...@@ -6205,6 +6212,16 @@ const siginfo_fields_union = extern union {
6205 },6212 },
6206};6213};
62076214
6215pub const CLD = enum(i32) {
6216 EXITED = 1,
6217 KILLED = 2,
6218 DUMPED = 3,
6219 TRAPPED = 4,
6220 STOPPED = 5,
6221 CONTINUED = 6,
6222 _,
6223};
6224
6208pub const siginfo_t = if (is_mips)6225pub const siginfo_t = if (is_mips)
6209 extern struct {6226 extern struct {
6210 signo: SIG,6227 signo: SIG,
lib/std/os/linux/IoUring/test.zig+1-1
...@@ -280,7 +280,7 @@ test "splice/read" {...@@ -280,7 +280,7 @@ test "splice/read" {
280 var buffer_read = [_]u8{98} ** 20;280 var buffer_read = [_]u8{98} ** 20;
281 try file_src.writeStreamingAll(io, &buffer_write);281 try file_src.writeStreamingAll(io, &buffer_write);
282282
283 const fds = try posix.pipe();283 const fds = try std.Io.Threaded.pipe2(.{});
284 const pipe_offset: u64 = std.math.maxInt(u64);284 const pipe_offset: u64 = std.math.maxInt(u64);
285285
286 const sqe_splice_to_pipe = try ring.splice(0x11111111, fd_src, 0, fds[1], pipe_offset, buffer_write.len);286 const sqe_splice_to_pipe = try ring.splice(0x11111111, fd_src, 0, fds[1], pipe_offset, buffer_write.len);
lib/std/os/linux/syscalls.zig+1-2
...@@ -1,5 +1,4 @@...@@ -1,5 +1,4 @@
1// This file is automatically generated, DO NOT edit it manually.1// This file is automatically generated by tools/generate_linux_syscalls.zig
2// See tools/generate_linux_syscalls.zig for more info.
3// This list current as of kernel: 6.18.22// This list current as of kernel: 6.18.2
43
5pub const X86 = enum(usize) {4pub const X86 = enum(usize) {
lib/std/os/windows.zig+9-141
...@@ -2927,7 +2927,7 @@ pub fn CreateSymbolicLink(...@@ -2927,7 +2927,7 @@ pub fn CreateSymbolicLink(
2927 // Already an NT path, no need to do anything to it2927 // Already an NT path, no need to do anything to it
2928 break :target_path target_path;2928 break :target_path target_path;
2929 } else {2929 } else {
2930 switch (getWin32PathType(u16, target_path)) {2930 switch (std.fs.path.getWin32PathType(u16, target_path)) {
2931 // Rooted paths need to avoid getting put through wToPrefixedFileW2931 // Rooted paths need to avoid getting put through wToPrefixedFileW
2932 // (and they are treated as relative in this context)2932 // (and they are treated as relative in this context)
2933 // Note: It seems that rooted paths in symbolic links are relative to2933 // 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...@@ -3756,17 +3756,6 @@ pub fn GetModuleFileNameW(hModule: ?HMODULE, buf_ptr: [*]u16, buf_len: DWORD) Ge
3756 return buf_ptr[0..rc :0];3756 return buf_ptr[0..rc :0];
3757}3757}
37583758
3759pub const TerminateProcessError = error{ AccessDenied, Unexpected };
3760
3761pub fn TerminateProcess(hProcess: HANDLE, uExitCode: UINT) TerminateProcessError!void {
3762 if (kernel32.TerminateProcess(hProcess, uExitCode) == 0) {
3763 switch (GetLastError()) {
3764 Win32Error.ACCESS_DENIED => return error.AccessDenied,
3765 else => |err| return unexpectedError(err),
3766 }
3767 }
3768}
3769
3770pub const NtAllocateVirtualMemoryError = error{3759pub const NtAllocateVirtualMemoryError = error{
3771 AccessDenied,3760 AccessDenied,
3772 InvalidParameter,3761 InvalidParameter,
...@@ -3919,7 +3908,7 @@ pub fn CreateProcessW(...@@ -3919,7 +3908,7 @@ pub fn CreateProcessW(
3919 lpThreadAttributes: ?*SECURITY_ATTRIBUTES,3908 lpThreadAttributes: ?*SECURITY_ATTRIBUTES,
3920 bInheritHandles: BOOL,3909 bInheritHandles: BOOL,
3921 dwCreationFlags: CreateProcessFlags,3910 dwCreationFlags: CreateProcessFlags,
3922 lpEnvironment: ?*anyopaque,3911 lpEnvironment: ?[*:0]u16,
3923 lpCurrentDirectory: ?LPCWSTR,3912 lpCurrentDirectory: ?LPCWSTR,
3924 lpStartupInfo: *STARTUPINFOW,3913 lpStartupInfo: *STARTUPINFOW,
3925 lpProcessInformation: *PROCESS_INFORMATION,3914 lpProcessInformation: *PROCESS_INFORMATION,
...@@ -4235,7 +4224,7 @@ pub const RemoveDotDirsError = error{TooManyParentDirs};...@@ -4235,7 +4224,7 @@ pub const RemoveDotDirsError = error{TooManyParentDirs};
4235/// 2) all repeating back slashes have been collapsed4224/// 2) all repeating back slashes have been collapsed
4236/// 3) the path is a relative one (does not start with a back slash)4225/// 3) the path is a relative one (does not start with a back slash)
4237pub fn removeDotDirsSanitized(comptime T: type, path: []T) RemoveDotDirsError!usize {4226pub fn removeDotDirsSanitized(comptime T: type, path: []T) RemoveDotDirsError!usize {
4238 std.debug.assert(path.len == 0 or path[0] != '\\');4227 assert(path.len == 0 or path[0] != '\\');
42394228
4240 var write_idx: usize = 0;4229 var write_idx: usize = 0;
4241 var read_idx: usize = 0;4230 var read_idx: usize = 0;
...@@ -4251,7 +4240,7 @@ pub fn removeDotDirsSanitized(comptime T: type, path: []T) RemoveDotDirsError!us...@@ -4251,7 +4240,7 @@ pub fn removeDotDirsSanitized(comptime T: type, path: []T) RemoveDotDirsError!us
4251 }4240 }
4252 if (after_dot == '.' and (read_idx + 2 == path.len or path[read_idx + 2] == '\\')) {4241 if (after_dot == '.' and (read_idx + 2 == path.len or path[read_idx + 2] == '\\')) {
4253 if (write_idx == 0) return error.TooManyParentDirs;4242 if (write_idx == 0) return error.TooManyParentDirs;
4254 std.debug.assert(write_idx >= 2);4243 assert(write_idx >= 2);
4255 write_idx -= 1;4244 write_idx -= 1;
4256 while (true) {4245 while (true) {
4257 write_idx -= 1;4246 write_idx -= 1;
...@@ -4353,7 +4342,7 @@ pub fn wToPrefixedFileW(dir: ?HANDLE, path: [:0]const u16) Wtf16ToPrefixedFileWE...@@ -4353,7 +4342,7 @@ pub fn wToPrefixedFileW(dir: ?HANDLE, path: [:0]const u16) Wtf16ToPrefixedFileWE
4353 path_space.data[path_space.len] = 0;4342 path_space.data[path_space.len] = 0;
4354 return path_space;4343 return path_space;
4355 } else {4344 } else {
4356 const path_type = getWin32PathType(u16, path);4345 const path_type = std.fs.path.getWin32PathType(u16, path);
4357 var path_space: PathSpace = undefined;4346 var path_space: PathSpace = undefined;
4358 if (path_type == .local_device) {4347 if (path_type == .local_device) {
4359 switch (getLocalDevicePathType(u16, path)) {4348 switch (getLocalDevicePathType(u16, path)) {
...@@ -4491,8 +4480,8 @@ pub fn wToPrefixedFileW(dir: ?HANDLE, path: [:0]const u16) Wtf16ToPrefixedFileWE...@@ -4491,8 +4480,8 @@ pub fn wToPrefixedFileW(dir: ?HANDLE, path: [:0]const u16) Wtf16ToPrefixedFileWE
4491 if (path_type == .unc_absolute) {4480 if (path_type == .unc_absolute) {
4492 // Now add in the UNC, the `C` should overwrite the first `\` of the4481 // Now add in the UNC, the `C` should overwrite the first `\` of the
4493 // FullPathName, ultimately resulting in `\??\UNC\<the rest of the path>`4482 // FullPathName, ultimately resulting in `\??\UNC\<the rest of the path>`
4494 std.debug.assert(path_space.data[path_buf_offset] == '\\');4483 assert(path_space.data[path_buf_offset] == '\\');
4495 std.debug.assert(path_space.data[path_buf_offset + 1] == '\\');4484 assert(path_space.data[path_buf_offset + 1] == '\\');
4496 const unc = [_]u16{ 'U', 'N', 'C' };4485 const unc = [_]u16{ 'U', 'N', 'C' };
4497 path_space.data[nt_prefix.len..][0..unc.len].* = unc;4486 path_space.data[nt_prefix.len..][0..unc.len].* = unc;
4498 }4487 }
...@@ -4500,127 +4489,6 @@ pub fn wToPrefixedFileW(dir: ?HANDLE, path: [:0]const u16) Wtf16ToPrefixedFileWE...@@ -4500,127 +4489,6 @@ pub fn wToPrefixedFileW(dir: ?HANDLE, path: [:0]const u16) Wtf16ToPrefixedFileWE
4500 }4489 }
4501}4490}
45024491
4503/// Similar to `RTL_PATH_TYPE`, but without the `UNKNOWN` path type.
4504pub const Win32PathType = enum {
4505 /// `\\server\share\foo`
4506 unc_absolute,
4507 /// `C:\foo`
4508 drive_absolute,
4509 /// `C:foo`
4510 drive_relative,
4511 /// `\foo`
4512 rooted,
4513 /// `foo`
4514 relative,
4515 /// `\\.\foo`, `\\?\foo`
4516 local_device,
4517 /// `\\.`, `\\?`
4518 root_local_device,
4519};
4520
4521/// Get the path type of a Win32 namespace path.
4522/// Similar to `RtlDetermineDosPathNameType_U`.
4523/// If `T` is `u16`, then `path` should be encoded as WTF-16LE.
4524pub fn getWin32PathType(comptime T: type, path: []const T) Win32PathType {
4525 if (path.len < 1) return .relative;
4526
4527 const windows_path = std.fs.path.PathType.windows;
4528 if (windows_path.isSep(T, path[0])) {
4529 // \x
4530 if (path.len < 2 or !windows_path.isSep(T, path[1])) return .rooted;
4531 // \\. or \\?
4532 if (path.len > 2 and (path[2] == mem.nativeToLittle(T, '.') or path[2] == mem.nativeToLittle(T, '?'))) {
4533 // exactly \\. or \\? with nothing trailing
4534 if (path.len == 3) return .root_local_device;
4535 // \\.\x or \\?\x
4536 if (windows_path.isSep(T, path[3])) return .local_device;
4537 }
4538 // \\x
4539 return .unc_absolute;
4540 } else {
4541 // Some choice has to be made about how non-ASCII code points as drive-letters are handled, since
4542 // path[0] is a different size for WTF-16 vs WTF-8, leading to a potential mismatch in classification
4543 // for a WTF-8 path and its WTF-16 equivalent. For example, `€:\` encoded in WTF-16 is three code
4544 // units `<0x20AC>:\` whereas `€:\` encoded as WTF-8 is 6 code units `<0xE2><0x82><0xAC>:\` so
4545 // checking path[0], path[1] and path[2] would not behave the same between WTF-8/WTF-16.
4546 //
4547 // `RtlDetermineDosPathNameType_U` exclusively deals with WTF-16 and considers
4548 // `€:\` a drive-absolute path, but code points that take two WTF-16 code units to encode get
4549 // classified as a relative path (e.g. with U+20000 as the drive-letter that'd be encoded
4550 // in WTF-16 as `<0xD840><0xDC00>:\` and be considered a relative path).
4551 //
4552 // The choice made here is to emulate the behavior of `RtlDetermineDosPathNameType_U` for both
4553 // WTF-16 and WTF-8. This is because, while unlikely and not supported by the Disk Manager GUI,
4554 // drive letters are not actually restricted to A-Z. Using `SetVolumeMountPointW` will allow you
4555 // to set any byte value as a drive letter, and going through `IOCTL_MOUNTMGR_CREATE_POINT` will
4556 // allow you to set any WTF-16 code unit as a drive letter.
4557 //
4558 // Non-A-Z drive letters don't interact well with most of Windows, but certain things do work, e.g.
4559 // `cd /D €:\` will work, filesystem functions still work, etc.
4560 //
4561 // The unfortunate part of this is that this makes handling WTF-8 more complicated as we can't
4562 // just check path[0], path[1], path[2].
4563 const colon_i: usize = switch (T) {
4564 u8 => i: {
4565 const code_point_len = std.unicode.utf8ByteSequenceLength(path[0]) catch return .relative;
4566 // Conveniently, 4-byte sequences in WTF-8 have the same starting code point
4567 // as 2-code-unit sequences in WTF-16.
4568 if (code_point_len > 3) return .relative;
4569 break :i code_point_len;
4570 },
4571 u16 => 1,
4572 else => @compileError("unsupported type: " ++ @typeName(T)),
4573 };
4574 // x
4575 if (path.len < colon_i + 1 or path[colon_i] != mem.nativeToLittle(T, ':')) return .relative;
4576 // x:\
4577 if (path.len > colon_i + 1 and windows_path.isSep(T, path[colon_i + 1])) return .drive_absolute;
4578 // x:
4579 return .drive_relative;
4580 }
4581}
4582
4583test getWin32PathType {
4584 try std.testing.expectEqual(.relative, getWin32PathType(u8, ""));
4585 try std.testing.expectEqual(.relative, getWin32PathType(u8, "x"));
4586 try std.testing.expectEqual(.relative, getWin32PathType(u8, "x\\"));
4587
4588 try std.testing.expectEqual(.root_local_device, getWin32PathType(u8, "//."));
4589 try std.testing.expectEqual(.root_local_device, getWin32PathType(u8, "/\\?"));
4590 try std.testing.expectEqual(.root_local_device, getWin32PathType(u8, "\\\\?"));
4591
4592 try std.testing.expectEqual(.local_device, getWin32PathType(u8, "//./x"));
4593 try std.testing.expectEqual(.local_device, getWin32PathType(u8, "/\\?\\x"));
4594 try std.testing.expectEqual(.local_device, getWin32PathType(u8, "\\\\?\\x"));
4595 // local device paths require a path separator after the root, otherwise it is considered a UNC path
4596 try std.testing.expectEqual(.unc_absolute, getWin32PathType(u8, "\\\\?x"));
4597 try std.testing.expectEqual(.unc_absolute, getWin32PathType(u8, "//.x"));
4598
4599 try std.testing.expectEqual(.unc_absolute, getWin32PathType(u8, "//"));
4600 try std.testing.expectEqual(.unc_absolute, getWin32PathType(u8, "\\\\x"));
4601 try std.testing.expectEqual(.unc_absolute, getWin32PathType(u8, "//x"));
4602
4603 try std.testing.expectEqual(.rooted, getWin32PathType(u8, "\\x"));
4604 try std.testing.expectEqual(.rooted, getWin32PathType(u8, "/"));
4605
4606 try std.testing.expectEqual(.drive_relative, getWin32PathType(u8, "x:"));
4607 try std.testing.expectEqual(.drive_relative, getWin32PathType(u8, "x:abc"));
4608 try std.testing.expectEqual(.drive_relative, getWin32PathType(u8, "x:a/b/c"));
4609
4610 try std.testing.expectEqual(.drive_absolute, getWin32PathType(u8, "x:\\"));
4611 try std.testing.expectEqual(.drive_absolute, getWin32PathType(u8, "x:\\abc"));
4612 try std.testing.expectEqual(.drive_absolute, getWin32PathType(u8, "x:/a/b/c"));
4613
4614 // Non-ASCII code point that is encoded as one WTF-16 code unit is considered a valid drive letter
4615 try std.testing.expectEqual(.drive_absolute, getWin32PathType(u8, "€:\\"));
4616 try std.testing.expectEqual(.drive_absolute, getWin32PathType(u16, std.unicode.wtf8ToWtf16LeStringLiteral("€:\\")));
4617 try std.testing.expectEqual(.drive_relative, getWin32PathType(u8, "€:"));
4618 try std.testing.expectEqual(.drive_relative, getWin32PathType(u16, std.unicode.wtf8ToWtf16LeStringLiteral("€:")));
4619 // But code points that are encoded as two WTF-16 code units are not
4620 try std.testing.expectEqual(.relative, getWin32PathType(u8, "\u{10000}:\\"));
4621 try std.testing.expectEqual(.relative, getWin32PathType(u16, std.unicode.wtf8ToWtf16LeStringLiteral("\u{10000}:\\")));
4622}
4623
4624/// Returns true if the path starts with `\??\`, which is indicative of an NT path4492/// Returns true if the path starts with `\??\`, which is indicative of an NT path
4625/// but is not enough to fully distinguish between NT paths and Win32 paths, as4493/// but is not enough to fully distinguish between NT paths and Win32 paths, as
4626/// `\??\` is not actually a distinct prefix but rather the path to a special virtual4494/// `\??\` is not actually a distinct prefix but rather the path to a special virtual
...@@ -4660,10 +4528,10 @@ const LocalDevicePathType = enum {...@@ -4660,10 +4528,10 @@ const LocalDevicePathType = enum {
4660};4528};
46614529
4662/// Only relevant for Win32 -> NT path conversion.4530/// Only relevant for Win32 -> NT path conversion.
4663/// Asserts `path` is of type `Win32PathType.local_device`.4531/// Asserts `path` is of type `std.fs.path.Win32PathType.local_device`.
4664fn getLocalDevicePathType(comptime T: type, path: []const T) LocalDevicePathType {4532fn getLocalDevicePathType(comptime T: type, path: []const T) LocalDevicePathType {
4665 if (std.debug.runtime_safety) {4533 if (std.debug.runtime_safety) {
4666 std.debug.assert(getWin32PathType(T, path) == .local_device);4534 assert(std.fs.path.getWin32PathType(T, path) == .local_device);
4667 }4535 }
46684536
4669 const backslash = mem.nativeToLittle(T, '\\');4537 const backslash = mem.nativeToLittle(T, '\\');
lib/std/os/windows/kernel32.zig+1-1
...@@ -265,7 +265,7 @@ pub extern "kernel32" fn CreateProcessW(...@@ -265,7 +265,7 @@ pub extern "kernel32" fn CreateProcessW(
265 lpThreadAttributes: ?*SECURITY_ATTRIBUTES,265 lpThreadAttributes: ?*SECURITY_ATTRIBUTES,
266 bInheritHandles: BOOL,266 bInheritHandles: BOOL,
267 dwCreationFlags: windows.CreateProcessFlags,267 dwCreationFlags: windows.CreateProcessFlags,
268 lpEnvironment: ?LPVOID,268 lpEnvironment: ?[*:0]const u16,
269 lpCurrentDirectory: ?LPCWSTR,269 lpCurrentDirectory: ?LPCWSTR,
270 lpStartupInfo: *STARTUPINFOW,270 lpStartupInfo: *STARTUPINFOW,
271 lpProcessInformation: *PROCESS_INFORMATION,271 lpProcessInformation: *PROCESS_INFORMATION,
lib/std/os/windows/test.zig+3-3
...@@ -274,8 +274,8 @@ test "getWin32PathType vs RtlDetermineDosPathNameType_U" {...@@ -274,8 +274,8 @@ test "getWin32PathType vs RtlDetermineDosPathNameType_U" {
274 std.debug.assert(std.unicode.wtf16LeToWtf8(wtf8_buf.items, path) == wtf8_len);274 std.debug.assert(std.unicode.wtf16LeToWtf8(wtf8_buf.items, path) == wtf8_len);
275275
276 const windows_type = RtlDetermineDosPathNameType_U(path);276 const windows_type = RtlDetermineDosPathNameType_U(path);
277 const wtf16_type = windows.getWin32PathType(u16, path);277 const wtf16_type = std.fs.path.getWin32PathType(u16, path);
278 const wtf8_type = windows.getWin32PathType(u8, wtf8_buf.items);278 const wtf8_type = std.fs.path.getWin32PathType(u8, wtf8_buf.items);
279279
280 checkPathType(windows_type, wtf16_type) catch |err| {280 checkPathType(windows_type, wtf16_type) catch |err| {
281 std.debug.print("expected type {}, got {} for path: {f}\n", .{ windows_type, wtf16_type, std.unicode.fmtUtf16Le(path) });281 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" {...@@ -295,7 +295,7 @@ test "getWin32PathType vs RtlDetermineDosPathNameType_U" {
295 }295 }
296}296}
297297
298fn checkPathType(windows_type: RTL_PATH_TYPE, zig_type: windows.Win32PathType) !void {298fn checkPathType(windows_type: RTL_PATH_TYPE, zig_type: std.fs.path.Win32PathType) !void {
299 const expected_windows_type: RTL_PATH_TYPE = switch (zig_type) {299 const expected_windows_type: RTL_PATH_TYPE = switch (zig_type) {
300 .unc_absolute => .UncAbsolute,300 .unc_absolute => .UncAbsolute,
301 .drive_absolute => .DriveAbsolute,301 .drive_absolute => .DriveAbsolute,
lib/std/posix.zig+1-550
...@@ -772,214 +772,10 @@ pub fn openatZ(dir_fd: fd_t, file_path: [*:0]const u8, flags: O, mode: mode_t) O...@@ -772,214 +772,10 @@ pub fn openatZ(dir_fd: fd_t, file_path: [*:0]const u8, flags: O, mode: mode_t) O
772 }772 }
773}773}
774774
775pub fn dup(old_fd: fd_t) !fd_t {
776 const rc = system.dup(old_fd);
777 return switch (errno(rc)) {
778 .SUCCESS => return @intCast(rc),
779 .MFILE => error.ProcessFdQuotaExceeded,
780 .BADF => unreachable, // invalid file descriptor
781 else => |err| return unexpectedErrno(err),
782 };
783}
784
785pub fn dup2(old_fd: fd_t, new_fd: fd_t) !void {
786 while (true) {
787 switch (errno(system.dup2(old_fd, new_fd))) {
788 .SUCCESS => return,
789 .BUSY, .INTR => continue,
790 .MFILE => return error.ProcessFdQuotaExceeded,
791 .INVAL => unreachable, // invalid parameters passed to dup2
792 .BADF => unreachable, // invalid file descriptor
793 else => |err| return unexpectedErrno(err),
794 }
795 }
796}
797
798pub fn getpid() pid_t {
799 return system.getpid();
800}
801
802pub fn getppid() pid_t {775pub fn getppid() pid_t {
803 return system.getppid();776 return system.getppid();
804}777}
805778
806pub const ExecveError = error{
807 SystemResources,
808 AccessDenied,
809 PermissionDenied,
810 InvalidExe,
811 FileSystem,
812 IsDir,
813 FileNotFound,
814 NotDir,
815 FileBusy,
816 ProcessFdQuotaExceeded,
817 SystemFdQuotaExceeded,
818 NameTooLong,
819} || UnexpectedError;
820
821/// This function ignores PATH environment variable. See `execvpeZ` for that.
822pub fn execveZ(
823 path: [*:0]const u8,
824 child_argv: [*:null]const ?[*:0]const u8,
825 envp: [*:null]const ?[*:0]const u8,
826) ExecveError {
827 switch (errno(system.execve(path, child_argv, envp))) {
828 .SUCCESS => unreachable,
829 .FAULT => unreachable,
830 .@"2BIG" => return error.SystemResources,
831 .MFILE => return error.ProcessFdQuotaExceeded,
832 .NAMETOOLONG => return error.NameTooLong,
833 .NFILE => return error.SystemFdQuotaExceeded,
834 .NOMEM => return error.SystemResources,
835 .ACCES => return error.AccessDenied,
836 .PERM => return error.PermissionDenied,
837 .INVAL => return error.InvalidExe,
838 .NOEXEC => return error.InvalidExe,
839 .IO => return error.FileSystem,
840 .LOOP => return error.FileSystem,
841 .ISDIR => return error.IsDir,
842 .NOENT => return error.FileNotFound,
843 .NOTDIR => return error.NotDir,
844 .TXTBSY => return error.FileBusy,
845 else => |err| switch (native_os) {
846 .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => switch (err) {
847 .BADEXEC => return error.InvalidExe,
848 .BADARCH => return error.InvalidExe,
849 else => return unexpectedErrno(err),
850 },
851 .linux => switch (err) {
852 .LIBBAD => return error.InvalidExe,
853 else => return unexpectedErrno(err),
854 },
855 else => return unexpectedErrno(err),
856 },
857 }
858}
859
860pub const Arg0Expand = enum {
861 expand,
862 no_expand,
863};
864
865/// Like `execvpeZ` except if `arg0_expand` is `.expand`, then `argv` is mutable,
866/// and `argv[0]` is expanded to be the same absolute path that is passed to the execve syscall.
867/// If this function returns with an error, `argv[0]` will be restored to the value it was when it was passed in.
868pub fn execvpeZ_expandArg0(
869 comptime arg0_expand: Arg0Expand,
870 file: [*:0]const u8,
871 child_argv: switch (arg0_expand) {
872 .expand => [*:null]?[*:0]const u8,
873 .no_expand => [*:null]const ?[*:0]const u8,
874 },
875 envp: [*:null]const ?[*:0]const u8,
876) ExecveError {
877 const file_slice = mem.sliceTo(file, 0);
878 if (mem.findScalar(u8, file_slice, '/') != null) return execveZ(file, child_argv, envp);
879
880 const PATH = getenvZ("PATH") orelse "/usr/local/bin:/bin/:/usr/bin";
881 // Use of PATH_MAX here is valid as the path_buf will be passed
882 // directly to the operating system in execveZ.
883 var path_buf: [PATH_MAX]u8 = undefined;
884 var it = mem.tokenizeScalar(u8, PATH, ':');
885 var seen_eacces = false;
886 var err: ExecveError = error.FileNotFound;
887
888 // In case of expanding arg0 we must put it back if we return with an error.
889 const prev_arg0 = child_argv[0];
890 defer switch (arg0_expand) {
891 .expand => child_argv[0] = prev_arg0,
892 .no_expand => {},
893 };
894
895 while (it.next()) |search_path| {
896 const path_len = search_path.len + file_slice.len + 1;
897 if (path_buf.len < path_len + 1) return error.NameTooLong;
898 @memcpy(path_buf[0..search_path.len], search_path);
899 path_buf[search_path.len] = '/';
900 @memcpy(path_buf[search_path.len + 1 ..][0..file_slice.len], file_slice);
901 path_buf[path_len] = 0;
902 const full_path = path_buf[0..path_len :0].ptr;
903 switch (arg0_expand) {
904 .expand => child_argv[0] = full_path,
905 .no_expand => {},
906 }
907 err = execveZ(full_path, child_argv, envp);
908 switch (err) {
909 error.AccessDenied => seen_eacces = true,
910 error.FileNotFound, error.NotDir => {},
911 else => |e| return e,
912 }
913 }
914 if (seen_eacces) return error.AccessDenied;
915 return err;
916}
917
918/// This function also uses the PATH environment variable to get the full path to the executable.
919/// If `file` is an absolute path, this is the same as `execveZ`.
920pub fn execvpeZ(
921 file: [*:0]const u8,
922 argv_ptr: [*:null]const ?[*:0]const u8,
923 envp: [*:null]const ?[*:0]const u8,
924) ExecveError {
925 return execvpeZ_expandArg0(.no_expand, file, argv_ptr, envp);
926}
927
928/// Get an environment variable.
929/// See also `getenvZ`.
930pub fn getenv(key: []const u8) ?[:0]const u8 {
931 if (native_os == .windows) {
932 @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.");
933 }
934 if (mem.findScalar(u8, key, '=') != null) {
935 return null;
936 }
937 if (builtin.link_libc) {
938 var ptr = std.c.environ;
939 while (ptr[0]) |line| : (ptr += 1) {
940 var line_i: usize = 0;
941 while (line[line_i] != 0) : (line_i += 1) {
942 if (line_i == key.len) break;
943 if (line[line_i] != key[line_i]) break;
944 }
945 if ((line_i != key.len) or (line[line_i] != '=')) continue;
946
947 return mem.sliceTo(line + line_i + 1, 0);
948 }
949 return null;
950 }
951 if (native_os == .wasi) {
952 @compileError("std.posix.getenv is unavailable for WASI. See std.process.getEnvMap or std.process.getEnvVarOwned for a cross-platform API.");
953 }
954 // The simplified start logic doesn't populate environ.
955 if (std.start.simplified_logic) return null;
956 // TODO see https://github.com/ziglang/zig/issues/4524
957 for (std.os.environ) |ptr| {
958 var line_i: usize = 0;
959 while (ptr[line_i] != 0) : (line_i += 1) {
960 if (line_i == key.len) break;
961 if (ptr[line_i] != key[line_i]) break;
962 }
963 if ((line_i != key.len) or (ptr[line_i] != '=')) continue;
964
965 return mem.sliceTo(ptr + line_i + 1, 0);
966 }
967 return null;
968}
969
970/// Get an environment variable with a null-terminated name.
971/// See also `getenv`.
972pub fn getenvZ(key: [*:0]const u8) ?[:0]const u8 {
973 if (builtin.link_libc) {
974 const value = system.getenv(key) orelse return null;
975 return mem.sliceTo(value, 0);
976 }
977 if (native_os == .windows) {
978 @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.");
979 }
980 return getenv(mem.sliceTo(key, 0));
981}
982
983pub const GetCwdError = error{779pub const GetCwdError = error{
984 NameTooLong,780 NameTooLong,
985 CurrentWorkingDirectoryUnlinked,781 CurrentWorkingDirectoryUnlinked,
...@@ -1013,198 +809,6 @@ pub fn getcwd(out_buffer: []u8) GetCwdError![]u8 {...@@ -1013,198 +809,6 @@ pub fn getcwd(out_buffer: []u8) GetCwdError![]u8 {
1013 }809 }
1014}810}
1015811
1016/// On Windows, `sub_dir_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
1017/// On WASI, `sub_dir_path` should be encoded as valid UTF-8.
1018/// On other platforms, `sub_dir_path` is an opaque sequence of bytes with no particular encoding.
1019pub fn mkdirat(dir_fd: fd_t, sub_dir_path: []const u8, mode: mode_t) MakeDirError!void {
1020 if (native_os == .windows) {
1021 @compileError("use std.Io instead");
1022 } else if (native_os == .wasi and !builtin.link_libc) {
1023 @compileError("use std.Io instead");
1024 } else {
1025 const sub_dir_path_c = try toPosixPath(sub_dir_path);
1026 return mkdiratZ(dir_fd, &sub_dir_path_c, mode);
1027 }
1028}
1029
1030/// Same as `mkdirat` except the parameters are null-terminated.
1031pub fn mkdiratZ(dir_fd: fd_t, sub_dir_path: [*:0]const u8, mode: mode_t) MakeDirError!void {
1032 if (native_os == .windows) {
1033 @compileError("use std.Io instead");
1034 } else if (native_os == .wasi and !builtin.link_libc) {
1035 @compileError("use std.Io instead");
1036 }
1037 switch (errno(system.mkdirat(dir_fd, sub_dir_path, mode))) {
1038 .SUCCESS => return,
1039 .ACCES => return error.AccessDenied,
1040 .BADF => unreachable,
1041 .PERM => return error.PermissionDenied,
1042 .DQUOT => return error.DiskQuota,
1043 .EXIST => return error.PathAlreadyExists,
1044 .FAULT => unreachable,
1045 .LOOP => return error.SymLinkLoop,
1046 .MLINK => return error.LinkQuotaExceeded,
1047 .NAMETOOLONG => return error.NameTooLong,
1048 .NOENT => return error.FileNotFound,
1049 .NOMEM => return error.SystemResources,
1050 .NOSPC => return error.NoSpaceLeft,
1051 .NOTDIR => return error.NotDir,
1052 .ROFS => return error.ReadOnlyFileSystem,
1053 // dragonfly: when dir_fd is unlinked from filesystem
1054 .NOTCONN => return error.FileNotFound,
1055 .ILSEQ => return error.BadPathName,
1056 else => |err| return unexpectedErrno(err),
1057 }
1058}
1059
1060pub const MakeDirError = std.Io.Dir.CreateDirError;
1061
1062/// Create a directory.
1063/// `mode` is ignored on Windows and WASI.
1064/// On Windows, `dir_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
1065/// On WASI, `dir_path` should be encoded as valid UTF-8.
1066/// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding.
1067pub fn mkdir(dir_path: []const u8, mode: mode_t) MakeDirError!void {
1068 if (native_os == .wasi and !builtin.link_libc) {
1069 return mkdirat(AT.FDCWD, dir_path, mode);
1070 } else if (native_os == .windows) {
1071 const dir_path_w = try windows.sliceToPrefixedFileW(null, dir_path);
1072 return mkdirW(dir_path_w.span(), mode);
1073 } else {
1074 const dir_path_c = try toPosixPath(dir_path);
1075 return mkdirZ(&dir_path_c, mode);
1076 }
1077}
1078
1079/// Same as `mkdir` but the parameter is null-terminated.
1080/// On Windows, `dir_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
1081/// On WASI, `dir_path` should be encoded as valid UTF-8.
1082/// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding.
1083pub fn mkdirZ(dir_path: [*:0]const u8, mode: mode_t) MakeDirError!void {
1084 if (native_os == .windows) {
1085 const dir_path_w = try windows.cStrToPrefixedFileW(null, dir_path);
1086 return mkdirW(dir_path_w.span(), mode);
1087 } else if (native_os == .wasi and !builtin.link_libc) {
1088 return mkdir(mem.sliceTo(dir_path, 0), mode);
1089 }
1090 switch (errno(system.mkdir(dir_path, mode))) {
1091 .SUCCESS => return,
1092 .ACCES => return error.AccessDenied,
1093 .PERM => return error.PermissionDenied,
1094 .DQUOT => return error.DiskQuota,
1095 .EXIST => return error.PathAlreadyExists,
1096 .FAULT => unreachable,
1097 .LOOP => return error.SymLinkLoop,
1098 .MLINK => return error.LinkQuotaExceeded,
1099 .NAMETOOLONG => return error.NameTooLong,
1100 .NOENT => return error.FileNotFound,
1101 .NOMEM => return error.SystemResources,
1102 .NOSPC => return error.NoSpaceLeft,
1103 .NOTDIR => return error.NotDir,
1104 .ROFS => return error.ReadOnlyFileSystem,
1105 .ILSEQ => return error.BadPathName,
1106 else => |err| return unexpectedErrno(err),
1107 }
1108}
1109
1110/// Windows-only. Same as `mkdir` but the parameters is WTF16LE encoded.
1111pub fn mkdirW(dir_path_w: []const u16, mode: mode_t) MakeDirError!void {
1112 _ = mode;
1113 const sub_dir_handle = windows.OpenFile(dir_path_w, .{
1114 .dir = Io.Dir.cwd().handle,
1115 .access_mask = .{
1116 .STANDARD = .{ .SYNCHRONIZE = true },
1117 .GENERIC = .{ .READ = true },
1118 },
1119 .creation = .CREATE,
1120 .filter = .dir_only,
1121 }) catch |err| switch (err) {
1122 error.IsDir => return error.Unexpected,
1123 error.PipeBusy => return error.Unexpected,
1124 error.NoDevice => return error.Unexpected,
1125 error.WouldBlock => return error.Unexpected,
1126 error.AntivirusInterference => return error.Unexpected,
1127 error.OperationCanceled => return error.Unexpected,
1128 else => |e| return e,
1129 };
1130 windows.CloseHandle(sub_dir_handle);
1131}
1132
1133pub const ChangeCurDirError = error{
1134 AccessDenied,
1135 FileSystem,
1136 SymLinkLoop,
1137 NameTooLong,
1138 FileNotFound,
1139 SystemResources,
1140 NotDir,
1141 /// WASI: file paths must be valid UTF-8.
1142 /// Windows: file paths provided by the user must be valid WTF-8.
1143 /// https://wtf-8.codeberg.page/
1144 BadPathName,
1145} || UnexpectedError;
1146
1147/// Changes the current working directory of the calling process.
1148/// On Windows, `dir_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
1149/// On WASI, `dir_path` should be encoded as valid UTF-8.
1150/// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding.
1151pub fn chdir(dir_path: []const u8) ChangeCurDirError!void {
1152 if (native_os == .wasi and !builtin.link_libc) {
1153 @compileError("unsupported OS");
1154 } else if (native_os == .windows) {
1155 @compileError("unsupported OS");
1156 } else {
1157 const dir_path_c = try toPosixPath(dir_path);
1158 return chdirZ(&dir_path_c);
1159 }
1160}
1161
1162/// Same as `chdir` except the parameter is null-terminated.
1163/// On Windows, `dir_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
1164/// On WASI, `dir_path` should be encoded as valid UTF-8.
1165/// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding.
1166pub fn chdirZ(dir_path: [*:0]const u8) ChangeCurDirError!void {
1167 if (native_os == .windows) {
1168 @compileError("unsupported OS");
1169 } else if (native_os == .wasi and !builtin.link_libc) {
1170 @compileError("unsupported OS");
1171 }
1172 switch (errno(system.chdir(dir_path))) {
1173 .SUCCESS => return,
1174 .ACCES => return error.AccessDenied,
1175 .FAULT => unreachable,
1176 .IO => return error.FileSystem,
1177 .LOOP => return error.SymLinkLoop,
1178 .NAMETOOLONG => return error.NameTooLong,
1179 .NOENT => return error.FileNotFound,
1180 .NOMEM => return error.SystemResources,
1181 .NOTDIR => return error.NotDir,
1182 .ILSEQ => return error.BadPathName,
1183 else => |err| return unexpectedErrno(err),
1184 }
1185}
1186
1187pub const FchdirError = error{
1188 AccessDenied,
1189 NotDir,
1190 FileSystem,
1191} || UnexpectedError;
1192
1193pub fn fchdir(dirfd: fd_t) FchdirError!void {
1194 if (dirfd == AT.FDCWD) return;
1195 while (true) {
1196 switch (errno(system.fchdir(dirfd))) {
1197 .SUCCESS => return,
1198 .ACCES => return error.AccessDenied,
1199 .BADF => unreachable,
1200 .NOTDIR => return error.NotDir,
1201 .INTR => continue,
1202 .IO => return error.FileSystem,
1203 else => |err| return unexpectedErrno(err),
1204 }
1205 }
1206}
1207
1208pub const SetEidError = error{812pub const SetEidError = error{
1209 InvalidUserId,813 InvalidUserId,
1210 PermissionDenied,814 PermissionDenied,
...@@ -1231,16 +835,6 @@ pub fn seteuid(uid: uid_t) SetEidError!void {...@@ -1231,16 +835,6 @@ pub fn seteuid(uid: uid_t) SetEidError!void {
1231 }835 }
1232}836}
1233837
1234pub fn setreuid(ruid: uid_t, euid: uid_t) SetIdError!void {
1235 switch (errno(system.setreuid(ruid, euid))) {
1236 .SUCCESS => return,
1237 .AGAIN => return error.ResourceLimitReached,
1238 .INVAL => return error.InvalidUserId,
1239 .PERM => return error.PermissionDenied,
1240 else => |err| return unexpectedErrno(err),
1241 }
1242}
1243
1244pub fn setgid(gid: gid_t) SetIdError!void {838pub fn setgid(gid: gid_t) SetIdError!void {
1245 switch (errno(system.setgid(gid))) {839 switch (errno(system.setgid(gid))) {
1246 .SUCCESS => return,840 .SUCCESS => return,
...@@ -1260,34 +854,6 @@ pub fn setegid(uid: uid_t) SetEidError!void {...@@ -1260,34 +854,6 @@ pub fn setegid(uid: uid_t) SetEidError!void {
1260 }854 }
1261}855}
1262856
1263pub fn setregid(rgid: gid_t, egid: gid_t) SetIdError!void {
1264 switch (errno(system.setregid(rgid, egid))) {
1265 .SUCCESS => return,
1266 .AGAIN => return error.ResourceLimitReached,
1267 .INVAL => return error.InvalidUserId,
1268 .PERM => return error.PermissionDenied,
1269 else => |err| return unexpectedErrno(err),
1270 }
1271}
1272
1273pub const SetPgidError = error{
1274 ProcessAlreadyExec,
1275 InvalidProcessGroupId,
1276 PermissionDenied,
1277 ProcessNotFound,
1278} || UnexpectedError;
1279
1280pub fn setpgid(pid: pid_t, pgid: pid_t) SetPgidError!void {
1281 switch (errno(system.setpgid(pid, pgid))) {
1282 .SUCCESS => return,
1283 .ACCES => return error.ProcessAlreadyExec,
1284 .INVAL => return error.InvalidProcessGroupId,
1285 .PERM => return error.PermissionDenied,
1286 .SRCH => return error.ProcessNotFound,
1287 else => |err| return unexpectedErrno(err),
1288 }
1289}
1290
1291pub fn getuid() uid_t {857pub fn getuid() uid_t {
1292 return system.getuid();858 return system.getuid();
1293}859}
...@@ -1899,53 +1465,12 @@ pub fn getsockoptError(sockfd: fd_t) ConnectError!void {...@@ -1899,53 +1465,12 @@ pub fn getsockoptError(sockfd: fd_t) ConnectError!void {
1899 }1465 }
1900}1466}
19011467
1902pub const WaitPidResult = struct {
1903 pid: pid_t,
1904 status: u32,
1905};
1906
1907/// Use this version of the `waitpid` wrapper if you spawned your child process using explicit
1908/// `fork` and `execve` method.
1909pub fn waitpid(pid: pid_t, flags: u32) WaitPidResult {
1910 var status: if (builtin.link_libc) c_int else u32 = undefined;
1911 while (true) {
1912 const rc = system.waitpid(pid, &status, @intCast(flags));
1913 switch (errno(rc)) {
1914 .SUCCESS => return .{
1915 .pid = @intCast(rc),
1916 .status = @bitCast(status),
1917 },
1918 .INTR => continue,
1919 .CHILD => unreachable, // The process specified does not exist. It would be a race condition to handle this error.
1920 .INVAL => unreachable, // Invalid flags.
1921 else => unreachable,
1922 }
1923 }
1924}
1925
1926pub fn wait4(pid: pid_t, flags: u32, ru: ?*rusage) WaitPidResult {
1927 var status: if (builtin.link_libc) c_int else u32 = undefined;
1928 while (true) {
1929 const rc = system.wait4(pid, &status, @intCast(flags), ru);
1930 switch (errno(rc)) {
1931 .SUCCESS => return .{
1932 .pid = @intCast(rc),
1933 .status = @bitCast(status),
1934 },
1935 .INTR => continue,
1936 .CHILD => unreachable, // The process specified does not exist. It would be a race condition to handle this error.
1937 .INVAL => unreachable, // Invalid flags.
1938 else => unreachable,
1939 }
1940 }
1941}
1942
1943pub const FStatError = std.Io.File.StatError;1468pub const FStatError = std.Io.File.StatError;
19441469
1945/// Return information about a file descriptor.1470/// Return information about a file descriptor.
1946pub fn fstat(fd: fd_t) FStatError!Stat {1471pub fn fstat(fd: fd_t) FStatError!Stat {
1947 if (native_os == .wasi and !builtin.link_libc) {1472 if (native_os == .wasi and !builtin.link_libc) {
1948 return Stat.fromFilestat(try std.os.fstat_wasi(fd));1473 @compileError("unsupported OS");
1949 }1474 }
19501475
1951 var stat = mem.zeroes(Stat);1476 var stat = mem.zeroes(Stat);
...@@ -2468,80 +1993,6 @@ pub fn msync(memory: []align(page_size_min) u8, flags: i32) MSyncError!void {...@@ -2468,80 +1993,6 @@ pub fn msync(memory: []align(page_size_min) u8, flags: i32) MSyncError!void {
2468 }1993 }
2469}1994}
24701995
2471pub const PipeError = error{
2472 SystemFdQuotaExceeded,
2473 ProcessFdQuotaExceeded,
2474} || UnexpectedError;
2475
2476/// Creates a unidirectional data channel that can be used for interprocess communication.
2477pub fn pipe() PipeError![2]fd_t {
2478 var fds: [2]fd_t = undefined;
2479 switch (errno(system.pipe(&fds))) {
2480 .SUCCESS => return fds,
2481 .INVAL => unreachable, // Invalid parameters to pipe()
2482 .FAULT => unreachable, // Invalid fds pointer
2483 .NFILE => return error.SystemFdQuotaExceeded,
2484 .MFILE => return error.ProcessFdQuotaExceeded,
2485 else => |err| return unexpectedErrno(err),
2486 }
2487}
2488
2489pub fn pipe2(flags: O) PipeError![2]fd_t {
2490 if (@TypeOf(system.pipe2) != void) {
2491 var fds: [2]fd_t = undefined;
2492 switch (errno(system.pipe2(&fds, flags))) {
2493 .SUCCESS => return fds,
2494 .INVAL => unreachable, // Invalid flags
2495 .FAULT => unreachable, // Invalid fds pointer
2496 .NFILE => return error.SystemFdQuotaExceeded,
2497 .MFILE => return error.ProcessFdQuotaExceeded,
2498 else => |err| return unexpectedErrno(err),
2499 }
2500 }
2501
2502 const fds: [2]fd_t = try pipe();
2503 errdefer {
2504 close(fds[0]);
2505 close(fds[1]);
2506 }
2507
2508 // https://github.com/ziglang/zig/issues/18882
2509 if (@as(u32, @bitCast(flags)) == 0)
2510 return fds;
2511
2512 // CLOEXEC is special, it's a file descriptor flag and must be set using
2513 // F.SETFD.
2514 if (flags.CLOEXEC) {
2515 for (fds) |fd| {
2516 switch (errno(system.fcntl(fd, F.SETFD, @as(u32, FD_CLOEXEC)))) {
2517 .SUCCESS => {},
2518 .INVAL => unreachable, // Invalid flags
2519 .BADF => unreachable, // Always a race condition
2520 else => |err| return unexpectedErrno(err),
2521 }
2522 }
2523 }
2524
2525 const new_flags: u32 = f: {
2526 var new_flags = flags;
2527 new_flags.CLOEXEC = false;
2528 break :f @bitCast(new_flags);
2529 };
2530 // Set every other flag affecting the file status using F.SETFL.
2531 if (new_flags != 0) {
2532 for (fds) |fd| {
2533 switch (errno(system.fcntl(fd, F.SETFL, new_flags))) {
2534 .SUCCESS => {},
2535 .INVAL => unreachable, // Invalid flags
2536 .BADF => unreachable, // Always a race condition
2537 else => |err| return unexpectedErrno(err),
2538 }
2539 }
2540 }
2541
2542 return fds;
2543}
2544
2545pub const SysCtlError = error{1996pub const SysCtlError = error{
2546 PermissionDenied,1997 PermissionDenied,
2547 SystemResources,1998 SystemResources,
lib/std/posix/test.zig+5-41
...@@ -22,10 +22,10 @@ const tmpDir = std.testing.tmpDir;...@@ -22,10 +22,10 @@ const tmpDir = std.testing.tmpDir;
2222
23test "check WASI CWD" {23test "check WASI CWD" {
24 if (native_os == .wasi) {24 if (native_os == .wasi) {
25 if (std.options.wasiCwd() != 3) {25 const cwd: Dir = .cwd();
26 if (cwd.handle != 3) {
26 @panic("WASI code that uses cwd (like this test) needs a preopen for cwd (add '--dir=.' to wasmtime)");27 @panic("WASI code that uses cwd (like this test) needs a preopen for cwd (add '--dir=.' to wasmtime)");
27 }28 }
28
29 if (!builtin.link_libc) {29 if (!builtin.link_libc) {
30 // WASI without-libc hardcodes fd 3 as the FDCWD token so it can be passed directly to WASI calls30 // WASI without-libc hardcodes fd 3 as the FDCWD token so it can be passed directly to WASI calls
31 try expectEqual(3, posix.AT.FDCWD);31 try expectEqual(3, posix.AT.FDCWD);
...@@ -131,7 +131,7 @@ test "pipe" {...@@ -131,7 +131,7 @@ test "pipe" {
131 if (native_os == .windows or native_os == .wasi)131 if (native_os == .windows or native_os == .wasi)
132 return error.SkipZigTest;132 return error.SkipZigTest;
133133
134 const fds = try posix.pipe();134 const fds = try std.Io.Threaded.pipe2(.{});
135 try expect((try posix.write(fds[1], "hello")) == 5);135 try expect((try posix.write(fds[1], "hello")) == 5);
136 var buf: [16]u8 = undefined;136 var buf: [16]u8 = undefined;
137 try expect((try posix.read(fds[0], buf[0..])) == 5);137 try expect((try posix.read(fds[0], buf[0..])) == 5);
...@@ -140,11 +140,6 @@ test "pipe" {...@@ -140,11 +140,6 @@ test "pipe" {
140 posix.close(fds[0]);140 posix.close(fds[0]);
141}141}
142142
143test "argsAlloc" {
144 const args = try std.process.argsAlloc(std.testing.allocator);
145 std.process.argsFree(std.testing.allocator, args);
146}
147
148test "memfd_create" {143test "memfd_create" {
149 const io = testing.io;144 const io = testing.io;
150145
...@@ -438,42 +433,11 @@ test "sigset add/del" {...@@ -438,42 +433,11 @@ test "sigset add/del" {
438 }433 }
439}434}
440435
441test "dup & dup2" {
442 switch (native_os) {
443 .linux, .illumos => {},
444 else => return error.SkipZigTest,
445 }
446
447 const io = testing.io;
448
449 var tmp = tmpDir(.{});
450 defer tmp.cleanup();
451
452 {
453 var file = try tmp.dir.createFile(io, "os_dup_test", .{});
454 defer file.close(io);
455
456 var duped = Io.File{ .handle = try posix.dup(file.handle) };
457 defer duped.close(io);
458 try duped.writeStreamingAll(io, "dup");
459
460 // Tests aren't run in parallel so using the next fd shouldn't be an issue.
461 const new_fd = duped.handle + 1;
462 try posix.dup2(file.handle, new_fd);
463 var dup2ed = Io.File{ .handle = new_fd };
464 defer dup2ed.close(io);
465 try dup2ed.writeStreamingAll(io, "dup2");
466 }
467
468 var buffer: [8]u8 = undefined;
469 try expectEqualStrings("dupdup2", try tmp.dir.readFile(io, "os_dup_test", &buffer));
470}
471
472test "getpid" {436test "getpid" {
473 if (native_os == .wasi) return error.SkipZigTest;437 if (native_os == .wasi) return error.SkipZigTest;
474 if (native_os == .windows) return error.SkipZigTest;438 if (native_os == .windows) return error.SkipZigTest;
475439
476 try expect(posix.getpid() != 0);440 try expect(posix.system.getpid() != 0);
477}441}
478442
479test "getppid" {443test "getppid" {
...@@ -532,7 +496,7 @@ test "rename smoke test" {...@@ -532,7 +496,7 @@ test "rename smoke test" {
532 // Create some directory496 // Create some directory
533 const file_path = try Dir.path.join(gpa, &.{ base_path, "some_dir" });497 const file_path = try Dir.path.join(gpa, &.{ base_path, "some_dir" });
534 defer gpa.free(file_path);498 defer gpa.free(file_path);
535 try posix.mkdir(file_path, mode);499 try Io.Dir.createDirAbsolute(io, file_path, .fromMode(mode));
536500
537 // Rename the directory501 // Rename the directory
538 const new_file_path = try Dir.path.join(gpa, &.{ base_path, "some_other_dir" });502 const new_file_path = try Dir.path.join(gpa, &.{ base_path, "some_other_dir" });
lib/std/process.zig+294-1742
...@@ -16,10 +16,8 @@ const unicode = std.unicode;...@@ -16,10 +16,8 @@ const unicode = std.unicode;
16const max_path_bytes = std.fs.max_path_bytes;16const max_path_bytes = std.fs.max_path_bytes;
1717
18pub const Child = @import("process/Child.zig");18pub const Child = @import("process/Child.zig");
19pub const changeCurDir = posix.chdir;19pub const Args = @import("process/Args.zig");
20pub const changeCurDirZ = posix.chdirZ;20pub const Environ = @import("process/Environ.zig");
21
22pub const GetCwdError = posix.GetCwdError;
2321
24/// This is the global, process-wide protection to coordinate stderr writes.22/// This is the global, process-wide protection to coordinate stderr writes.
25///23///
...@@ -28,6 +26,40 @@ pub const GetCwdError = posix.GetCwdError;...@@ -28,6 +26,40 @@ pub const GetCwdError = posix.GetCwdError;
28/// information.26/// information.
29pub var stderr_thread_mutex: std.Thread.Mutex.Recursive = .init;27pub var stderr_thread_mutex: std.Thread.Mutex.Recursive = .init;
3028
29/// A standard set of pre-initialized useful APIs for programs to take
30/// advantage of. This is the type of the first parameter of the main function.
31/// Applications wanting more flexibility can accept `Init.Minimal` instead.
32///
33/// Completion of https://github.com/ziglang/zig/issues/24510 will also allow
34/// the second parameter of the main function to be a custom struct that
35/// contain auto-parsed CLI arguments.
36pub const Init = struct {
37 /// `Init` is a superset of `Minimal`; the latter is included here.
38 minimal: Minimal,
39 /// Permanent storage for the entire process, cleaned automatically on
40 /// exit. Not threadsafe.
41 arena: *std.heap.ArenaAllocator,
42 /// A default-selected general purpose allocator for temporary heap
43 /// allocations. Debug mode will set up leak checking if possible.
44 /// Threadsafe.
45 gpa: Allocator,
46 /// An appropriate default Io implementation based on the target
47 /// configuration. Debug mode will set up leak checking if possible.
48 io: Io,
49 /// Environment variables, initialized with `gpa`. Not threadsafe.
50 environ_map: *Environ.Map,
51
52 /// Alternative to `Init` as the first parameter of the main function.
53 pub const Minimal = struct {
54 /// Environment variables.
55 environ: Environ,
56 /// Command line arguments.
57 args: Args,
58 };
59};
60
61pub const GetCwdError = posix.GetCwdError;
62
31/// The result is a slice of `out_buffer`, from index `0`.63/// The result is a slice of `out_buffer`, from index `0`.
32/// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/).64/// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/).
33/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.65/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
...@@ -73,1484 +105,6 @@ test getCwdAlloc {...@@ -73,1484 +105,6 @@ test getCwdAlloc {
73 testing.allocator.free(cwd);105 testing.allocator.free(cwd);
74}106}
75107
76pub const EnvMap = struct {
77 hash_map: HashMap,
78
79 const HashMap = std.HashMap(
80 []const u8,
81 []const u8,
82 EnvNameHashContext,
83 std.hash_map.default_max_load_percentage,
84 );
85
86 pub const Size = HashMap.Size;
87
88 pub const EnvNameHashContext = struct {
89 fn upcase(c: u21) u21 {
90 if (c <= std.math.maxInt(u16))
91 return windows.ntdll.RtlUpcaseUnicodeChar(@as(u16, @intCast(c)));
92 return c;
93 }
94
95 pub fn hash(self: @This(), s: []const u8) u64 {
96 _ = self;
97 if (native_os == .windows) {
98 var h = std.hash.Wyhash.init(0);
99 var it = unicode.Wtf8View.initUnchecked(s).iterator();
100 while (it.nextCodepoint()) |cp| {
101 const cp_upper = upcase(cp);
102 h.update(&[_]u8{
103 @as(u8, @intCast((cp_upper >> 16) & 0xff)),
104 @as(u8, @intCast((cp_upper >> 8) & 0xff)),
105 @as(u8, @intCast((cp_upper >> 0) & 0xff)),
106 });
107 }
108 return h.final();
109 }
110 return std.hash_map.hashString(s);
111 }
112
113 pub fn eql(self: @This(), a: []const u8, b: []const u8) bool {
114 _ = self;
115 if (native_os == .windows) {
116 var it_a = unicode.Wtf8View.initUnchecked(a).iterator();
117 var it_b = unicode.Wtf8View.initUnchecked(b).iterator();
118 while (true) {
119 const c_a = it_a.nextCodepoint() orelse break;
120 const c_b = it_b.nextCodepoint() orelse return false;
121 if (upcase(c_a) != upcase(c_b))
122 return false;
123 }
124 return if (it_b.nextCodepoint()) |_| false else true;
125 }
126 return std.hash_map.eqlString(a, b);
127 }
128 };
129
130 /// Create a EnvMap backed by a specific allocator.
131 /// That allocator will be used for both backing allocations
132 /// and string deduplication.
133 pub fn init(allocator: Allocator) EnvMap {
134 return EnvMap{ .hash_map = HashMap.init(allocator) };
135 }
136
137 /// Free the backing storage of the map, as well as all
138 /// of the stored keys and values.
139 pub fn deinit(self: *EnvMap) void {
140 var it = self.hash_map.iterator();
141 while (it.next()) |entry| {
142 self.free(entry.key_ptr.*);
143 self.free(entry.value_ptr.*);
144 }
145
146 self.hash_map.deinit();
147 }
148
149 /// Same as `put` but the key and value become owned by the EnvMap rather
150 /// than being copied.
151 /// If `putMove` fails, the ownership of key and value does not transfer.
152 /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string.
153 pub fn putMove(self: *EnvMap, key: []u8, value: []u8) !void {
154 assert(unicode.wtf8ValidateSlice(key));
155 const get_or_put = try self.hash_map.getOrPut(key);
156 if (get_or_put.found_existing) {
157 self.free(get_or_put.key_ptr.*);
158 self.free(get_or_put.value_ptr.*);
159 get_or_put.key_ptr.* = key;
160 }
161 get_or_put.value_ptr.* = value;
162 }
163
164 /// `key` and `value` are copied into the EnvMap.
165 /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string.
166 pub fn put(self: *EnvMap, key: []const u8, value: []const u8) !void {
167 assert(unicode.wtf8ValidateSlice(key));
168 const value_copy = try self.copy(value);
169 errdefer self.free(value_copy);
170 const get_or_put = try self.hash_map.getOrPut(key);
171 if (get_or_put.found_existing) {
172 self.free(get_or_put.value_ptr.*);
173 } else {
174 get_or_put.key_ptr.* = self.copy(key) catch |err| {
175 _ = self.hash_map.remove(key);
176 return err;
177 };
178 }
179 get_or_put.value_ptr.* = value_copy;
180 }
181
182 /// Find the address of the value associated with a key.
183 /// The returned pointer is invalidated if the map resizes.
184 /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string.
185 pub fn getPtr(self: EnvMap, key: []const u8) ?*[]const u8 {
186 assert(unicode.wtf8ValidateSlice(key));
187 return self.hash_map.getPtr(key);
188 }
189
190 /// Return the map's copy of the value associated with
191 /// a key. The returned string is invalidated if this
192 /// key is removed from the map.
193 /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string.
194 pub fn get(self: EnvMap, key: []const u8) ?[]const u8 {
195 assert(unicode.wtf8ValidateSlice(key));
196 return self.hash_map.get(key);
197 }
198
199 /// Removes the item from the map and frees its value.
200 /// This invalidates the value returned by get() for this key.
201 /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string.
202 pub fn remove(self: *EnvMap, key: []const u8) void {
203 assert(unicode.wtf8ValidateSlice(key));
204 const kv = self.hash_map.fetchRemove(key) orelse return;
205 self.free(kv.key);
206 self.free(kv.value);
207 }
208
209 /// Returns the number of KV pairs stored in the map.
210 pub fn count(self: EnvMap) HashMap.Size {
211 return self.hash_map.count();
212 }
213
214 /// Returns an iterator over entries in the map.
215 pub fn iterator(self: *const EnvMap) HashMap.Iterator {
216 return self.hash_map.iterator();
217 }
218
219 /// Returns a full copy of `em` allocated with `gpa`, which is not necessarily
220 /// the same allocator used to allocate `em`.
221 pub fn clone(em: *const EnvMap, gpa: Allocator) Allocator.Error!EnvMap {
222 var new: EnvMap = .init(gpa);
223 errdefer new.deinit();
224 // Since we need to dupe the keys and values, the only way for error handling to not be a
225 // nightmare is to add keys to an empty map one-by-one. This could be avoided if this
226 // abstraction were a bit less... OOP-esque.
227 try new.hash_map.ensureUnusedCapacity(em.hash_map.count());
228 var it = em.hash_map.iterator();
229 while (it.next()) |entry| {
230 try new.put(entry.key_ptr.*, entry.value_ptr.*);
231 }
232 return new;
233 }
234
235 fn free(self: EnvMap, value: []const u8) void {
236 self.hash_map.allocator.free(value);
237 }
238
239 fn copy(self: EnvMap, value: []const u8) ![]u8 {
240 return self.hash_map.allocator.dupe(u8, value);
241 }
242};
243
244test EnvMap {
245 var env = EnvMap.init(testing.allocator);
246 defer env.deinit();
247
248 try env.put("SOMETHING_NEW", "hello");
249 try testing.expectEqualStrings("hello", env.get("SOMETHING_NEW").?);
250 try testing.expectEqual(@as(EnvMap.Size, 1), env.count());
251
252 // overwrite
253 try env.put("SOMETHING_NEW", "something");
254 try testing.expectEqualStrings("something", env.get("SOMETHING_NEW").?);
255 try testing.expectEqual(@as(EnvMap.Size, 1), env.count());
256
257 // a new longer name to test the Windows-specific conversion buffer
258 try env.put("SOMETHING_NEW_AND_LONGER", "1");
259 try testing.expectEqualStrings("1", env.get("SOMETHING_NEW_AND_LONGER").?);
260 try testing.expectEqual(@as(EnvMap.Size, 2), env.count());
261
262 // case insensitivity on Windows only
263 if (native_os == .windows) {
264 try testing.expectEqualStrings("1", env.get("something_New_aNd_LONGER").?);
265 } else {
266 try testing.expect(null == env.get("something_New_aNd_LONGER"));
267 }
268
269 var it = env.iterator();
270 var count: EnvMap.Size = 0;
271 while (it.next()) |entry| {
272 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.*);
273 try testing.expect(is_an_expected_name);
274 count += 1;
275 }
276 try testing.expectEqual(@as(EnvMap.Size, 2), count);
277
278 env.remove("SOMETHING_NEW");
279 try testing.expect(env.get("SOMETHING_NEW") == null);
280
281 try testing.expectEqual(@as(EnvMap.Size, 1), env.count());
282
283 if (native_os == .windows) {
284 // test Unicode case-insensitivity on Windows
285 try env.put("КИРиллИЦА", "something else");
286 try testing.expectEqualStrings("something else", env.get("кириллица").?);
287
288 // and WTF-8 that's not valid UTF-8
289 const wtf8_with_surrogate_pair = try unicode.wtf16LeToWtf8Alloc(testing.allocator, &[_]u16{
290 std.mem.nativeToLittle(u16, 0xD83D), // unpaired high surrogate
291 });
292 defer testing.allocator.free(wtf8_with_surrogate_pair);
293
294 try env.put(wtf8_with_surrogate_pair, wtf8_with_surrogate_pair);
295 try testing.expectEqualSlices(u8, wtf8_with_surrogate_pair, env.get(wtf8_with_surrogate_pair).?);
296 }
297}
298
299pub const GetEnvMapError = error{
300 OutOfMemory,
301 /// WASI-only. `environ_sizes_get` or `environ_get`
302 /// failed for an unexpected reason.
303 Unexpected,
304};
305
306/// Returns a snapshot of the environment variables of the current process.
307/// Any modifications to the resulting EnvMap will not be reflected in the environment, and
308/// likewise, any future modifications to the environment will not be reflected in the EnvMap.
309/// Caller owns resulting `EnvMap` and should call its `deinit` fn when done.
310pub fn getEnvMap(allocator: Allocator) GetEnvMapError!EnvMap {
311 var result = EnvMap.init(allocator);
312 errdefer result.deinit();
313
314 if (native_os == .windows) {
315 const ptr = windows.peb().ProcessParameters.Environment;
316
317 var i: usize = 0;
318 while (ptr[i] != 0) {
319 const key_start = i;
320
321 // There are some special environment variables that start with =,
322 // so we need a special case to not treat = as a key/value separator
323 // if it's the first character.
324 // https://devblogs.microsoft.com/oldnewthing/20100506-00/?p=14133
325 if (ptr[key_start] == '=') i += 1;
326
327 while (ptr[i] != 0 and ptr[i] != '=') : (i += 1) {}
328 const key_w = ptr[key_start..i];
329 const key = try unicode.wtf16LeToWtf8Alloc(allocator, key_w);
330 errdefer allocator.free(key);
331
332 if (ptr[i] == '=') i += 1;
333
334 const value_start = i;
335 while (ptr[i] != 0) : (i += 1) {}
336 const value_w = ptr[value_start..i];
337 const value = try unicode.wtf16LeToWtf8Alloc(allocator, value_w);
338 errdefer allocator.free(value);
339
340 i += 1; // skip over null byte
341
342 try result.putMove(key, value);
343 }
344 return result;
345 } else if (native_os == .wasi and !builtin.link_libc) {
346 var environ_count: usize = undefined;
347 var environ_buf_size: usize = undefined;
348
349 const environ_sizes_get_ret = std.os.wasi.environ_sizes_get(&environ_count, &environ_buf_size);
350 if (environ_sizes_get_ret != .SUCCESS) {
351 return posix.unexpectedErrno(environ_sizes_get_ret);
352 }
353
354 if (environ_count == 0) {
355 return result;
356 }
357
358 const environ = try allocator.alloc([*:0]u8, environ_count);
359 defer allocator.free(environ);
360 const environ_buf = try allocator.alloc(u8, environ_buf_size);
361 defer allocator.free(environ_buf);
362
363 const environ_get_ret = std.os.wasi.environ_get(environ.ptr, environ_buf.ptr);
364 if (environ_get_ret != .SUCCESS) {
365 return posix.unexpectedErrno(environ_get_ret);
366 }
367
368 for (environ) |env| {
369 const pair = mem.sliceTo(env, 0);
370 var parts = mem.splitScalar(u8, pair, '=');
371 const key = parts.first();
372 const value = parts.rest();
373 try result.put(key, value);
374 }
375 return result;
376 } else if (builtin.link_libc) {
377 var ptr = std.c.environ;
378 while (ptr[0]) |line| : (ptr += 1) {
379 var line_i: usize = 0;
380 while (line[line_i] != 0 and line[line_i] != '=') : (line_i += 1) {}
381 const key = line[0..line_i];
382
383 var end_i: usize = line_i;
384 while (line[end_i] != 0) : (end_i += 1) {}
385 const value = line[line_i + 1 .. end_i];
386
387 try result.put(key, value);
388 }
389 return result;
390 } else {
391 for (std.os.environ) |line| {
392 var line_i: usize = 0;
393 while (line[line_i] != 0 and line[line_i] != '=') : (line_i += 1) {}
394 const key = line[0..line_i];
395
396 var end_i: usize = line_i;
397 while (line[end_i] != 0) : (end_i += 1) {}
398 const value = line[line_i + 1 .. end_i];
399
400 try result.put(key, value);
401 }
402 return result;
403 }
404}
405
406test getEnvMap {
407 var env = try getEnvMap(testing.allocator);
408 defer env.deinit();
409}
410
411pub const GetEnvVarOwnedError = error{
412 OutOfMemory,
413 EnvironmentVariableNotFound,
414
415 /// On Windows, environment variable keys provided by the user must be valid WTF-8.
416 /// https://wtf-8.codeberg.page/
417 InvalidWtf8,
418};
419
420/// Caller must free returned memory.
421/// On Windows, if `key` is not valid [WTF-8](https://wtf-8.codeberg.page/),
422/// then `error.InvalidWtf8` is returned.
423/// On Windows, the value is encoded as [WTF-8](https://wtf-8.codeberg.page/).
424/// On other platforms, the value is an opaque sequence of bytes with no particular encoding.
425pub fn getEnvVarOwned(allocator: Allocator, key: []const u8) GetEnvVarOwnedError![]u8 {
426 if (native_os == .windows) {
427 const result_w = blk: {
428 var stack_alloc = std.heap.stackFallback(256 * @sizeOf(u16), allocator);
429 const stack_allocator = stack_alloc.get();
430 const key_w = try unicode.wtf8ToWtf16LeAllocZ(stack_allocator, key);
431 defer stack_allocator.free(key_w);
432
433 break :blk getenvW(key_w) orelse return error.EnvironmentVariableNotFound;
434 };
435 // wtf16LeToWtf8Alloc can only fail with OutOfMemory
436 return unicode.wtf16LeToWtf8Alloc(allocator, result_w);
437 } else if (native_os == .wasi and !builtin.link_libc) {
438 var envmap = getEnvMap(allocator) catch return error.OutOfMemory;
439 defer envmap.deinit();
440 const val = envmap.get(key) orelse return error.EnvironmentVariableNotFound;
441 return allocator.dupe(u8, val);
442 } else {
443 const result = posix.getenv(key) orelse return error.EnvironmentVariableNotFound;
444 return allocator.dupe(u8, result);
445 }
446}
447
448/// On Windows, `key` must be valid WTF-8.
449pub inline fn hasEnvVarConstant(comptime key: []const u8) bool {
450 if (native_os == .windows) {
451 const key_w = comptime unicode.wtf8ToWtf16LeStringLiteral(key);
452 return getenvW(key_w) != null;
453 } else if (native_os == .wasi and !builtin.link_libc) {
454 return false;
455 } else {
456 return posix.getenv(key) != null;
457 }
458}
459
460/// On Windows, `key` must be valid WTF-8.
461pub inline fn hasNonEmptyEnvVarConstant(comptime key: []const u8) bool {
462 if (native_os == .windows) {
463 const key_w = comptime unicode.wtf8ToWtf16LeStringLiteral(key);
464 const value = getenvW(key_w) orelse return false;
465 return value.len != 0;
466 } else if (native_os == .wasi and !builtin.link_libc) {
467 return false;
468 } else {
469 const value = posix.getenv(key) orelse return false;
470 return value.len != 0;
471 }
472}
473
474pub const ParseEnvVarIntError = std.fmt.ParseIntError || error{EnvironmentVariableNotFound};
475
476/// Parses an environment variable as an integer.
477///
478/// Since the key is comptime-known, no allocation is needed.
479///
480/// On Windows, `key` must be valid WTF-8.
481pub fn parseEnvVarInt(comptime key: []const u8, comptime I: type, base: u8) ParseEnvVarIntError!I {
482 if (native_os == .windows) {
483 const key_w = comptime std.unicode.wtf8ToWtf16LeStringLiteral(key);
484 const text = getenvW(key_w) orelse return error.EnvironmentVariableNotFound;
485 return std.fmt.parseIntWithGenericCharacter(I, u16, text, base);
486 } else if (native_os == .wasi and !builtin.link_libc) {
487 @compileError("parseEnvVarInt is not supported for WASI without libc");
488 } else {
489 const text = posix.getenv(key) orelse return error.EnvironmentVariableNotFound;
490 return std.fmt.parseInt(I, text, base);
491 }
492}
493
494pub const HasEnvVarError = error{
495 OutOfMemory,
496
497 /// On Windows, environment variable keys provided by the user must be valid WTF-8.
498 /// https://wtf-8.codeberg.page/
499 InvalidWtf8,
500};
501
502/// On Windows, if `key` is not valid [WTF-8](https://wtf-8.codeberg.page/),
503/// then `error.InvalidWtf8` is returned.
504pub fn hasEnvVar(allocator: Allocator, key: []const u8) HasEnvVarError!bool {
505 if (native_os == .windows) {
506 var stack_alloc = std.heap.stackFallback(256 * @sizeOf(u16), allocator);
507 const stack_allocator = stack_alloc.get();
508 const key_w = try unicode.wtf8ToWtf16LeAllocZ(stack_allocator, key);
509 defer stack_allocator.free(key_w);
510 return getenvW(key_w) != null;
511 } else if (native_os == .wasi and !builtin.link_libc) {
512 var envmap = getEnvMap(allocator) catch return error.OutOfMemory;
513 defer envmap.deinit();
514 return envmap.getPtr(key) != null;
515 } else {
516 return posix.getenv(key) != null;
517 }
518}
519
520/// On Windows, if `key` is not valid [WTF-8](https://wtf-8.codeberg.page/),
521/// then `error.InvalidWtf8` is returned.
522pub fn hasNonEmptyEnvVar(allocator: Allocator, key: []const u8) HasEnvVarError!bool {
523 if (native_os == .windows) {
524 var stack_alloc = std.heap.stackFallback(256 * @sizeOf(u16), allocator);
525 const stack_allocator = stack_alloc.get();
526 const key_w = try unicode.wtf8ToWtf16LeAllocZ(stack_allocator, key);
527 defer stack_allocator.free(key_w);
528 const value = getenvW(key_w) orelse return false;
529 return value.len != 0;
530 } else if (native_os == .wasi and !builtin.link_libc) {
531 var envmap = getEnvMap(allocator) catch return error.OutOfMemory;
532 defer envmap.deinit();
533 const value = envmap.getPtr(key) orelse return false;
534 return value.len != 0;
535 } else {
536 const value = posix.getenv(key) orelse return false;
537 return value.len != 0;
538 }
539}
540
541/// Windows-only. Get an environment variable with a null-terminated, WTF-16 encoded name.
542/// The returned slice points to memory in the PEB.
543///
544/// This function performs a Unicode-aware case-insensitive lookup using RtlEqualUnicodeString.
545///
546/// See also:
547/// * `std.posix.getenv`
548/// * `getEnvMap`
549/// * `getEnvVarOwned`
550/// * `hasEnvVarConstant`
551/// * `hasEnvVar`
552pub fn getenvW(key: [*:0]const u16) ?[:0]const u16 {
553 if (native_os != .windows) {
554 @compileError("Windows-only");
555 }
556 const key_slice = mem.sliceTo(key, 0);
557 // '=' anywhere but the start makes this an invalid environment variable name
558 if (key_slice.len > 0 and std.mem.findScalar(u16, key_slice[1..], '=') != null) {
559 return null;
560 }
561 const ptr = windows.peb().ProcessParameters.Environment;
562 var i: usize = 0;
563 while (ptr[i] != 0) {
564 const key_value = mem.sliceTo(ptr[i..], 0);
565
566 // There are some special environment variables that start with =,
567 // so we need a special case to not treat = as a key/value separator
568 // if it's the first character.
569 // https://devblogs.microsoft.com/oldnewthing/20100506-00/?p=14133
570 const equal_search_start: usize = if (key_value[0] == '=') 1 else 0;
571 const equal_index = std.mem.findScalarPos(u16, key_value, equal_search_start, '=') orelse {
572 // This is enforced by CreateProcess.
573 // If violated, CreateProcess will fail with INVALID_PARAMETER.
574 unreachable; // must contain a =
575 };
576
577 const this_key = key_value[0..equal_index];
578 if (windows.eqlIgnoreCaseWtf16(key_slice, this_key)) {
579 return key_value[equal_index + 1 ..];
580 }
581
582 // skip past the NUL terminator
583 i += key_value.len + 1;
584 }
585 return null;
586}
587
588test getEnvVarOwned {
589 try testing.expectError(
590 error.EnvironmentVariableNotFound,
591 getEnvVarOwned(std.testing.allocator, "BADENV"),
592 );
593}
594
595test hasEnvVarConstant {
596 if (native_os == .wasi and !builtin.link_libc) return error.SkipZigTest;
597
598 try testing.expect(!hasEnvVarConstant("BADENV"));
599}
600
601test hasEnvVar {
602 const has_env = try hasEnvVar(std.testing.allocator, "BADENV");
603 try testing.expect(!has_env);
604}
605
606pub const ArgIteratorPosix = struct {
607 index: usize,
608 count: usize,
609
610 pub const InitError = error{};
611
612 pub fn init() ArgIteratorPosix {
613 return ArgIteratorPosix{
614 .index = 0,
615 .count = std.os.argv.len,
616 };
617 }
618
619 pub fn next(self: *ArgIteratorPosix) ?[:0]const u8 {
620 if (self.index == self.count) return null;
621
622 const s = std.os.argv[self.index];
623 self.index += 1;
624 return mem.sliceTo(s, 0);
625 }
626
627 pub fn skip(self: *ArgIteratorPosix) bool {
628 if (self.index == self.count) return false;
629
630 self.index += 1;
631 return true;
632 }
633};
634
635pub const ArgIteratorWasi = struct {
636 allocator: Allocator,
637 index: usize,
638 args: [][:0]u8,
639
640 pub const InitError = error{OutOfMemory} || posix.UnexpectedError;
641
642 /// You must call deinit to free the internal buffer of the
643 /// iterator after you are done.
644 pub fn init(allocator: Allocator) InitError!ArgIteratorWasi {
645 const fetched_args = try ArgIteratorWasi.internalInit(allocator);
646 return ArgIteratorWasi{
647 .allocator = allocator,
648 .index = 0,
649 .args = fetched_args,
650 };
651 }
652
653 fn internalInit(allocator: Allocator) InitError![][:0]u8 {
654 var count: usize = undefined;
655 var buf_size: usize = undefined;
656
657 switch (std.os.wasi.args_sizes_get(&count, &buf_size)) {
658 .SUCCESS => {},
659 else => |err| return posix.unexpectedErrno(err),
660 }
661
662 if (count == 0) {
663 return &[_][:0]u8{};
664 }
665
666 const argv = try allocator.alloc([*:0]u8, count);
667 defer allocator.free(argv);
668
669 const argv_buf = try allocator.alloc(u8, buf_size);
670
671 switch (std.os.wasi.args_get(argv.ptr, argv_buf.ptr)) {
672 .SUCCESS => {},
673 else => |err| return posix.unexpectedErrno(err),
674 }
675
676 var result_args = try allocator.alloc([:0]u8, count);
677 var i: usize = 0;
678 while (i < count) : (i += 1) {
679 result_args[i] = mem.sliceTo(argv[i], 0);
680 }
681
682 return result_args;
683 }
684
685 pub fn next(self: *ArgIteratorWasi) ?[:0]const u8 {
686 if (self.index == self.args.len) return null;
687
688 const arg = self.args[self.index];
689 self.index += 1;
690 return arg;
691 }
692
693 pub fn skip(self: *ArgIteratorWasi) bool {
694 if (self.index == self.args.len) return false;
695
696 self.index += 1;
697 return true;
698 }
699
700 /// Call to free the internal buffer of the iterator.
701 pub fn deinit(self: *ArgIteratorWasi) void {
702 // Nothing is allocated when there are no args
703 if (self.args.len == 0) return;
704
705 const last_item = self.args[self.args.len - 1];
706 const last_byte_addr = @intFromPtr(last_item.ptr) + last_item.len + 1; // null terminated
707 const first_item_ptr = self.args[0].ptr;
708 const len = last_byte_addr - @intFromPtr(first_item_ptr);
709 self.allocator.free(first_item_ptr[0..len]);
710 self.allocator.free(self.args);
711 }
712};
713
714/// Iterator that implements the Windows command-line parsing algorithm.
715/// The implementation is intended to be compatible with the post-2008 C runtime,
716/// but is *not* intended to be compatible with `CommandLineToArgvW` since
717/// `CommandLineToArgvW` uses the pre-2008 parsing rules.
718///
719/// This iterator faithfully implements the parsing behavior observed from the C runtime with
720/// one exception: if the command-line string is empty, the iterator will immediately complete
721/// without returning any arguments (whereas the C runtime will return a single argument
722/// representing the name of the current executable).
723///
724/// The essential parts of the algorithm are described in Microsoft's documentation:
725///
726/// - https://learn.microsoft.com/en-us/cpp/cpp/main-function-command-line-args?view=msvc-170#parsing-c-command-line-arguments
727///
728/// David Deley explains some additional undocumented quirks in great detail:
729///
730/// - https://daviddeley.com/autohotkey/parameters/parameters.htm#WINCRULES
731pub const ArgIteratorWindows = struct {
732 allocator: Allocator,
733 /// Encoded as WTF-16 LE.
734 cmd_line: []const u16,
735 index: usize = 0,
736 /// Owned by the iterator. Long enough to hold contiguous NUL-terminated slices
737 /// of each argument encoded as WTF-8.
738 buffer: []u8,
739 start: usize = 0,
740 end: usize = 0,
741
742 pub const InitError = error{OutOfMemory};
743
744 /// `cmd_line_w` *must* be a WTF16-LE-encoded string.
745 ///
746 /// The iterator stores and uses `cmd_line_w`, so its memory must be valid for
747 /// at least as long as the returned ArgIteratorWindows.
748 pub fn init(allocator: Allocator, cmd_line_w: []const u16) InitError!ArgIteratorWindows {
749 const wtf8_len = unicode.calcWtf8Len(cmd_line_w);
750
751 // This buffer must be large enough to contain contiguous NUL-terminated slices
752 // of each argument.
753 // - During parsing, the length of a parsed argument will always be equal to
754 // to less than its unparsed length
755 // - The first argument needs one extra byte of space allocated for its NUL
756 // terminator, but for each subsequent argument the necessary whitespace
757 // between arguments guarantees room for their NUL terminator(s).
758 const buffer = try allocator.alloc(u8, wtf8_len + 1);
759 errdefer allocator.free(buffer);
760
761 return .{
762 .allocator = allocator,
763 .cmd_line = cmd_line_w,
764 .buffer = buffer,
765 };
766 }
767
768 /// Returns the next argument and advances the iterator. Returns `null` if at the end of the
769 /// command-line string. The iterator owns the returned slice.
770 /// The result is encoded as [WTF-8](https://wtf-8.codeberg.page/).
771 pub fn next(self: *ArgIteratorWindows) ?[:0]const u8 {
772 return self.nextWithStrategy(next_strategy);
773 }
774
775 /// Skips the next argument and advances the iterator. Returns `true` if an argument was
776 /// skipped, `false` if at the end of the command-line string.
777 pub fn skip(self: *ArgIteratorWindows) bool {
778 return self.nextWithStrategy(skip_strategy);
779 }
780
781 const next_strategy = struct {
782 const T = ?[:0]const u8;
783
784 const eof = null;
785
786 /// Returns '\' if any backslashes are emitted, otherwise returns `last_emitted_code_unit`.
787 fn emitBackslashes(self: *ArgIteratorWindows, count: usize, last_emitted_code_unit: ?u16) ?u16 {
788 for (0..count) |_| {
789 self.buffer[self.end] = '\\';
790 self.end += 1;
791 }
792 return if (count != 0) '\\' else last_emitted_code_unit;
793 }
794
795 /// If `last_emitted_code_unit` and `code_unit` form a surrogate pair, then
796 /// the previously emitted high surrogate is overwritten by the codepoint encoded
797 /// by the surrogate pair, and `null` is returned.
798 /// Otherwise, `code_unit` is emitted and returned.
799 fn emitCharacter(self: *ArgIteratorWindows, code_unit: u16, last_emitted_code_unit: ?u16) ?u16 {
800 // Because we are emitting WTF-8, we need to
801 // check to see if we've emitted two consecutive surrogate
802 // codepoints that form a valid surrogate pair in order
803 // to ensure that we're always emitting well-formed WTF-8
804 // (https://wtf-8.codeberg.page/#concatenating).
805 //
806 // If we do have a valid surrogate pair, we need to emit
807 // the UTF-8 sequence for the codepoint that they encode
808 // instead of the WTF-8 encoding for the two surrogate pairs
809 // separately.
810 //
811 // This is relevant when dealing with a WTF-16 encoded
812 // command line like this:
813 // "<0xD801>"<0xDC37>
814 // which would get parsed and converted to WTF-8 as:
815 // <0xED><0xA0><0x81><0xED><0xB0><0xB7>
816 // but instead, we need to recognize the surrogate pair
817 // and emit the codepoint it encodes, which in this
818 // example is U+10437 (𐐷), which is encoded in UTF-8 as:
819 // <0xF0><0x90><0x90><0xB7>
820 if (last_emitted_code_unit != null and
821 std.unicode.utf16IsLowSurrogate(code_unit) and
822 std.unicode.utf16IsHighSurrogate(last_emitted_code_unit.?))
823 {
824 const codepoint = std.unicode.utf16DecodeSurrogatePair(&.{ last_emitted_code_unit.?, code_unit }) catch unreachable;
825
826 // Unpaired surrogate is 3 bytes long
827 const dest = self.buffer[self.end - 3 ..];
828 const len = unicode.utf8Encode(codepoint, dest) catch unreachable;
829 // All codepoints that require a surrogate pair (> U+FFFF) are encoded as 4 bytes
830 assert(len == 4);
831 self.end += 1;
832 return null;
833 }
834
835 const wtf8_len = std.unicode.wtf8Encode(code_unit, self.buffer[self.end..]) catch unreachable;
836 self.end += wtf8_len;
837 return code_unit;
838 }
839
840 fn yieldArg(self: *ArgIteratorWindows) [:0]const u8 {
841 self.buffer[self.end] = 0;
842 const arg = self.buffer[self.start..self.end :0];
843 self.end += 1;
844 self.start = self.end;
845 return arg;
846 }
847 };
848
849 const skip_strategy = struct {
850 const T = bool;
851
852 const eof = false;
853
854 fn emitBackslashes(_: *ArgIteratorWindows, _: usize, last_emitted_code_unit: ?u16) ?u16 {
855 return last_emitted_code_unit;
856 }
857
858 fn emitCharacter(_: *ArgIteratorWindows, _: u16, last_emitted_code_unit: ?u16) ?u16 {
859 return last_emitted_code_unit;
860 }
861
862 fn yieldArg(_: *ArgIteratorWindows) bool {
863 return true;
864 }
865 };
866
867 fn nextWithStrategy(self: *ArgIteratorWindows, comptime strategy: type) strategy.T {
868 var last_emitted_code_unit: ?u16 = null;
869 // The first argument (the executable name) uses different parsing rules.
870 if (self.index == 0) {
871 if (self.cmd_line.len == 0 or self.cmd_line[0] == 0) {
872 // Immediately complete the iterator.
873 // The C runtime would return the name of the current executable here.
874 return strategy.eof;
875 }
876
877 var inside_quotes = false;
878 while (true) : (self.index += 1) {
879 const char = if (self.index != self.cmd_line.len)
880 mem.littleToNative(u16, self.cmd_line[self.index])
881 else
882 0;
883 switch (char) {
884 0 => {
885 return strategy.yieldArg(self);
886 },
887 '"' => {
888 inside_quotes = !inside_quotes;
889 },
890 ' ', '\t' => {
891 if (inside_quotes) {
892 last_emitted_code_unit = strategy.emitCharacter(self, char, last_emitted_code_unit);
893 } else {
894 self.index += 1;
895 return strategy.yieldArg(self);
896 }
897 },
898 else => {
899 last_emitted_code_unit = strategy.emitCharacter(self, char, last_emitted_code_unit);
900 },
901 }
902 }
903 }
904
905 // Skip spaces and tabs. The iterator completes if we reach the end of the string here.
906 while (true) : (self.index += 1) {
907 const char = if (self.index != self.cmd_line.len)
908 mem.littleToNative(u16, self.cmd_line[self.index])
909 else
910 0;
911 switch (char) {
912 0 => return strategy.eof,
913 ' ', '\t' => continue,
914 else => break,
915 }
916 }
917
918 // Parsing rules for subsequent arguments:
919 //
920 // - The end of the string always terminates the current argument.
921 // - When not in 'inside_quotes' mode, a space or tab terminates the current argument.
922 // - 2n backslashes followed by a quote emit n backslashes (note: n can be zero).
923 // If in 'inside_quotes' and the quote is immediately followed by a second quote,
924 // one quote is emitted and the other is skipped, otherwise, the quote is skipped
925 // and 'inside_quotes' is toggled.
926 // - 2n + 1 backslashes followed by a quote emit n backslashes followed by a quote.
927 // - n backslashes not followed by a quote emit n backslashes.
928 var backslash_count: usize = 0;
929 var inside_quotes = false;
930 while (true) : (self.index += 1) {
931 const char = if (self.index != self.cmd_line.len)
932 mem.littleToNative(u16, self.cmd_line[self.index])
933 else
934 0;
935 switch (char) {
936 0 => {
937 last_emitted_code_unit = strategy.emitBackslashes(self, backslash_count, last_emitted_code_unit);
938 return strategy.yieldArg(self);
939 },
940 ' ', '\t' => {
941 last_emitted_code_unit = strategy.emitBackslashes(self, backslash_count, last_emitted_code_unit);
942 backslash_count = 0;
943 if (inside_quotes) {
944 last_emitted_code_unit = strategy.emitCharacter(self, char, last_emitted_code_unit);
945 } else return strategy.yieldArg(self);
946 },
947 '"' => {
948 const char_is_escaped_quote = backslash_count % 2 != 0;
949 last_emitted_code_unit = strategy.emitBackslashes(self, backslash_count / 2, last_emitted_code_unit);
950 backslash_count = 0;
951 if (char_is_escaped_quote) {
952 last_emitted_code_unit = strategy.emitCharacter(self, '"', last_emitted_code_unit);
953 } else {
954 if (inside_quotes and
955 self.index + 1 != self.cmd_line.len and
956 mem.littleToNative(u16, self.cmd_line[self.index + 1]) == '"')
957 {
958 last_emitted_code_unit = strategy.emitCharacter(self, '"', last_emitted_code_unit);
959 self.index += 1;
960 } else {
961 inside_quotes = !inside_quotes;
962 }
963 }
964 },
965 '\\' => {
966 backslash_count += 1;
967 },
968 else => {
969 last_emitted_code_unit = strategy.emitBackslashes(self, backslash_count, last_emitted_code_unit);
970 backslash_count = 0;
971 last_emitted_code_unit = strategy.emitCharacter(self, char, last_emitted_code_unit);
972 },
973 }
974 }
975 }
976
977 /// Frees the iterator's copy of the command-line string and all previously returned
978 /// argument slices.
979 pub fn deinit(self: *ArgIteratorWindows) void {
980 self.allocator.free(self.buffer);
981 }
982};
983
984/// Optional parameters for `ArgIteratorGeneral`
985pub const ArgIteratorGeneralOptions = struct {
986 comments: bool = false,
987 single_quotes: bool = false,
988};
989
990/// A general Iterator to parse a string into a set of arguments
991pub fn ArgIteratorGeneral(comptime options: ArgIteratorGeneralOptions) type {
992 return struct {
993 allocator: Allocator,
994 index: usize = 0,
995 cmd_line: []const u8,
996
997 /// Should the cmd_line field be free'd (using the allocator) on deinit()?
998 free_cmd_line_on_deinit: bool,
999
1000 /// buffer MUST be long enough to hold the cmd_line plus a null terminator.
1001 /// buffer will we free'd (using the allocator) on deinit()
1002 buffer: []u8,
1003 start: usize = 0,
1004 end: usize = 0,
1005
1006 pub const Self = @This();
1007
1008 pub const InitError = error{OutOfMemory};
1009
1010 /// cmd_line_utf8 MUST remain valid and constant while using this instance
1011 pub fn init(allocator: Allocator, cmd_line_utf8: []const u8) InitError!Self {
1012 const buffer = try allocator.alloc(u8, cmd_line_utf8.len + 1);
1013 errdefer allocator.free(buffer);
1014
1015 return Self{
1016 .allocator = allocator,
1017 .cmd_line = cmd_line_utf8,
1018 .free_cmd_line_on_deinit = false,
1019 .buffer = buffer,
1020 };
1021 }
1022
1023 /// cmd_line_utf8 will be free'd (with the allocator) on deinit()
1024 pub fn initTakeOwnership(allocator: Allocator, cmd_line_utf8: []const u8) InitError!Self {
1025 const buffer = try allocator.alloc(u8, cmd_line_utf8.len + 1);
1026 errdefer allocator.free(buffer);
1027
1028 return Self{
1029 .allocator = allocator,
1030 .cmd_line = cmd_line_utf8,
1031 .free_cmd_line_on_deinit = true,
1032 .buffer = buffer,
1033 };
1034 }
1035
1036 // Skips over whitespace in the cmd_line.
1037 // Returns false if the terminating sentinel is reached, true otherwise.
1038 // Also skips over comments (if supported).
1039 fn skipWhitespace(self: *Self) bool {
1040 while (true) : (self.index += 1) {
1041 const character = if (self.index != self.cmd_line.len) self.cmd_line[self.index] else 0;
1042 switch (character) {
1043 0 => return false,
1044 ' ', '\t', '\r', '\n' => continue,
1045 '#' => {
1046 if (options.comments) {
1047 while (true) : (self.index += 1) {
1048 switch (self.cmd_line[self.index]) {
1049 '\n' => break,
1050 0 => return false,
1051 else => continue,
1052 }
1053 }
1054 continue;
1055 } else {
1056 break;
1057 }
1058 },
1059 else => break,
1060 }
1061 }
1062 return true;
1063 }
1064
1065 pub fn skip(self: *Self) bool {
1066 if (!self.skipWhitespace()) {
1067 return false;
1068 }
1069
1070 var backslash_count: usize = 0;
1071 var in_quote = false;
1072 while (true) : (self.index += 1) {
1073 const character = if (self.index != self.cmd_line.len) self.cmd_line[self.index] else 0;
1074 switch (character) {
1075 0 => return true,
1076 '"', '\'' => {
1077 if (!options.single_quotes and character == '\'') {
1078 backslash_count = 0;
1079 continue;
1080 }
1081 const quote_is_real = backslash_count % 2 == 0;
1082 if (quote_is_real) {
1083 in_quote = !in_quote;
1084 }
1085 },
1086 '\\' => {
1087 backslash_count += 1;
1088 },
1089 ' ', '\t', '\r', '\n' => {
1090 if (!in_quote) {
1091 return true;
1092 }
1093 backslash_count = 0;
1094 },
1095 else => {
1096 backslash_count = 0;
1097 continue;
1098 },
1099 }
1100 }
1101 }
1102
1103 /// Returns a slice of the internal buffer that contains the next argument.
1104 /// Returns null when it reaches the end.
1105 pub fn next(self: *Self) ?[:0]const u8 {
1106 if (!self.skipWhitespace()) {
1107 return null;
1108 }
1109
1110 var backslash_count: usize = 0;
1111 var in_quote = false;
1112 while (true) : (self.index += 1) {
1113 const character = if (self.index != self.cmd_line.len) self.cmd_line[self.index] else 0;
1114 switch (character) {
1115 0 => {
1116 self.emitBackslashes(backslash_count);
1117 self.buffer[self.end] = 0;
1118 const token = self.buffer[self.start..self.end :0];
1119 self.end += 1;
1120 self.start = self.end;
1121 return token;
1122 },
1123 '"', '\'' => {
1124 if (!options.single_quotes and character == '\'') {
1125 self.emitBackslashes(backslash_count);
1126 backslash_count = 0;
1127 self.emitCharacter(character);
1128 continue;
1129 }
1130 const quote_is_real = backslash_count % 2 == 0;
1131 self.emitBackslashes(backslash_count / 2);
1132 backslash_count = 0;
1133
1134 if (quote_is_real) {
1135 in_quote = !in_quote;
1136 } else {
1137 self.emitCharacter('"');
1138 }
1139 },
1140 '\\' => {
1141 backslash_count += 1;
1142 },
1143 ' ', '\t', '\r', '\n' => {
1144 self.emitBackslashes(backslash_count);
1145 backslash_count = 0;
1146 if (in_quote) {
1147 self.emitCharacter(character);
1148 } else {
1149 self.buffer[self.end] = 0;
1150 const token = self.buffer[self.start..self.end :0];
1151 self.end += 1;
1152 self.start = self.end;
1153 return token;
1154 }
1155 },
1156 else => {
1157 self.emitBackslashes(backslash_count);
1158 backslash_count = 0;
1159 self.emitCharacter(character);
1160 },
1161 }
1162 }
1163 }
1164
1165 fn emitBackslashes(self: *Self, emit_count: usize) void {
1166 var i: usize = 0;
1167 while (i < emit_count) : (i += 1) {
1168 self.emitCharacter('\\');
1169 }
1170 }
1171
1172 fn emitCharacter(self: *Self, char: u8) void {
1173 self.buffer[self.end] = char;
1174 self.end += 1;
1175 }
1176
1177 /// Call to free the internal buffer of the iterator.
1178 pub fn deinit(self: *Self) void {
1179 self.allocator.free(self.buffer);
1180
1181 if (self.free_cmd_line_on_deinit) {
1182 self.allocator.free(self.cmd_line);
1183 }
1184 }
1185 };
1186}
1187
1188/// Cross-platform command line argument iterator.
1189pub const ArgIterator = struct {
1190 const InnerType = switch (native_os) {
1191 .windows => ArgIteratorWindows,
1192 .wasi => if (builtin.link_libc) ArgIteratorPosix else ArgIteratorWasi,
1193 else => ArgIteratorPosix,
1194 };
1195
1196 inner: InnerType,
1197
1198 /// Initialize the args iterator. Consider using initWithAllocator() instead
1199 /// for cross-platform compatibility.
1200 pub fn init() ArgIterator {
1201 if (native_os == .wasi) {
1202 @compileError("In WASI, use initWithAllocator instead.");
1203 }
1204 if (native_os == .windows) {
1205 @compileError("In Windows, use initWithAllocator instead.");
1206 }
1207
1208 return ArgIterator{ .inner = InnerType.init() };
1209 }
1210
1211 pub const InitError = InnerType.InitError;
1212
1213 /// You must deinitialize iterator's internal buffers by calling `deinit` when done.
1214 pub fn initWithAllocator(allocator: Allocator) InitError!ArgIterator {
1215 if (native_os == .wasi and !builtin.link_libc) {
1216 return ArgIterator{ .inner = try InnerType.init(allocator) };
1217 }
1218 if (native_os == .windows) {
1219 const cmd_line = std.os.windows.peb().ProcessParameters.CommandLine;
1220 const cmd_line_w = cmd_line.Buffer.?[0 .. cmd_line.Length / 2];
1221 return ArgIterator{ .inner = try InnerType.init(allocator, cmd_line_w) };
1222 }
1223
1224 return ArgIterator{ .inner = InnerType.init() };
1225 }
1226
1227 /// Get the next argument. Returns 'null' if we are at the end.
1228 /// Returned slice is pointing to the iterator's internal buffer.
1229 /// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/).
1230 /// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
1231 pub fn next(self: *ArgIterator) ?([:0]const u8) {
1232 return self.inner.next();
1233 }
1234
1235 /// Parse past 1 argument without capturing it.
1236 /// Returns `true` if skipped an arg, `false` if we are at the end.
1237 pub fn skip(self: *ArgIterator) bool {
1238 return self.inner.skip();
1239 }
1240
1241 /// Call this to free the iterator's internal buffer if the iterator
1242 /// was created with `initWithAllocator` function.
1243 pub fn deinit(self: *ArgIterator) void {
1244 // Unless we're targeting WASI or Windows, this is a no-op.
1245 if (native_os == .wasi and !builtin.link_libc) {
1246 self.inner.deinit();
1247 }
1248
1249 if (native_os == .windows) {
1250 self.inner.deinit();
1251 }
1252 }
1253};
1254
1255/// Holds the command-line arguments, with the program name as the first entry.
1256/// Use argsWithAllocator() for cross-platform code.
1257pub fn args() ArgIterator {
1258 return ArgIterator.init();
1259}
1260
1261/// You must deinitialize iterator's internal buffers by calling `deinit` when done.
1262pub fn argsWithAllocator(allocator: Allocator) ArgIterator.InitError!ArgIterator {
1263 return ArgIterator.initWithAllocator(allocator);
1264}
1265
1266/// Caller must call argsFree on result.
1267/// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/).
1268/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
1269pub fn argsAlloc(allocator: Allocator) ![][:0]u8 {
1270 // TODO refactor to only make 1 allocation.
1271 var it = try argsWithAllocator(allocator);
1272 defer it.deinit();
1273
1274 var contents = std.array_list.Managed(u8).init(allocator);
1275 defer contents.deinit();
1276
1277 var slice_list = std.array_list.Managed(usize).init(allocator);
1278 defer slice_list.deinit();
1279
1280 while (it.next()) |arg| {
1281 try contents.appendSlice(arg[0 .. arg.len + 1]);
1282 try slice_list.append(arg.len);
1283 }
1284
1285 const contents_slice = contents.items;
1286 const slice_sizes = slice_list.items;
1287 const slice_list_bytes = try math.mul(usize, @sizeOf([]u8), slice_sizes.len);
1288 const total_bytes = try math.add(usize, slice_list_bytes, contents_slice.len);
1289 const buf = try allocator.alignedAlloc(u8, .of([]u8), total_bytes);
1290 errdefer allocator.free(buf);
1291
1292 const result_slice_list = mem.bytesAsSlice([:0]u8, buf[0..slice_list_bytes]);
1293 const result_contents = buf[slice_list_bytes..];
1294 @memcpy(result_contents[0..contents_slice.len], contents_slice);
1295
1296 var contents_index: usize = 0;
1297 for (slice_sizes, 0..) |len, i| {
1298 const new_index = contents_index + len;
1299 result_slice_list[i] = result_contents[contents_index..new_index :0];
1300 contents_index = new_index + 1;
1301 }
1302
1303 return result_slice_list;
1304}
1305
1306pub fn argsFree(allocator: Allocator, args_alloc: []const [:0]u8) void {
1307 var total_bytes: usize = 0;
1308 for (args_alloc) |arg| {
1309 total_bytes += @sizeOf([]u8) + arg.len + 1;
1310 }
1311 const unaligned_allocated_buf = @as([*]const u8, @ptrCast(args_alloc.ptr))[0..total_bytes];
1312 const aligned_allocated_buf: []align(@alignOf([]u8)) const u8 = @alignCast(unaligned_allocated_buf);
1313 return allocator.free(aligned_allocated_buf);
1314}
1315
1316test ArgIteratorWindows {
1317 const t = testArgIteratorWindows;
1318
1319 try t(
1320 \\"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\")"
1321 , &.{
1322 \\C:\Program Files\zig\zig.exe
1323 ,
1324 \\run
1325 ,
1326 \\.\src\main.zig
1327 ,
1328 \\-target
1329 ,
1330 \\x86_64-windows-gnu
1331 ,
1332 \\-O
1333 ,
1334 \\ReleaseSafe
1335 ,
1336 \\--
1337 ,
1338 \\--emoji=🗿
1339 ,
1340 \\--eval=new Regex("Dwayne \"The Rock\" Johnson")
1341 ,
1342 });
1343
1344 // Empty
1345 try t("", &.{});
1346
1347 // Separators
1348 try t("aa bb cc", &.{ "aa", "bb", "cc" });
1349 try t("aa\tbb\tcc", &.{ "aa", "bb", "cc" });
1350 try t("aa\nbb\ncc", &.{"aa\nbb\ncc"});
1351 try t("aa\r\nbb\r\ncc", &.{"aa\r\nbb\r\ncc"});
1352 try t("aa\rbb\rcc", &.{"aa\rbb\rcc"});
1353 try t("aa\x07bb\x07cc", &.{"aa\x07bb\x07cc"});
1354 try t("aa\x7Fbb\x7Fcc", &.{"aa\x7Fbb\x7Fcc"});
1355 try t("aa🦎bb🦎cc", &.{"aa🦎bb🦎cc"});
1356
1357 // Leading/trailing whitespace
1358 try t(" ", &.{""});
1359 try t(" aa bb ", &.{ "", "aa", "bb" });
1360 try t("\t\t", &.{""});
1361 try t("\t\taa\t\tbb\t\t", &.{ "", "aa", "bb" });
1362 try t("\n\n", &.{"\n\n"});
1363 try t("\n\naa\n\nbb\n\n", &.{"\n\naa\n\nbb\n\n"});
1364
1365 // Executable name with quotes/backslashes
1366 try t("\"aa bb\tcc\ndd\"", &.{"aa bb\tcc\ndd"});
1367 try t("\"", &.{""});
1368 try t("\"\"", &.{""});
1369 try t("\"\"\"", &.{""});
1370 try t("\"\"\"\"", &.{""});
1371 try t("\"\"\"\"\"", &.{""});
1372 try t("aa\"bb\"cc\"dd", &.{"aabbccdd"});
1373 try t("aa\"bb cc\"dd", &.{"aabb ccdd"});
1374 try t("\"aa\\\"bb\"", &.{"aa\\bb"});
1375 try t("\"aa\\\\\"", &.{"aa\\\\"});
1376 try t("aa\\\"bb", &.{"aa\\bb"});
1377 try t("aa\\\\\"bb", &.{"aa\\\\bb"});
1378
1379 // Arguments with quotes/backslashes
1380 try t(". \"aa bb\tcc\ndd\"", &.{ ".", "aa bb\tcc\ndd" });
1381 try t(". aa\" \"bb\"\t\"cc\"\n\"dd\"", &.{ ".", "aa bb\tcc\ndd" });
1382 try t(". ", &.{"."});
1383 try t(". \"", &.{ ".", "" });
1384 try t(". \"\"", &.{ ".", "" });
1385 try t(". \"\"\"", &.{ ".", "\"" });
1386 try t(". \"\"\"\"", &.{ ".", "\"" });
1387 try t(". \"\"\"\"\"", &.{ ".", "\"\"" });
1388 try t(". \"\"\"\"\"\"", &.{ ".", "\"\"" });
1389 try t(". \" \"", &.{ ".", " " });
1390 try t(". \" \"\"", &.{ ".", " \"" });
1391 try t(". \" \"\"\"", &.{ ".", " \"" });
1392 try t(". \" \"\"\"\"", &.{ ".", " \"\"" });
1393 try t(". \" \"\"\"\"\"", &.{ ".", " \"\"" });
1394 try t(". \" \"\"\"\"\"\"", &.{ ".", " \"\"\"" });
1395 try t(". \\\"", &.{ ".", "\"" });
1396 try t(". \\\"\"", &.{ ".", "\"" });
1397 try t(". \\\"\"\"", &.{ ".", "\"" });
1398 try t(". \\\"\"\"\"", &.{ ".", "\"\"" });
1399 try t(". \\\"\"\"\"\"", &.{ ".", "\"\"" });
1400 try t(". \\\"\"\"\"\"\"", &.{ ".", "\"\"\"" });
1401 try t(". \" \\\"", &.{ ".", " \"" });
1402 try t(". \" \\\"\"", &.{ ".", " \"" });
1403 try t(". \" \\\"\"\"", &.{ ".", " \"\"" });
1404 try t(". \" \\\"\"\"\"", &.{ ".", " \"\"" });
1405 try t(". \" \\\"\"\"\"\"", &.{ ".", " \"\"\"" });
1406 try t(". \" \\\"\"\"\"\"\"", &.{ ".", " \"\"\"" });
1407 try t(". aa\\bb\\\\cc\\\\\\dd", &.{ ".", "aa\\bb\\\\cc\\\\\\dd" });
1408 try t(". \\\\\\\"aa bb\"", &.{ ".", "\\\"aa", "bb" });
1409 try t(". \\\\\\\\\"aa bb\"", &.{ ".", "\\\\aa bb" });
1410
1411 // From https://learn.microsoft.com/en-us/cpp/cpp/main-function-command-line-args#results-of-parsing-command-lines
1412 try t(
1413 \\foo.exe "abc" d e
1414 , &.{ "foo.exe", "abc", "d", "e" });
1415 try t(
1416 \\foo.exe a\\b d"e f"g h
1417 , &.{ "foo.exe", "a\\\\b", "de fg", "h" });
1418 try t(
1419 \\foo.exe a\\\"b c d
1420 , &.{ "foo.exe", "a\\\"b", "c", "d" });
1421 try t(
1422 \\foo.exe a\\\\"b c" d e
1423 , &.{ "foo.exe", "a\\\\b c", "d", "e" });
1424 try t(
1425 \\foo.exe a"b"" c d
1426 , &.{ "foo.exe", "ab\" c d" });
1427
1428 // From https://daviddeley.com/autohotkey/parameters/parameters.htm#WINCRULESEX
1429 try t("foo.exe CallMeIshmael", &.{ "foo.exe", "CallMeIshmael" });
1430 try t("foo.exe \"Call Me Ishmael\"", &.{ "foo.exe", "Call Me Ishmael" });
1431 try t("foo.exe Cal\"l Me I\"shmael", &.{ "foo.exe", "Call Me Ishmael" });
1432 try t("foo.exe CallMe\\\"Ishmael", &.{ "foo.exe", "CallMe\"Ishmael" });
1433 try t("foo.exe \"CallMe\\\"Ishmael\"", &.{ "foo.exe", "CallMe\"Ishmael" });
1434 try t("foo.exe \"Call Me Ishmael\\\\\"", &.{ "foo.exe", "Call Me Ishmael\\" });
1435 try t("foo.exe \"CallMe\\\\\\\"Ishmael\"", &.{ "foo.exe", "CallMe\\\"Ishmael" });
1436 try t("foo.exe a\\\\\\b", &.{ "foo.exe", "a\\\\\\b" });
1437 try t("foo.exe \"a\\\\\\b\"", &.{ "foo.exe", "a\\\\\\b" });
1438
1439 // Surrogate pair encoding of 𐐷 separated by quotes.
1440 // Encoded as WTF-16:
1441 // "<0xD801>"<0xDC37>
1442 // Encoded as WTF-8:
1443 // "<0xED><0xA0><0x81>"<0xED><0xB0><0xB7>
1444 // During parsing, the quotes drop out and the surrogate pair
1445 // should end up encoded as its normal UTF-8 representation.
1446 try t("foo.exe \"\xed\xa0\x81\"\xed\xb0\xb7", &.{ "foo.exe", "𐐷" });
1447}
1448
1449fn testArgIteratorWindows(cmd_line: []const u8, expected_args: []const []const u8) !void {
1450 const cmd_line_w = try unicode.wtf8ToWtf16LeAllocZ(testing.allocator, cmd_line);
1451 defer testing.allocator.free(cmd_line_w);
1452
1453 // next
1454 {
1455 var it = try ArgIteratorWindows.init(testing.allocator, cmd_line_w);
1456 defer it.deinit();
1457
1458 for (expected_args) |expected| {
1459 if (it.next()) |actual| {
1460 try testing.expectEqualStrings(expected, actual);
1461 } else {
1462 return error.TestUnexpectedResult;
1463 }
1464 }
1465 try testing.expect(it.next() == null);
1466 }
1467
1468 // skip
1469 {
1470 var it = try ArgIteratorWindows.init(testing.allocator, cmd_line_w);
1471 defer it.deinit();
1472
1473 for (0..expected_args.len) |_| {
1474 try testing.expect(it.skip());
1475 }
1476 try testing.expect(!it.skip());
1477 }
1478}
1479
1480test "general arg parsing" {
1481 try testGeneralCmdLine("a b\tc d", &.{ "a", "b", "c", "d" });
1482 try testGeneralCmdLine("\"abc\" d e", &.{ "abc", "d", "e" });
1483 try testGeneralCmdLine("a\\\\\\b d\"e f\"g h", &.{ "a\\\\\\b", "de fg", "h" });
1484 try testGeneralCmdLine("a\\\\\\\"b c d", &.{ "a\\\"b", "c", "d" });
1485 try testGeneralCmdLine("a\\\\\\\\\"b c\" d e", &.{ "a\\\\b c", "d", "e" });
1486 try testGeneralCmdLine("a b\tc \"d f", &.{ "a", "b", "c", "d f" });
1487 try testGeneralCmdLine("j k l\\", &.{ "j", "k", "l\\" });
1488 try testGeneralCmdLine("\"\" x y z\\\\", &.{ "", "x", "y", "z\\\\" });
1489
1490 try testGeneralCmdLine("\".\\..\\zig-cache\\build\" \"bin\\zig.exe\" \".\\..\" \".\\..\\zig-cache\" \"--help\"", &.{
1491 ".\\..\\zig-cache\\build",
1492 "bin\\zig.exe",
1493 ".\\..",
1494 ".\\..\\zig-cache",
1495 "--help",
1496 });
1497
1498 try testGeneralCmdLine(
1499 \\ 'foo' "bar"
1500 , &.{ "'foo'", "bar" });
1501}
1502
1503fn testGeneralCmdLine(input_cmd_line: []const u8, expected_args: []const []const u8) !void {
1504 var it = try ArgIteratorGeneral(.{}).init(std.testing.allocator, input_cmd_line);
1505 defer it.deinit();
1506 for (expected_args) |expected_arg| {
1507 const arg = it.next().?;
1508 try testing.expectEqualStrings(expected_arg, arg);
1509 }
1510 try testing.expect(it.next() == null);
1511}
1512
1513test "response file arg parsing" {
1514 try testResponseFileCmdLine(
1515 \\a b
1516 \\c d\
1517 , &.{ "a", "b", "c", "d\\" });
1518 try testResponseFileCmdLine("a b c d\\", &.{ "a", "b", "c", "d\\" });
1519
1520 try testResponseFileCmdLine(
1521 \\j
1522 \\ k l # this is a comment \\ \\\ \\\\ "none" "\\" "\\\"
1523 \\ "m" #another comment
1524 \\
1525 , &.{ "j", "k", "l", "m" });
1526
1527 try testResponseFileCmdLine(
1528 \\ "" q ""
1529 \\ "r s # t" "u\" v" #another comment
1530 \\
1531 , &.{ "", "q", "", "r s # t", "u\" v" });
1532
1533 try testResponseFileCmdLine(
1534 \\ -l"advapi32" a# b#c d#
1535 \\e\\\
1536 , &.{ "-ladvapi32", "a#", "b#c", "d#", "e\\\\\\" });
1537
1538 try testResponseFileCmdLine(
1539 \\ 'foo' "bar"
1540 , &.{ "foo", "bar" });
1541}
1542
1543fn testResponseFileCmdLine(input_cmd_line: []const u8, expected_args: []const []const u8) !void {
1544 var it = try ArgIteratorGeneral(.{ .comments = true, .single_quotes = true })
1545 .init(std.testing.allocator, input_cmd_line);
1546 defer it.deinit();
1547 for (expected_args) |expected_arg| {
1548 const arg = it.next().?;
1549 try testing.expectEqualStrings(expected_arg, arg);
1550 }
1551 try testing.expect(it.next() == null);
1552}
1553
1554pub const UserInfo = struct {108pub const UserInfo = struct {
1555 uid: posix.uid_t,109 uid: posix.uid_t,
1556 gid: posix.gid_t,110 gid: posix.gid_t,
...@@ -1706,71 +260,278 @@ pub fn getBaseAddress() usize {...@@ -1706,71 +260,278 @@ pub fn getBaseAddress() usize {
1706 }260 }
1707}261}
1708262
1709/// Tells whether calling the `execv` or `execve` functions will be a compile error.263/// Tells whether the target operating system supports replacing the current
1710pub const can_execv = switch (native_os) {264/// process image. If this is `false` then calling `replace` or `replaceFile`
265/// functions will return `error.OperationUnsupported`.
266pub const can_replace = switch (native_os) {
1711 .windows, .haiku, .wasi => false,267 .windows, .haiku, .wasi => false,
1712 else => true,268 else => true,
1713};269};
1714270
1715/// Tells whether spawning child processes is supported (e.g. via Child)271/// Tells whether spawning child processes is supported.
1716pub const can_spawn = switch (native_os) {272pub const can_spawn = switch (native_os) {
1717 .wasi, .ios, .tvos, .visionos, .watchos => false,273 .wasi, .ios, .tvos, .visionos, .watchos => false,
1718 else => true,274 else => true,
1719};275};
1720276
1721pub const ExecvError = std.posix.ExecveError || error{OutOfMemory};277pub const ReplaceError = error{
1722278 /// The target operating system cannot replace the process image with a new
1723/// Replaces the current process image with the executed process.279 /// one.
1724/// This function must allocate memory to add a null terminating bytes on path and each arg.280 OperationUnsupported,
1725/// It must also convert to KEY=VALUE\0 format for environment variables, and include null281 SystemResources,
1726/// pointers after the args and after the environment variables.282 AccessDenied,
1727/// `argv[0]` is the executable path.283 PermissionDenied,
1728/// This function also uses the PATH environment variable to get the full path to the executable.284 InvalidExe,
1729/// Due to the heap-allocation, it is illegal to call this function in a fork() child.285 FileSystem,
1730/// For that use case, use the `std.posix` functions directly.286 IsDir,
1731pub fn execv(allocator: Allocator, argv: []const []const u8) ExecvError {287 FileNotFound,
1732 return execve(allocator, argv, null);288 NotDir,
289 FileBusy,
290 ProcessFdQuotaExceeded,
291 SystemFdQuotaExceeded,
292} || Allocator.Error || Io.Dir.PathNameError || Io.Cancelable || Io.UnexpectedError;
293
294pub const ReplaceOptions = struct {
295 argv: []const []const u8,
296 expand_arg0: ArgExpansion = .no_expand,
297 /// Replaces the environment when provided. The PATH value from here is
298 /// never used to resolve `argv[0]`.
299 environ_map: ?*const Environ.Map = null,
300};
301
302/// Replaces the current process image with the executed process. If this
303/// function succeeds, it does not return.
304///
305/// `argv[0]` is the name of the process to replace the current one with. If it
306/// is not already a file path (i.e. it contains '/'), it is resolved into a
307/// file path based on PATH from the parent environment.
308///
309/// It is illegal to call this function in a fork() child.
310pub fn replace(io: Io, options: ReplaceOptions) ReplaceError {
311 return io.vtable.processReplace(io.userdata, options);
1733}312}
1734313
1735/// Replaces the current process image with the executed process.314/// Replaces the current process image with the executed process. If this
1736/// This function must allocate memory to add a null terminating bytes on path and each arg.315/// function succeeds, it does not return.
1737/// It must also convert to KEY=VALUE\0 format for environment variables, and include null316///
1738/// pointers after the args and after the environment variables.317/// `argv[0]` is the file path of the process to replace the current one with,
1739/// `argv[0]` is the executable path.318/// relative to `dir`. It is *always* treated as a file path, even if it does
1740/// This function also uses the PATH environment variable to get the full path to the executable.319/// not contain '/'.
1741/// Due to the heap-allocation, it is illegal to call this function in a fork() child.320///
1742/// For that use case, use the `std.posix` functions directly.321/// It is illegal to call this function in a fork() child.
1743pub fn execve(322pub fn replacePath(io: Io, dir: Io.Dir, options: ReplaceOptions) ReplaceError {
1744 allocator: Allocator,323 return io.vtable.processReplacePath(io.userdata, dir, options);
324}
325
326pub const ArgExpansion = enum { expand, no_expand };
327
328/// File name extensions supported natively by `CreateProcess()` on Windows.
329pub const WindowsExtension = enum { bat, cmd, com, exe };
330
331pub const SpawnError = error{
332 /// The operating system does not support creating child processes.
333 OperationUnsupported,
334 OutOfMemory,
335 /// POSIX-only. `StdIo.ignore` was selected and opening `/dev/null` returned ENODEV.
336 NoDevice,
337 /// Windows-only. `cwd` or `argv` was provided and it was invalid WTF-8.
338 /// https://wtf-8.codeberg.page/
339 InvalidWtf8,
340 /// Windows-only. `cwd` was provided, but the path did not exist when spawning the child process.
341 CurrentWorkingDirectoryUnlinked,
342 /// Windows-only. NUL (U+0000), LF (U+000A), CR (U+000D) are not allowed
343 /// within arguments when executing a `.bat`/`.cmd` script.
344 /// - NUL/LF signifiies end of arguments, so anything afterwards
345 /// would be lost after execution.
346 /// - CR is stripped by `cmd.exe`, so any CR codepoints
347 /// would be lost after execution.
348 InvalidBatchScriptArg,
349 SystemResources,
350 AccessDenied,
351 PermissionDenied,
352 InvalidExe,
353 FileSystem,
354 IsDir,
355 FileNotFound,
356 NotDir,
357 FileBusy,
358 ProcessFdQuotaExceeded,
359 SystemFdQuotaExceeded,
360 ResourceLimitReached,
361 InvalidUserId,
362 InvalidProcessGroupId,
363 SymLinkLoop,
364 InvalidName,
365 /// An attempt was made to change the process group ID of one of the
366 /// children of the calling process and the child had already performed an
367 /// image replacement.
368 ProcessAlreadyExec,
369} || Io.Dir.PathNameError || Io.Cancelable || Io.UnexpectedError;
370
371pub const SpawnOptions = struct {
1745 argv: []const []const u8,372 argv: []const []const u8,
1746 env_map: ?*const EnvMap,373
1747) ExecvError {374 /// Set to change the current working directory when spawning the child process.
1748 if (!can_execv) @compileError("The target OS does not support execv");375 cwd: ?[]const u8 = null,
1749376 /// Set to change the current working directory when spawning the child process.
1750 var arena_allocator = std.heap.ArenaAllocator.init(allocator);377 /// This is not yet implemented for Windows. See https://github.com/ziglang/zig/issues/5190
1751 defer arena_allocator.deinit();378 /// Once that is done, `cwd` will be deprecated in favor of this field.
1752 const arena = arena_allocator.allocator();379 cwd_dir: ?Io.Dir = null,
1753380 /// Replaces the child environment when provided. The PATH value from here
1754 const argv_buf = try arena.allocSentinel(?[*:0]const u8, argv.len, null);381 /// is not used to resolve `argv[0]`; that resolution always uses parent
1755 for (argv, 0..) |arg, i| argv_buf[i] = (try arena.dupeZ(u8, arg)).ptr;382 /// environment.
1756383 environ_map: ?*const Environ.Map = null,
1757 const envp = m: {384 expand_arg0: ArgExpansion = .no_expand,
1758 if (env_map) |m| {385 /// When populated, a pipe will be created for the child process to
1759 const envp_buf = try createNullDelimitedEnvMap(arena, m);386 /// communicate progress back to the parent. The file descriptor of the
1760 break :m envp_buf.ptr;387 /// write end of the pipe will be specified in the `ZIG_PROGRESS`
1761 } else if (builtin.link_libc) {388 /// environment variable inside the child process. The progress reported by
1762 break :m std.c.environ;389 /// the child will be attached to this progress node in the parent process.
1763 } else if (builtin.output_mode == .Exe) {390 ///
1764 // Then we have Zig start code and this works.391 /// The child's progress tree will be grafted into the parent's progress tree,
1765 // TODO type-safety for null-termination of `os.environ`.392 /// by substituting this node with the child's root node.
1766 break :m @as([*:null]const ?[*:0]const u8, @ptrCast(std.os.environ.ptr));393 progress_node: std.Progress.Node = std.Progress.Node.none,
1767 } else {394
1768 // TODO come up with a solution for this.395 stdin: StdIo = .inherit,
1769 @compileError("missing std lib enhancement: std.process.execv implementation has no way to collect the environment variables to forward to the child process");396 stdout: StdIo = .inherit,
1770 }397 stderr: StdIo = .inherit,
398
399 /// Set to true to obtain rusage information for the child process.
400 /// Depending on the target platform and implementation status, the
401 /// requested statistics may or may not be available. If they are
402 /// available, then the `resource_usage_statistics` field will be populated
403 /// after calling `wait`.
404 /// On Linux and Darwin, this obtains rusage statistics from wait4().
405 request_resource_usage_statistics: bool = false,
406
407 /// Set to change the user id when spawning the child process.
408 uid: ?posix.uid_t = null,
409 /// Set to change the group id when spawning the child process.
410 gid: ?posix.gid_t = null,
411 /// Set to change the process group id when spawning the child process.
412 pgid: ?posix.pid_t = null,
413
414 /// Start child process in suspended state.
415 /// For Posix systems it's started as if SIGSTOP was sent.
416 start_suspended: bool = false,
417 /// Windows-only. Sets the CREATE_NO_WINDOW flag in CreateProcess.
418 create_no_window: bool = false,
419 /// Darwin-only. Disable ASLR for the child process.
420 disable_aslr: bool = false,
421
422 /// Behavior of the child process's standard input, output, and error streams.
423 pub const StdIo = union(enum) {
424 /// Inherit the corresponding stream from the parent process.
425 inherit,
426 /// Pass an already open file from the parent to the child.
427 file: File,
428 /// Pass a null stream to the child process by opening "/dev/null" on POSIX
429 /// and "NUL" on Windows.
430 ignore,
431 /// Create a new pipe for the stream.
432 ///
433 /// The corresponding field (`stdout`, `stderr`, or `stdin`) will be
434 /// assigned a `File` object that can be used to read from or write to the
435 /// pipe.
436 pipe,
437 /// Spawn the child process with the corresponding stream missing. This
438 /// will likely result in the child encountering EBADF if it tries to use
439 /// stdin, stdout, or stderr, or if only one stream is closed, it will
440 /// result in them getting mixed up. Generally, this option is for advanced
441 /// use cases only.
442 close,
1771 };443 };
444};
445
446/// Creates a child process.
447///
448/// `argv[0]` is the name of the program to execute. If it is not already a
449/// file path (i.e. it contains '/'), it is resolved into a file path based on
450/// PATH from the parent environment.
451pub fn spawn(io: Io, options: SpawnOptions) SpawnError!Child {
452 return io.vtable.processSpawn(io.userdata, options);
453}
1772454
1773 return posix.execvpeZ_expandArg0(.no_expand, argv_buf.ptr[0].?, argv_buf.ptr, envp);455/// Creates a child process.
456///
457/// `argv[0]` is the file path of the program to execute, relative to `dir`. It
458/// is *always* treated as a file path, even if it does not contain '/'.
459pub fn spawnPath(io: Io, dir: Io.Dir, options: SpawnOptions) SpawnError!Child {
460 return io.vtable.processSpawnPath(io.userdata, dir, options);
461}
462
463pub const RunError = posix.GetCwdError || posix.ReadError || SpawnError || posix.PollError || error{
464 StdoutStreamTooLong,
465 StderrStreamTooLong,
466};
467
468pub const RunOptions = struct {
469 argv: []const []const u8,
470 max_output_bytes: usize = 50 * 1024,
471
472 /// Set to change the current working directory when spawning the child process.
473 cwd: ?[]const u8 = null,
474 /// Set to change the current working directory when spawning the child process.
475 /// This is not yet implemented for Windows. See https://github.com/ziglang/zig/issues/5190
476 /// Once that is done, `cwd` will be deprecated in favor of this field.
477 cwd_dir: ?Io.Dir = null,
478 /// Replaces the child environment when provided. The PATH value from here
479 /// is not used to resolve `argv[0]`; that resolution always uses parent
480 /// environment.
481 environ_map: ?*const Environ.Map = null,
482 expand_arg0: ArgExpansion = .no_expand,
483 /// When populated, a pipe will be created for the child process to
484 /// communicate progress back to the parent. The file descriptor of the
485 /// write end of the pipe will be specified in the `ZIG_PROGRESS`
486 /// environment variable inside the child process. The progress reported by
487 /// the child will be attached to this progress node in the parent process.
488 ///
489 /// The child's progress tree will be grafted into the parent's progress tree,
490 /// by substituting this node with the child's root node.
491 progress_node: std.Progress.Node = std.Progress.Node.none,
492 /// Windows-only. Sets the CREATE_NO_WINDOW flag in CreateProcess.
493 create_no_window: bool = true,
494 /// Darwin-only. Disable ASLR for the child process.
495 disable_aslr: bool = false,
496};
497
498pub const RunResult = struct {
499 term: Child.Term,
500 stdout: []u8,
501 stderr: []u8,
502};
503
504/// Spawns a child process, waits for it, collecting stdout and stderr, and then returns.
505/// If it succeeds, the caller owns result.stdout and result.stderr memory.
506pub fn run(gpa: Allocator, io: Io, options: RunOptions) RunError!RunResult {
507 var child = try spawn(io, .{
508 .argv = options.argv,
509 .cwd = options.cwd,
510 .cwd_dir = options.cwd_dir,
511 .environ_map = options.environ_map,
512 .expand_arg0 = options.expand_arg0,
513 .progress_node = options.progress_node,
514 .create_no_window = options.create_no_window,
515 .disable_aslr = options.disable_aslr,
516
517 .stdin = .ignore,
518 .stdout = .pipe,
519 .stderr = .pipe,
520 });
521 defer child.kill(io);
522
523 var stdout: std.ArrayList(u8) = .empty;
524 defer stdout.deinit(gpa);
525 var stderr: std.ArrayList(u8) = .empty;
526 defer stderr.deinit(gpa);
527
528 try child.collectOutput(gpa, &stdout, &stderr, options.max_output_bytes);
529
530 return .{
531 .stdout = try stdout.toOwnedSlice(gpa),
532 .stderr = try stderr.toOwnedSlice(gpa),
533 .term = try child.wait(io),
534 };
1774}535}
1775536
1776pub const TotalSystemMemoryError = error{537pub const TotalSystemMemoryError = error{
...@@ -1903,215 +664,6 @@ test raiseFileDescriptorLimit {...@@ -1903,215 +664,6 @@ test raiseFileDescriptorLimit {
1903 raiseFileDescriptorLimit();664 raiseFileDescriptorLimit();
1904}665}
1905666
1906pub const CreateEnvironOptions = struct {
1907 /// `null` means to leave the `ZIG_PROGRESS` environment variable unmodified.
1908 /// If non-null, negative means to remove the environment variable, and >= 0
1909 /// means to provide it with the given integer.
1910 zig_progress_fd: ?i32 = null,
1911};
1912
1913/// Creates a null-delimited environment variable block in the format
1914/// expected by POSIX, from a hash map plus options.
1915pub fn createEnvironFromMap(
1916 arena: Allocator,
1917 map: *const EnvMap,
1918 options: CreateEnvironOptions,
1919) Allocator.Error![:null]?[*:0]u8 {
1920 const ZigProgressAction = enum { nothing, edit, delete, add };
1921 const zig_progress_action: ZigProgressAction = a: {
1922 const fd = options.zig_progress_fd orelse break :a .nothing;
1923 const contains = map.get("ZIG_PROGRESS") != null;
1924 if (fd >= 0) {
1925 break :a if (contains) .edit else .add;
1926 } else {
1927 if (contains) break :a .delete;
1928 }
1929 break :a .nothing;
1930 };
1931
1932 const envp_count: usize = c: {
1933 var count: usize = map.count();
1934 switch (zig_progress_action) {
1935 .add => count += 1,
1936 .delete => count -= 1,
1937 .nothing, .edit => {},
1938 }
1939 break :c count;
1940 };
1941
1942 const envp_buf = try arena.allocSentinel(?[*:0]u8, envp_count, null);
1943 var i: usize = 0;
1944
1945 if (zig_progress_action == .add) {
1946 envp_buf[i] = try std.fmt.allocPrintSentinel(arena, "ZIG_PROGRESS={d}", .{options.zig_progress_fd.?}, 0);
1947 i += 1;
1948 }
1949
1950 {
1951 var it = map.iterator();
1952 while (it.next()) |pair| {
1953 if (mem.eql(u8, pair.key_ptr.*, "ZIG_PROGRESS")) switch (zig_progress_action) {
1954 .add => unreachable,
1955 .delete => continue,
1956 .edit => {
1957 envp_buf[i] = try std.fmt.allocPrintSentinel(arena, "{s}={d}", .{
1958 pair.key_ptr.*, options.zig_progress_fd.?,
1959 }, 0);
1960 i += 1;
1961 continue;
1962 },
1963 .nothing => {},
1964 };
1965
1966 envp_buf[i] = try std.fmt.allocPrintSentinel(arena, "{s}={s}", .{ pair.key_ptr.*, pair.value_ptr.* }, 0);
1967 i += 1;
1968 }
1969 }
1970
1971 assert(i == envp_count);
1972 return envp_buf;
1973}
1974
1975/// Creates a null-delimited environment variable block in the format
1976/// expected by POSIX, from a hash map plus options.
1977pub fn createEnvironFromExisting(
1978 arena: Allocator,
1979 existing: [*:null]const ?[*:0]const u8,
1980 options: CreateEnvironOptions,
1981) Allocator.Error![:null]?[*:0]u8 {
1982 const existing_count, const contains_zig_progress = c: {
1983 var count: usize = 0;
1984 var contains = false;
1985 while (existing[count]) |line| : (count += 1) {
1986 contains = contains or mem.eql(u8, mem.sliceTo(line, '='), "ZIG_PROGRESS");
1987 }
1988 break :c .{ count, contains };
1989 };
1990 const ZigProgressAction = enum { nothing, edit, delete, add };
1991 const zig_progress_action: ZigProgressAction = a: {
1992 const fd = options.zig_progress_fd orelse break :a .nothing;
1993 if (fd >= 0) {
1994 break :a if (contains_zig_progress) .edit else .add;
1995 } else {
1996 if (contains_zig_progress) break :a .delete;
1997 }
1998 break :a .nothing;
1999 };
2000
2001 const envp_count: usize = c: {
2002 var count: usize = existing_count;
2003 switch (zig_progress_action) {
2004 .add => count += 1,
2005 .delete => count -= 1,
2006 .nothing, .edit => {},
2007 }
2008 break :c count;
2009 };
2010
2011 const envp_buf = try arena.allocSentinel(?[*:0]u8, envp_count, null);
2012 var i: usize = 0;
2013 var existing_index: usize = 0;
2014
2015 if (zig_progress_action == .add) {
2016 envp_buf[i] = try std.fmt.allocPrintSentinel(arena, "ZIG_PROGRESS={d}", .{options.zig_progress_fd.?}, 0);
2017 i += 1;
2018 }
2019
2020 while (existing[existing_index]) |line| : (existing_index += 1) {
2021 if (mem.eql(u8, mem.sliceTo(line, '='), "ZIG_PROGRESS")) switch (zig_progress_action) {
2022 .add => unreachable,
2023 .delete => continue,
2024 .edit => {
2025 envp_buf[i] = try std.fmt.allocPrintSentinel(arena, "ZIG_PROGRESS={d}", .{options.zig_progress_fd.?}, 0);
2026 i += 1;
2027 continue;
2028 },
2029 .nothing => {},
2030 };
2031 envp_buf[i] = try arena.dupeZ(u8, mem.span(line));
2032 i += 1;
2033 }
2034
2035 assert(i == envp_count);
2036 return envp_buf;
2037}
2038
2039pub fn createNullDelimitedEnvMap(arena: mem.Allocator, env_map: *const EnvMap) Allocator.Error![:null]?[*:0]u8 {
2040 return createEnvironFromMap(arena, env_map, .{});
2041}
2042
2043test createNullDelimitedEnvMap {
2044 const allocator = testing.allocator;
2045 var envmap = EnvMap.init(allocator);
2046 defer envmap.deinit();
2047
2048 try envmap.put("HOME", "/home/ifreund");
2049 try envmap.put("WAYLAND_DISPLAY", "wayland-1");
2050 try envmap.put("DISPLAY", ":1");
2051 try envmap.put("DEBUGINFOD_URLS", " ");
2052 try envmap.put("XCURSOR_SIZE", "24");
2053
2054 var arena = std.heap.ArenaAllocator.init(allocator);
2055 defer arena.deinit();
2056 const environ = try createNullDelimitedEnvMap(arena.allocator(), &envmap);
2057
2058 try testing.expectEqual(@as(usize, 5), environ.len);
2059
2060 inline for (.{
2061 "HOME=/home/ifreund",
2062 "WAYLAND_DISPLAY=wayland-1",
2063 "DISPLAY=:1",
2064 "DEBUGINFOD_URLS= ",
2065 "XCURSOR_SIZE=24",
2066 }) |target| {
2067 for (environ) |variable| {
2068 if (mem.eql(u8, mem.span(variable orelse continue), target)) break;
2069 } else {
2070 try testing.expect(false); // Environment variable not found
2071 }
2072 }
2073}
2074
2075/// Caller must free result.
2076pub fn createWindowsEnvBlock(allocator: mem.Allocator, env_map: *const EnvMap) ![]u16 {
2077 // count bytes needed
2078 const max_chars_needed = x: {
2079 // Only need 2 trailing NUL code units for an empty environment
2080 var max_chars_needed: usize = if (env_map.count() == 0) 2 else 1;
2081 var it = env_map.iterator();
2082 while (it.next()) |pair| {
2083 // +1 for '='
2084 // +1 for null byte
2085 max_chars_needed += pair.key_ptr.len + pair.value_ptr.len + 2;
2086 }
2087 break :x max_chars_needed;
2088 };
2089 const result = try allocator.alloc(u16, max_chars_needed);
2090 errdefer allocator.free(result);
2091
2092 var it = env_map.iterator();
2093 var i: usize = 0;
2094 while (it.next()) |pair| {
2095 i += try unicode.wtf8ToWtf16Le(result[i..], pair.key_ptr.*);
2096 result[i] = '=';
2097 i += 1;
2098 i += try unicode.wtf8ToWtf16Le(result[i..], pair.value_ptr.*);
2099 result[i] = 0;
2100 i += 1;
2101 }
2102 result[i] = 0;
2103 i += 1;
2104 // An empty environment is a special case that requires a redundant
2105 // NUL terminator. CreateProcess will read the second code unit even
2106 // though theoretically the first should be enough to recognize that the
2107 // environment is empty (see https://nullprogram.com/blog/2023/08/23/)
2108 if (env_map.count() == 0) {
2109 result[i] = 0;
2110 i += 1;
2111 }
2112 return try allocator.realloc(result, i);
2113}
2114
2115/// Logs an error and then terminates the process with exit code 1.667/// Logs an error and then terminates the process with exit code 1.
2116pub fn fatal(comptime format: []const u8, format_arguments: anytype) noreturn {668pub fn fatal(comptime format: []const u8, format_arguments: anytype) noreturn {
2117 std.log.err(format, format_arguments);669 std.log.err(format, format_arguments);
lib/std/process/Args.zig created+976
...@@ -0,0 +1,976 @@
1const Args = @This();
2
3const builtin = @import("builtin");
4const native_os = builtin.os.tag;
5
6const std = @import("../std.zig");
7const Allocator = std.mem.Allocator;
8const assert = std.debug.assert;
9const testing = std.testing;
10
11vector: Vector,
12
13/// On WASI without libc, this is `void` because the environment has to be
14/// queried and heap-allocated at runtime.
15pub const Vector = switch (native_os) {
16 .windows => []const u16, // WTF-16 encoded
17 .wasi => switch (builtin.link_libc) {
18 false => void,
19 true => []const [*:0]const u8,
20 },
21 .freestanding, .other => void,
22 else => []const [*:0]const u8,
23};
24
25/// Cross-platform access to command line one argument at a time.
26pub const Iterator = struct {
27 const Inner = switch (native_os) {
28 .windows => Windows,
29 .wasi => if (builtin.link_libc) Posix else Wasi,
30 else => Posix,
31 };
32
33 inner: Inner,
34
35 /// Initialize the args iterator. Consider using `initAllocator` instead
36 /// for cross-platform compatibility.
37 pub fn init(a: Args) Iterator {
38 if (native_os == .wasi) @compileError("In WASI, use initAllocator instead.");
39 if (native_os == .windows) @compileError("In Windows, use initAllocator instead.");
40 return .{ .inner = .init(a) };
41 }
42
43 pub const InitError = Inner.InitError;
44
45 /// You must deinitialize iterator's internal buffers by calling `deinit` when done.
46 pub fn initAllocator(a: Args, gpa: Allocator) InitError!Iterator {
47 if (native_os == .wasi and !builtin.link_libc) {
48 return .{ .inner = try .init(gpa) };
49 }
50 if (native_os == .windows) {
51 return .{ .inner = try .init(gpa, a.vector) };
52 }
53
54 return .{ .inner = .init(a) };
55 }
56
57 /// Return subsequent argument, or `null` if no more remaining.
58 ///
59 /// Returned slice is pointing to the iterator's internal buffer.
60 /// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/).
61 /// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
62 pub fn next(it: *Iterator) ?[:0]const u8 {
63 return it.inner.next();
64 }
65
66 /// Parse past 1 argument without capturing it.
67 /// Returns `true` if skipped an arg, `false` if we are at the end.
68 pub fn skip(it: *Iterator) bool {
69 return it.inner.skip();
70 }
71
72 /// Required to release resources if the iterator was initialized with
73 /// `initAllocator` function.
74 pub fn deinit(it: *Iterator) void {
75 // Unless we're targeting WASI or Windows, this is a no-op.
76 if (native_os == .wasi and !builtin.link_libc) it.inner.deinit();
77 if (native_os == .windows) it.inner.deinit();
78 }
79
80 /// Iterator that implements the Windows command-line parsing algorithm.
81 ///
82 /// The implementation is intended to be compatible with the post-2008 C runtime,
83 /// but is *not* intended to be compatible with `CommandLineToArgvW` since
84 /// `CommandLineToArgvW` uses the pre-2008 parsing rules.
85 ///
86 /// This iterator faithfully implements the parsing behavior observed from the C runtime with
87 /// one exception: if the command-line string is empty, the iterator will immediately complete
88 /// without returning any arguments (whereas the C runtime will return a single argument
89 /// representing the name of the current executable).
90 ///
91 /// The essential parts of the algorithm are described in Microsoft's documentation:
92 ///
93 /// - https://learn.microsoft.com/en-us/cpp/cpp/main-function-command-line-args?view=msvc-170#parsing-c-command-line-arguments
94 ///
95 /// David Deley explains some additional undocumented quirks in great detail:
96 ///
97 /// - https://daviddeley.com/autohotkey/parameters/parameters.htm#WINCRULES
98 pub const Windows = struct {
99 allocator: Allocator,
100 /// Encoded as WTF-16 LE.
101 cmd_line: []const u16,
102 index: usize = 0,
103 /// Owned by the iterator. Long enough to hold contiguous NUL-terminated slices
104 /// of each argument encoded as WTF-8.
105 buffer: []u8,
106 start: usize = 0,
107 end: usize = 0,
108
109 pub const InitError = error{OutOfMemory};
110
111 /// `cmd_line_w` *must* be a WTF16-LE-encoded string.
112 ///
113 /// The iterator stores and uses `cmd_line_w`, so its memory must be valid for
114 /// at least as long as the returned Windows.
115 pub fn init(gpa: Allocator, cmd_line_w: []const u16) Windows.InitError!Windows {
116 const wtf8_len = std.unicode.calcWtf8Len(cmd_line_w);
117
118 // This buffer must be large enough to contain contiguous NUL-terminated slices
119 // of each argument.
120 // - During parsing, the length of a parsed argument will always be equal to
121 // to less than its unparsed length
122 // - The first argument needs one extra byte of space allocated for its NUL
123 // terminator, but for each subsequent argument the necessary whitespace
124 // between arguments guarantees room for their NUL terminator(s).
125 const buffer = try gpa.alloc(u8, wtf8_len + 1);
126 errdefer gpa.free(buffer);
127
128 return .{
129 .allocator = gpa,
130 .cmd_line = cmd_line_w,
131 .buffer = buffer,
132 };
133 }
134
135 /// Returns the next argument and advances the iterator. Returns `null` if at the end of the
136 /// command-line string. The iterator owns the returned slice.
137 /// The result is encoded as [WTF-8](https://wtf-8.codeberg.page/).
138 pub fn next(self: *Windows) ?[:0]const u8 {
139 return self.nextWithStrategy(next_strategy);
140 }
141
142 /// Skips the next argument and advances the iterator. Returns `true` if an argument was
143 /// skipped, `false` if at the end of the command-line string.
144 pub fn skip(self: *Windows) bool {
145 return self.nextWithStrategy(skip_strategy);
146 }
147
148 const next_strategy = struct {
149 const T = ?[:0]const u8;
150
151 const eof = null;
152
153 /// Returns '\' if any backslashes are emitted, otherwise returns `last_emitted_code_unit`.
154 fn emitBackslashes(self: *Windows, count: usize, last_emitted_code_unit: ?u16) ?u16 {
155 for (0..count) |_| {
156 self.buffer[self.end] = '\\';
157 self.end += 1;
158 }
159 return if (count != 0) '\\' else last_emitted_code_unit;
160 }
161
162 /// If `last_emitted_code_unit` and `code_unit` form a surrogate pair, then
163 /// the previously emitted high surrogate is overwritten by the codepoint encoded
164 /// by the surrogate pair, and `null` is returned.
165 /// Otherwise, `code_unit` is emitted and returned.
166 fn emitCharacter(self: *Windows, code_unit: u16, last_emitted_code_unit: ?u16) ?u16 {
167 // Because we are emitting WTF-8, we need to
168 // check to see if we've emitted two consecutive surrogate
169 // codepoints that form a valid surrogate pair in order
170 // to ensure that we're always emitting well-formed WTF-8
171 // (https://wtf-8.codeberg.page/#concatenating).
172 //
173 // If we do have a valid surrogate pair, we need to emit
174 // the UTF-8 sequence for the codepoint that they encode
175 // instead of the WTF-8 encoding for the two surrogate pairs
176 // separately.
177 //
178 // This is relevant when dealing with a WTF-16 encoded
179 // command line like this:
180 // "<0xD801>"<0xDC37>
181 // which would get parsed and converted to WTF-8 as:
182 // <0xED><0xA0><0x81><0xED><0xB0><0xB7>
183 // but instead, we need to recognize the surrogate pair
184 // and emit the codepoint it encodes, which in this
185 // example is U+10437 (𐐷), which is encoded in UTF-8 as:
186 // <0xF0><0x90><0x90><0xB7>
187 if (last_emitted_code_unit != null and
188 std.unicode.utf16IsLowSurrogate(code_unit) and
189 std.unicode.utf16IsHighSurrogate(last_emitted_code_unit.?))
190 {
191 const codepoint = std.unicode.utf16DecodeSurrogatePair(&.{ last_emitted_code_unit.?, code_unit }) catch unreachable;
192
193 // Unpaired surrogate is 3 bytes long
194 const dest = self.buffer[self.end - 3 ..];
195 const len = std.unicode.utf8Encode(codepoint, dest) catch unreachable;
196 // All codepoints that require a surrogate pair (> U+FFFF) are encoded as 4 bytes
197 assert(len == 4);
198 self.end += 1;
199 return null;
200 }
201
202 const wtf8_len = std.unicode.wtf8Encode(code_unit, self.buffer[self.end..]) catch unreachable;
203 self.end += wtf8_len;
204 return code_unit;
205 }
206
207 fn yieldArg(self: *Windows) [:0]const u8 {
208 self.buffer[self.end] = 0;
209 const arg = self.buffer[self.start..self.end :0];
210 self.end += 1;
211 self.start = self.end;
212 return arg;
213 }
214 };
215
216 const skip_strategy = struct {
217 const T = bool;
218
219 const eof = false;
220
221 fn emitBackslashes(_: *Windows, _: usize, last_emitted_code_unit: ?u16) ?u16 {
222 return last_emitted_code_unit;
223 }
224
225 fn emitCharacter(_: *Windows, _: u16, last_emitted_code_unit: ?u16) ?u16 {
226 return last_emitted_code_unit;
227 }
228
229 fn yieldArg(_: *Windows) bool {
230 return true;
231 }
232 };
233
234 fn nextWithStrategy(self: *Windows, comptime strategy: type) strategy.T {
235 var last_emitted_code_unit: ?u16 = null;
236 // The first argument (the executable name) uses different parsing rules.
237 if (self.index == 0) {
238 if (self.cmd_line.len == 0 or self.cmd_line[0] == 0) {
239 // Immediately complete the iterator.
240 // The C runtime would return the name of the current executable here.
241 return strategy.eof;
242 }
243
244 var inside_quotes = false;
245 while (true) : (self.index += 1) {
246 const char = if (self.index != self.cmd_line.len)
247 std.mem.littleToNative(u16, self.cmd_line[self.index])
248 else
249 0;
250 switch (char) {
251 0 => {
252 return strategy.yieldArg(self);
253 },
254 '"' => {
255 inside_quotes = !inside_quotes;
256 },
257 ' ', '\t' => {
258 if (inside_quotes) {
259 last_emitted_code_unit = strategy.emitCharacter(self, char, last_emitted_code_unit);
260 } else {
261 self.index += 1;
262 return strategy.yieldArg(self);
263 }
264 },
265 else => {
266 last_emitted_code_unit = strategy.emitCharacter(self, char, last_emitted_code_unit);
267 },
268 }
269 }
270 }
271
272 // Skip spaces and tabs. The iterator completes if we reach the end of the string here.
273 while (true) : (self.index += 1) {
274 const char = if (self.index != self.cmd_line.len)
275 std.mem.littleToNative(u16, self.cmd_line[self.index])
276 else
277 0;
278 switch (char) {
279 0 => return strategy.eof,
280 ' ', '\t' => continue,
281 else => break,
282 }
283 }
284
285 // Parsing rules for subsequent arguments:
286 //
287 // - The end of the string always terminates the current argument.
288 // - When not in 'inside_quotes' mode, a space or tab terminates the current argument.
289 // - 2n backslashes followed by a quote emit n backslashes (note: n can be zero).
290 // If in 'inside_quotes' and the quote is immediately followed by a second quote,
291 // one quote is emitted and the other is skipped, otherwise, the quote is skipped
292 // and 'inside_quotes' is toggled.
293 // - 2n + 1 backslashes followed by a quote emit n backslashes followed by a quote.
294 // - n backslashes not followed by a quote emit n backslashes.
295 var backslash_count: usize = 0;
296 var inside_quotes = false;
297 while (true) : (self.index += 1) {
298 const char = if (self.index != self.cmd_line.len)
299 std.mem.littleToNative(u16, self.cmd_line[self.index])
300 else
301 0;
302 switch (char) {
303 0 => {
304 last_emitted_code_unit = strategy.emitBackslashes(self, backslash_count, last_emitted_code_unit);
305 return strategy.yieldArg(self);
306 },
307 ' ', '\t' => {
308 last_emitted_code_unit = strategy.emitBackslashes(self, backslash_count, last_emitted_code_unit);
309 backslash_count = 0;
310 if (inside_quotes) {
311 last_emitted_code_unit = strategy.emitCharacter(self, char, last_emitted_code_unit);
312 } else return strategy.yieldArg(self);
313 },
314 '"' => {
315 const char_is_escaped_quote = backslash_count % 2 != 0;
316 last_emitted_code_unit = strategy.emitBackslashes(self, backslash_count / 2, last_emitted_code_unit);
317 backslash_count = 0;
318 if (char_is_escaped_quote) {
319 last_emitted_code_unit = strategy.emitCharacter(self, '"', last_emitted_code_unit);
320 } else {
321 if (inside_quotes and
322 self.index + 1 != self.cmd_line.len and
323 std.mem.littleToNative(u16, self.cmd_line[self.index + 1]) == '"')
324 {
325 last_emitted_code_unit = strategy.emitCharacter(self, '"', last_emitted_code_unit);
326 self.index += 1;
327 } else {
328 inside_quotes = !inside_quotes;
329 }
330 }
331 },
332 '\\' => {
333 backslash_count += 1;
334 },
335 else => {
336 last_emitted_code_unit = strategy.emitBackslashes(self, backslash_count, last_emitted_code_unit);
337 backslash_count = 0;
338 last_emitted_code_unit = strategy.emitCharacter(self, char, last_emitted_code_unit);
339 },
340 }
341 }
342 }
343
344 /// Frees the iterator's copy of the command-line string and all previously returned
345 /// argument slices.
346 pub fn deinit(self: *Windows) void {
347 self.allocator.free(self.buffer);
348 }
349 };
350
351 pub const Posix = struct {
352 remaining: Vector,
353
354 pub const InitError = error{};
355
356 pub fn init(a: Args) Posix {
357 return .{ .remaining = a.vector };
358 }
359
360 pub fn next(it: *Posix) ?[:0]const u8 {
361 if (it.remaining.len == 0) return null;
362 const arg = it.remaining[0];
363 it.remaining = it.remaining[1..];
364 return std.mem.sliceTo(arg, 0);
365 }
366
367 pub fn skip(it: *Posix) bool {
368 if (it.remaining.len == 0) return false;
369 it.remaining = it.remaining[1..];
370 return true;
371 }
372 };
373
374 pub const Wasi = struct {
375 allocator: Allocator,
376 index: usize,
377 args: [][:0]u8,
378
379 pub const InitError = error{OutOfMemory} || std.posix.UnexpectedError;
380
381 /// You must call deinit to free the internal buffer of the
382 /// iterator after you are done.
383 pub fn init(allocator: Allocator) Wasi.InitError!Wasi {
384 const fetched_args = try Wasi.internalInit(allocator);
385 return Wasi{
386 .allocator = allocator,
387 .index = 0,
388 .args = fetched_args,
389 };
390 }
391
392 fn internalInit(allocator: Allocator) Wasi.InitError![][:0]u8 {
393 var count: usize = undefined;
394 var buf_size: usize = undefined;
395
396 switch (std.os.wasi.args_sizes_get(&count, &buf_size)) {
397 .SUCCESS => {},
398 else => |err| return std.posix.unexpectedErrno(err),
399 }
400
401 if (count == 0) {
402 return &[_][:0]u8{};
403 }
404
405 const argv = try allocator.alloc([*:0]u8, count);
406 defer allocator.free(argv);
407
408 const argv_buf = try allocator.alloc(u8, buf_size);
409
410 switch (std.os.wasi.args_get(argv.ptr, argv_buf.ptr)) {
411 .SUCCESS => {},
412 else => |err| return std.posix.unexpectedErrno(err),
413 }
414
415 var result_args = try allocator.alloc([:0]u8, count);
416 var i: usize = 0;
417 while (i < count) : (i += 1) {
418 result_args[i] = std.mem.sliceTo(argv[i], 0);
419 }
420
421 return result_args;
422 }
423
424 pub fn next(self: *Wasi) ?[:0]const u8 {
425 if (self.index == self.args.len) return null;
426
427 const arg = self.args[self.index];
428 self.index += 1;
429 return arg;
430 }
431
432 pub fn skip(self: *Wasi) bool {
433 if (self.index == self.args.len) return false;
434
435 self.index += 1;
436 return true;
437 }
438
439 /// Call to free the internal buffer of the iterator.
440 pub fn deinit(self: *Wasi) void {
441 // Nothing is allocated when there are no args
442 if (self.args.len == 0) return;
443
444 const last_item = self.args[self.args.len - 1];
445 const last_byte_addr = @intFromPtr(last_item.ptr) + last_item.len + 1; // null terminated
446 const first_item_ptr = self.args[0].ptr;
447 const len = last_byte_addr - @intFromPtr(first_item_ptr);
448 self.allocator.free(first_item_ptr[0..len]);
449 self.allocator.free(self.args);
450 }
451 };
452};
453
454/// Holds the command-line arguments, with the program name as the first entry.
455/// Use `iterateAllocator` for cross-platform code.
456pub fn iterate(a: Args) Iterator {
457 return .init(a);
458}
459
460/// You must deinitialize iterator's internal buffers by calling `deinit` when
461/// done.
462pub fn iterateAllocator(a: Args, gpa: Allocator) Iterator.InitError!Iterator {
463 return .initAllocator(a, gpa);
464}
465
466pub const ToSliceError = Iterator.Windows.InitError || Iterator.Wasi.InitError;
467
468/// Returned value may reference several allocations and may point into `a`.
469/// Thefore, an arena-style allocator must be used.
470///
471/// * On Windows, the result is encoded as
472/// [WTF-8](https://wtf-8.codeberg.page/).
473/// * On other platforms, the result is an opaque sequence of bytes with no
474/// particular encoding.
475///
476/// See also:
477/// * `iterate`
478/// * `iterateAllocator`
479pub fn toSlice(a: Args, arena: Allocator) ToSliceError![]const [:0]const u8 {
480 if (native_os == .windows) {
481 var it = try a.iterateAllocator(arena);
482 var contents: std.ArrayList(u8) = .empty;
483 var slice_list: std.ArrayList(usize) = .empty;
484 while (it.next()) |arg| {
485 try contents.appendSlice(arena, arg[0 .. arg.len + 1]);
486 try slice_list.append(arena, arg.len);
487 }
488 const contents_slice = contents.items;
489 const slice_sizes = slice_list.items;
490 const slice_list_bytes = std.math.mul(usize, @sizeOf([]u8), slice_sizes.len) catch return error.OutOfMemory;
491 const total_bytes = std.math.add(usize, slice_list_bytes, contents_slice.len) catch return error.OutOfMemory;
492 const buf = try arena.alignedAlloc(u8, .of([]u8), total_bytes);
493 errdefer arena.free(buf);
494
495 const result_slice_list = std.mem.bytesAsSlice([:0]u8, buf[0..slice_list_bytes]);
496 const result_contents = buf[slice_list_bytes..];
497 @memcpy(result_contents[0..contents_slice.len], contents_slice);
498
499 var contents_index: usize = 0;
500 for (slice_sizes, 0..) |len, i| {
501 const new_index = contents_index + len;
502 result_slice_list[i] = result_contents[contents_index..new_index :0];
503 contents_index = new_index + 1;
504 }
505
506 return result_slice_list;
507 } else if (native_os == .wasi and !builtin.link_libc) {
508 var count: usize = undefined;
509 var buf_size: usize = undefined;
510
511 switch (std.os.wasi.args_sizes_get(&count, &buf_size)) {
512 .SUCCESS => {},
513 else => |err| return std.posix.unexpectedErrno(err),
514 }
515
516 if (count == 0) return &.{};
517
518 const argv = try arena.alloc([*:0]u8, count);
519 const argv_buf = try arena.alloc(u8, buf_size);
520
521 switch (std.os.wasi.args_get(argv.ptr, argv_buf.ptr)) {
522 .SUCCESS => {},
523 else => |err| return std.posix.unexpectedErrno(err),
524 }
525
526 const args = try arena.alloc([:0]const u8, count);
527 for (args, argv) |*dst, src| dst.* = std.mem.sliceTo(src, 0);
528 return args;
529 } else {
530 const args = try arena.alloc([:0]const u8, a.vector.len);
531 for (args, a.vector) |*dst, src| dst.* = std.mem.sliceTo(src, 0);
532 return args;
533 }
534}
535
536test "Iterator.Windows" {
537 const t = testIteratorWindows;
538
539 try t(
540 \\"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\")"
541 , &.{
542 \\C:\Program Files\zig\zig.exe
543 ,
544 \\run
545 ,
546 \\.\src\main.zig
547 ,
548 \\-target
549 ,
550 \\x86_64-windows-gnu
551 ,
552 \\-O
553 ,
554 \\ReleaseSafe
555 ,
556 \\--
557 ,
558 \\--emoji=🗿
559 ,
560 \\--eval=new Regex("Dwayne \"The Rock\" Johnson")
561 ,
562 });
563
564 // Empty
565 try t("", &.{});
566
567 // Separators
568 try t("aa bb cc", &.{ "aa", "bb", "cc" });
569 try t("aa\tbb\tcc", &.{ "aa", "bb", "cc" });
570 try t("aa\nbb\ncc", &.{"aa\nbb\ncc"});
571 try t("aa\r\nbb\r\ncc", &.{"aa\r\nbb\r\ncc"});
572 try t("aa\rbb\rcc", &.{"aa\rbb\rcc"});
573 try t("aa\x07bb\x07cc", &.{"aa\x07bb\x07cc"});
574 try t("aa\x7Fbb\x7Fcc", &.{"aa\x7Fbb\x7Fcc"});
575 try t("aa🦎bb🦎cc", &.{"aa🦎bb🦎cc"});
576
577 // Leading/trailing whitespace
578 try t(" ", &.{""});
579 try t(" aa bb ", &.{ "", "aa", "bb" });
580 try t("\t\t", &.{""});
581 try t("\t\taa\t\tbb\t\t", &.{ "", "aa", "bb" });
582 try t("\n\n", &.{"\n\n"});
583 try t("\n\naa\n\nbb\n\n", &.{"\n\naa\n\nbb\n\n"});
584
585 // Executable name with quotes/backslashes
586 try t("\"aa bb\tcc\ndd\"", &.{"aa bb\tcc\ndd"});
587 try t("\"", &.{""});
588 try t("\"\"", &.{""});
589 try t("\"\"\"", &.{""});
590 try t("\"\"\"\"", &.{""});
591 try t("\"\"\"\"\"", &.{""});
592 try t("aa\"bb\"cc\"dd", &.{"aabbccdd"});
593 try t("aa\"bb cc\"dd", &.{"aabb ccdd"});
594 try t("\"aa\\\"bb\"", &.{"aa\\bb"});
595 try t("\"aa\\\\\"", &.{"aa\\\\"});
596 try t("aa\\\"bb", &.{"aa\\bb"});
597 try t("aa\\\\\"bb", &.{"aa\\\\bb"});
598
599 // Arguments with quotes/backslashes
600 try t(". \"aa bb\tcc\ndd\"", &.{ ".", "aa bb\tcc\ndd" });
601 try t(". aa\" \"bb\"\t\"cc\"\n\"dd\"", &.{ ".", "aa bb\tcc\ndd" });
602 try t(". ", &.{"."});
603 try t(". \"", &.{ ".", "" });
604 try t(". \"\"", &.{ ".", "" });
605 try t(". \"\"\"", &.{ ".", "\"" });
606 try t(". \"\"\"\"", &.{ ".", "\"" });
607 try t(". \"\"\"\"\"", &.{ ".", "\"\"" });
608 try t(". \"\"\"\"\"\"", &.{ ".", "\"\"" });
609 try t(". \" \"", &.{ ".", " " });
610 try t(". \" \"\"", &.{ ".", " \"" });
611 try t(". \" \"\"\"", &.{ ".", " \"" });
612 try t(". \" \"\"\"\"", &.{ ".", " \"\"" });
613 try t(". \" \"\"\"\"\"", &.{ ".", " \"\"" });
614 try t(". \" \"\"\"\"\"\"", &.{ ".", " \"\"\"" });
615 try t(". \\\"", &.{ ".", "\"" });
616 try t(". \\\"\"", &.{ ".", "\"" });
617 try t(". \\\"\"\"", &.{ ".", "\"" });
618 try t(". \\\"\"\"\"", &.{ ".", "\"\"" });
619 try t(". \\\"\"\"\"\"", &.{ ".", "\"\"" });
620 try t(". \\\"\"\"\"\"\"", &.{ ".", "\"\"\"" });
621 try t(". \" \\\"", &.{ ".", " \"" });
622 try t(". \" \\\"\"", &.{ ".", " \"" });
623 try t(". \" \\\"\"\"", &.{ ".", " \"\"" });
624 try t(". \" \\\"\"\"\"", &.{ ".", " \"\"" });
625 try t(". \" \\\"\"\"\"\"", &.{ ".", " \"\"\"" });
626 try t(". \" \\\"\"\"\"\"\"", &.{ ".", " \"\"\"" });
627 try t(". aa\\bb\\\\cc\\\\\\dd", &.{ ".", "aa\\bb\\\\cc\\\\\\dd" });
628 try t(". \\\\\\\"aa bb\"", &.{ ".", "\\\"aa", "bb" });
629 try t(". \\\\\\\\\"aa bb\"", &.{ ".", "\\\\aa bb" });
630
631 // From https://learn.microsoft.com/en-us/cpp/cpp/main-function-command-line-args#results-of-parsing-command-lines
632 try t(
633 \\foo.exe "abc" d e
634 , &.{ "foo.exe", "abc", "d", "e" });
635 try t(
636 \\foo.exe a\\b d"e f"g h
637 , &.{ "foo.exe", "a\\\\b", "de fg", "h" });
638 try t(
639 \\foo.exe a\\\"b c d
640 , &.{ "foo.exe", "a\\\"b", "c", "d" });
641 try t(
642 \\foo.exe a\\\\"b c" d e
643 , &.{ "foo.exe", "a\\\\b c", "d", "e" });
644 try t(
645 \\foo.exe a"b"" c d
646 , &.{ "foo.exe", "ab\" c d" });
647
648 // From https://daviddeley.com/autohotkey/parameters/parameters.htm#WINCRULESEX
649 try t("foo.exe CallMeIshmael", &.{ "foo.exe", "CallMeIshmael" });
650 try t("foo.exe \"Call Me Ishmael\"", &.{ "foo.exe", "Call Me Ishmael" });
651 try t("foo.exe Cal\"l Me I\"shmael", &.{ "foo.exe", "Call Me Ishmael" });
652 try t("foo.exe CallMe\\\"Ishmael", &.{ "foo.exe", "CallMe\"Ishmael" });
653 try t("foo.exe \"CallMe\\\"Ishmael\"", &.{ "foo.exe", "CallMe\"Ishmael" });
654 try t("foo.exe \"Call Me Ishmael\\\\\"", &.{ "foo.exe", "Call Me Ishmael\\" });
655 try t("foo.exe \"CallMe\\\\\\\"Ishmael\"", &.{ "foo.exe", "CallMe\\\"Ishmael" });
656 try t("foo.exe a\\\\\\b", &.{ "foo.exe", "a\\\\\\b" });
657 try t("foo.exe \"a\\\\\\b\"", &.{ "foo.exe", "a\\\\\\b" });
658
659 // Surrogate pair encoding of 𐐷 separated by quotes.
660 // Encoded as WTF-16:
661 // "<0xD801>"<0xDC37>
662 // Encoded as WTF-8:
663 // "<0xED><0xA0><0x81>"<0xED><0xB0><0xB7>
664 // During parsing, the quotes drop out and the surrogate pair
665 // should end up encoded as its normal UTF-8 representation.
666 try t("foo.exe \"\xed\xa0\x81\"\xed\xb0\xb7", &.{ "foo.exe", "𐐷" });
667}
668
669fn testIteratorWindows(cmd_line: []const u8, expected_args: []const []const u8) !void {
670 const cmd_line_w = try std.unicode.wtf8ToWtf16LeAllocZ(testing.allocator, cmd_line);
671 defer testing.allocator.free(cmd_line_w);
672
673 // next
674 {
675 var it = try Iterator.Windows.init(testing.allocator, cmd_line_w);
676 defer it.deinit();
677
678 for (expected_args) |expected| {
679 if (it.next()) |actual| {
680 try testing.expectEqualStrings(expected, actual);
681 } else {
682 return error.TestUnexpectedResult;
683 }
684 }
685 try testing.expect(it.next() == null);
686 }
687
688 // skip
689 {
690 var it = try Iterator.Windows.init(testing.allocator, cmd_line_w);
691 defer it.deinit();
692
693 for (0..expected_args.len) |_| {
694 try testing.expect(it.skip());
695 }
696 try testing.expect(!it.skip());
697 }
698}
699
700test "general parsing" {
701 try testGeneralCmdLine("a b\tc d", &.{ "a", "b", "c", "d" });
702 try testGeneralCmdLine("\"abc\" d e", &.{ "abc", "d", "e" });
703 try testGeneralCmdLine("a\\\\\\b d\"e f\"g h", &.{ "a\\\\\\b", "de fg", "h" });
704 try testGeneralCmdLine("a\\\\\\\"b c d", &.{ "a\\\"b", "c", "d" });
705 try testGeneralCmdLine("a\\\\\\\\\"b c\" d e", &.{ "a\\\\b c", "d", "e" });
706 try testGeneralCmdLine("a b\tc \"d f", &.{ "a", "b", "c", "d f" });
707 try testGeneralCmdLine("j k l\\", &.{ "j", "k", "l\\" });
708 try testGeneralCmdLine("\"\" x y z\\\\", &.{ "", "x", "y", "z\\\\" });
709
710 try testGeneralCmdLine("\".\\..\\zig-cache\\build\" \"bin\\zig.exe\" \".\\..\" \".\\..\\zig-cache\" \"--help\"", &.{
711 ".\\..\\zig-cache\\build",
712 "bin\\zig.exe",
713 ".\\..",
714 ".\\..\\zig-cache",
715 "--help",
716 });
717
718 try testGeneralCmdLine(
719 \\ 'foo' "bar"
720 , &.{ "'foo'", "bar" });
721}
722
723fn testGeneralCmdLine(input_cmd_line: []const u8, expected_args: []const []const u8) !void {
724 var it = try IteratorGeneral(.{}).init(std.testing.allocator, input_cmd_line);
725 defer it.deinit();
726 for (expected_args) |expected_arg| {
727 const arg = it.next().?;
728 try testing.expectEqualStrings(expected_arg, arg);
729 }
730 try testing.expect(it.next() == null);
731}
732
733/// Optional parameters for `IteratorGeneral`
734pub const IteratorGeneralOptions = struct {
735 comments: bool = false,
736 single_quotes: bool = false,
737};
738
739/// A general Iterator to parse a string into a set of arguments
740pub fn IteratorGeneral(comptime options: IteratorGeneralOptions) type {
741 return struct {
742 allocator: Allocator,
743 index: usize = 0,
744 cmd_line: []const u8,
745
746 /// Should the cmd_line field be free'd (using the allocator) on deinit()?
747 free_cmd_line_on_deinit: bool,
748
749 /// buffer MUST be long enough to hold the cmd_line plus a null terminator.
750 /// buffer will we free'd (using the allocator) on deinit()
751 buffer: []u8,
752 start: usize = 0,
753 end: usize = 0,
754
755 pub const Self = @This();
756
757 pub const InitError = error{OutOfMemory};
758
759 /// cmd_line_utf8 MUST remain valid and constant while using this instance
760 pub fn init(allocator: Allocator, cmd_line_utf8: []const u8) InitError!Self {
761 const buffer = try allocator.alloc(u8, cmd_line_utf8.len + 1);
762 errdefer allocator.free(buffer);
763
764 return Self{
765 .allocator = allocator,
766 .cmd_line = cmd_line_utf8,
767 .free_cmd_line_on_deinit = false,
768 .buffer = buffer,
769 };
770 }
771
772 /// cmd_line_utf8 will be free'd (with the allocator) on deinit()
773 pub fn initTakeOwnership(allocator: Allocator, cmd_line_utf8: []const u8) InitError!Self {
774 const buffer = try allocator.alloc(u8, cmd_line_utf8.len + 1);
775 errdefer allocator.free(buffer);
776
777 return Self{
778 .allocator = allocator,
779 .cmd_line = cmd_line_utf8,
780 .free_cmd_line_on_deinit = true,
781 .buffer = buffer,
782 };
783 }
784
785 // Skips over whitespace in the cmd_line.
786 // Returns false if the terminating sentinel is reached, true otherwise.
787 // Also skips over comments (if supported).
788 fn skipWhitespace(self: *Self) bool {
789 while (true) : (self.index += 1) {
790 const character = if (self.index != self.cmd_line.len) self.cmd_line[self.index] else 0;
791 switch (character) {
792 0 => return false,
793 ' ', '\t', '\r', '\n' => continue,
794 '#' => {
795 if (options.comments) {
796 while (true) : (self.index += 1) {
797 switch (self.cmd_line[self.index]) {
798 '\n' => break,
799 0 => return false,
800 else => continue,
801 }
802 }
803 continue;
804 } else {
805 break;
806 }
807 },
808 else => break,
809 }
810 }
811 return true;
812 }
813
814 pub fn skip(self: *Self) bool {
815 if (!self.skipWhitespace()) {
816 return false;
817 }
818
819 var backslash_count: usize = 0;
820 var in_quote = false;
821 while (true) : (self.index += 1) {
822 const character = if (self.index != self.cmd_line.len) self.cmd_line[self.index] else 0;
823 switch (character) {
824 0 => return true,
825 '"', '\'' => {
826 if (!options.single_quotes and character == '\'') {
827 backslash_count = 0;
828 continue;
829 }
830 const quote_is_real = backslash_count % 2 == 0;
831 if (quote_is_real) {
832 in_quote = !in_quote;
833 }
834 },
835 '\\' => {
836 backslash_count += 1;
837 },
838 ' ', '\t', '\r', '\n' => {
839 if (!in_quote) {
840 return true;
841 }
842 backslash_count = 0;
843 },
844 else => {
845 backslash_count = 0;
846 continue;
847 },
848 }
849 }
850 }
851
852 /// Returns a slice of the internal buffer that contains the next argument.
853 /// Returns null when it reaches the end.
854 pub fn next(self: *Self) ?[:0]const u8 {
855 if (!self.skipWhitespace()) {
856 return null;
857 }
858
859 var backslash_count: usize = 0;
860 var in_quote = false;
861 while (true) : (self.index += 1) {
862 const character = if (self.index != self.cmd_line.len) self.cmd_line[self.index] else 0;
863 switch (character) {
864 0 => {
865 self.emitBackslashes(backslash_count);
866 self.buffer[self.end] = 0;
867 const token = self.buffer[self.start..self.end :0];
868 self.end += 1;
869 self.start = self.end;
870 return token;
871 },
872 '"', '\'' => {
873 if (!options.single_quotes and character == '\'') {
874 self.emitBackslashes(backslash_count);
875 backslash_count = 0;
876 self.emitCharacter(character);
877 continue;
878 }
879 const quote_is_real = backslash_count % 2 == 0;
880 self.emitBackslashes(backslash_count / 2);
881 backslash_count = 0;
882
883 if (quote_is_real) {
884 in_quote = !in_quote;
885 } else {
886 self.emitCharacter('"');
887 }
888 },
889 '\\' => {
890 backslash_count += 1;
891 },
892 ' ', '\t', '\r', '\n' => {
893 self.emitBackslashes(backslash_count);
894 backslash_count = 0;
895 if (in_quote) {
896 self.emitCharacter(character);
897 } else {
898 self.buffer[self.end] = 0;
899 const token = self.buffer[self.start..self.end :0];
900 self.end += 1;
901 self.start = self.end;
902 return token;
903 }
904 },
905 else => {
906 self.emitBackslashes(backslash_count);
907 backslash_count = 0;
908 self.emitCharacter(character);
909 },
910 }
911 }
912 }
913
914 fn emitBackslashes(self: *Self, emit_count: usize) void {
915 var i: usize = 0;
916 while (i < emit_count) : (i += 1) {
917 self.emitCharacter('\\');
918 }
919 }
920
921 fn emitCharacter(self: *Self, char: u8) void {
922 self.buffer[self.end] = char;
923 self.end += 1;
924 }
925
926 /// Call to free the internal buffer of the iterator.
927 pub fn deinit(self: *Self) void {
928 self.allocator.free(self.buffer);
929
930 if (self.free_cmd_line_on_deinit) {
931 self.allocator.free(self.cmd_line);
932 }
933 }
934 };
935}
936
937test "response file arg parsing" {
938 try testResponseFileCmdLine(
939 \\a b
940 \\c d\
941 , &.{ "a", "b", "c", "d\\" });
942 try testResponseFileCmdLine("a b c d\\", &.{ "a", "b", "c", "d\\" });
943
944 try testResponseFileCmdLine(
945 \\j
946 \\ k l # this is a comment \\ \\\ \\\\ "none" "\\" "\\\"
947 \\ "m" #another comment
948 \\
949 , &.{ "j", "k", "l", "m" });
950
951 try testResponseFileCmdLine(
952 \\ "" q ""
953 \\ "r s # t" "u\" v" #another comment
954 \\
955 , &.{ "", "q", "", "r s # t", "u\" v" });
956
957 try testResponseFileCmdLine(
958 \\ -l"advapi32" a# b#c d#
959 \\e\\\
960 , &.{ "-ladvapi32", "a#", "b#c", "d#", "e\\\\\\" });
961
962 try testResponseFileCmdLine(
963 \\ 'foo' "bar"
964 , &.{ "foo", "bar" });
965}
966
967fn testResponseFileCmdLine(input_cmd_line: []const u8, expected_args: []const []const u8) !void {
968 var it = try IteratorGeneral(.{ .comments = true, .single_quotes = true })
969 .init(std.testing.allocator, input_cmd_line);
970 defer it.deinit();
971 for (expected_args) |expected_arg| {
972 const arg = it.next().?;
973 try testing.expectEqualStrings(expected_arg, arg);
974 }
975 try testing.expect(it.next() == null);
976}
lib/std/process/Child.zig+39-1760
...@@ -5,116 +5,38 @@ const native_os = builtin.os.tag;...@@ -5,116 +5,38 @@ const native_os = builtin.os.tag;
55
6const std = @import("../std.zig");6const std = @import("../std.zig");
7const Io = std.Io;7const Io = std.Io;
8const unicode = std.unicode;
9const fs = std.fs;
10const process = std.process;8const process = std.process;
11const File = std.Io.File;9const File = std.Io.File;
12const windows = std.os.windows;
13const linux = std.os.linux;
14const posix = std.posix;
15const mem = std.mem;
16const EnvMap = std.process.EnvMap;
17const maxInt = std.math.maxInt;
18const assert = std.debug.assert;10const assert = std.debug.assert;
19const Allocator = std.mem.Allocator;11const Allocator = std.mem.Allocator;
20const ArrayList = std.ArrayList;12const ArrayList = std.ArrayList;
2113
22pub const Id = switch (native_os) {14pub const Id = switch (native_os) {
23 .windows => windows.HANDLE,15 .windows => std.os.windows.HANDLE,
24 .wasi => void,16 .wasi => void,
25 else => posix.pid_t,17 else => std.posix.pid_t,
26};18};
2719
28/// Available after calling `spawn()`. This becomes `undefined` after calling `wait()`.20/// After `wait` or `kill` is called, this becomes `null`.
29/// On Windows this is the hProcess.21/// On Windows this is the hProcess.
30/// On POSIX this is the pid.22/// On POSIX this is the pid.
31id: Id,23id: ?Id,
32thread_handle: if (native_os == .windows) windows.HANDLE else void,24thread_handle: if (native_os == .windows) std.os.windows.HANDLE else void,
33
34allocator: Allocator,
35
36/// The writing end of the child process's standard input pipe.25/// The writing end of the child process's standard input pipe.
37/// Usage requires `stdin_behavior == StdIo.Pipe`.26/// Usage requires `process.SpawnOptions.StdIo.pipe`.
38/// Available after calling `spawn()`.
39stdin: ?File,27stdin: ?File,
40
41/// The reading end of the child process's standard output pipe.28/// The reading end of the child process's standard output pipe.
42/// Usage requires `stdout_behavior == StdIo.Pipe`.29/// Usage requires `process.SpawnOptions.StdIo.pipe`.
43/// Available after calling `spawn()`.
44stdout: ?File,30stdout: ?File,
45
46/// The reading end of the child process's standard error pipe.31/// The reading end of the child process's standard error pipe.
47/// Usage requires `stderr_behavior == StdIo.Pipe`.32/// Usage requires `process.SpawnOptions.StdIo.pipe`.
48/// Available after calling `spawn()`.
49stderr: ?File,33stderr: ?File,
50
51/// Terminated state of the child process.
52/// Available after calling `wait()`.
53term: ?(SpawnError!Term),
54
55argv: []const []const u8,
56
57/// Leave as null to use the current env map using the supplied allocator.
58/// Required if unable to access the current env map (e.g. building a library on
59/// some platforms).
60env_map: ?*const EnvMap,
61
62stdin_behavior: StdIo,
63stdout_behavior: StdIo,
64stderr_behavior: StdIo,
65
66/// Set to change the user id when spawning the child process.
67uid: if (native_os == .windows or native_os == .wasi) void else ?posix.uid_t,
68
69/// Set to change the group id when spawning the child process.
70gid: if (native_os == .windows or native_os == .wasi) void else ?posix.gid_t,
71
72/// Set to change the process group id when spawning the child process.
73pgid: if (native_os == .windows or native_os == .wasi) void else ?posix.pid_t,
74
75/// Set to change the current working directory when spawning the child process.
76cwd: ?[]const u8,
77/// Set to change the current working directory when spawning the child process.
78/// This is not yet implemented for Windows. See https://github.com/ziglang/zig/issues/5190
79/// Once that is done, `cwd` will be deprecated in favor of this field.
80cwd_dir: ?Io.Dir = null,
81
82err_pipe: if (native_os == .windows) void else ?posix.fd_t,
83
84expand_arg0: Arg0Expand,
85
86/// Darwin-only. Disable ASLR for the child process.
87disable_aslr: bool = false,
88
89/// Start child process in suspended state.
90/// For Posix systems it's started as if SIGSTOP was sent.
91start_suspended: bool = false,
92
93/// Windows-only. Sets the CREATE_NO_WINDOW flag in CreateProcess.
94create_no_window: bool = false,
95
96/// Set to true to obtain rusage information for the child process.
97/// Depending on the target platform and implementation status, the
98/// requested statistics may or may not be available. If they are
99/// available, then the `resource_usage_statistics` field will be populated
100/// after calling `wait`.
101/// On Linux and Darwin, this obtains rusage statistics from wait4().
102request_resource_usage_statistics: bool = false,
103
104/// This is available after calling wait if34/// This is available after calling wait if
105/// `request_resource_usage_statistics` was set to `true` before calling35/// `request_resource_usage_statistics` was set to `true` before calling
106/// `spawn`.36/// `spawn`.
37/// TODO move this data into `Term`
107resource_usage_statistics: ResourceUsageStatistics = .{},38resource_usage_statistics: ResourceUsageStatistics = .{},
10839request_resource_usage_statistics: bool,
109/// When populated, a pipe will be created for the child process to
110/// communicate progress back to the parent. The file descriptor of the
111/// write end of the pipe will be specified in the `ZIG_PROGRESS`
112/// environment variable inside the child process. The progress reported by
113/// the child will be attached to this progress node in the parent process.
114///
115/// The child's progress tree will be grafted into the parent's progress tree,
116/// by substituting this node with the child's root node.
117progress_node: std.Progress.Node = std.Progress.Node.none,
11840
119pub const ResourceUsageStatistics = struct {41pub const ResourceUsageStatistics = struct {
120 rusage: @TypeOf(rusage_init) = rusage_init,42 rusage: @TypeOf(rusage_init) = rusage_init,
...@@ -164,233 +86,60 @@ pub const ResourceUsageStatistics = struct {...@@ -164,233 +86,60 @@ pub const ResourceUsageStatistics = struct {
164 .tvos,86 .tvos,
165 .visionos,87 .visionos,
166 .watchos,88 .watchos,
167 => @as(?posix.rusage, null),89 => @as(?std.posix.rusage, null),
168 .windows => @as(?windows.VM_COUNTERS, null),90 .windows => @as(?std.os.windows.VM_COUNTERS, null),
169 else => {},91 else => {},
170 };92 };
171};93};
17294
173pub const Arg0Expand = posix.Arg0Expand;
174
175pub const SpawnError = error{
176 OutOfMemory,
177
178 /// POSIX-only. `StdIo.Ignore` was selected and opening `/dev/null` returned ENODEV.
179 NoDevice,
180
181 /// Windows-only. `cwd` or `argv` was provided and it was invalid WTF-8.
182 /// https://wtf-8.codeberg.page/
183 InvalidWtf8,
184
185 /// Windows-only. `cwd` was provided, but the path did not exist when spawning the child process.
186 CurrentWorkingDirectoryUnlinked,
187
188 /// Windows-only. NUL (U+0000), LF (U+000A), CR (U+000D) are not allowed
189 /// within arguments when executing a `.bat`/`.cmd` script.
190 /// - NUL/LF signifiies end of arguments, so anything afterwards
191 /// would be lost after execution.
192 /// - CR is stripped by `cmd.exe`, so any CR codepoints
193 /// would be lost after execution.
194 InvalidBatchScriptArg,
195} ||
196 posix.ExecveError ||
197 posix.SetIdError ||
198 posix.SetPgidError ||
199 posix.ChangeCurDirError ||
200 windows.CreateProcessError ||
201 windows.GetProcessMemoryInfoError ||
202 windows.WaitForSingleObjectError;
203
204pub const Term = union(enum) {95pub const Term = union(enum) {
205 Exited: u8,96 exited: u8,
206 Signal: u32,97 signal: std.posix.SIG,
207 Stopped: u32,98 stopped: u32,
208 Unknown: u32,99 unknown: u32,
209};
210
211/// Behavior of the child process's standard input, output, and error
212/// streams.
213pub const StdIo = enum {
214 /// Inherit the stream from the parent process.
215 Inherit,
216
217 /// Pass a null stream to the child process.
218 /// This is /dev/null on POSIX and NUL on Windows.
219 Ignore,
220
221 /// Create a pipe for the stream.
222 /// The corresponding field (`stdout`, `stderr`, or `stdin`)
223 /// will be assigned a `File` object that can be used
224 /// to read from or write to the pipe.
225 Pipe,
226
227 /// Close the stream after the child process spawns.
228 Close,
229};100};
230101
231/// First argument in argv is the executable.102/// Requests for the operating system to forcibly terminate the child process,
232pub fn init(argv: []const []const u8, allocator: Allocator) Child {103/// then blocks until it terminates, then cleans up all resources.
233 return .{104///
234 .allocator = allocator,105/// Idempotent and does nothing after `wait` returns.
235 .argv = argv,106///
236 .id = undefined,107/// Uncancelable. Ignores unexpected errors from the operating system.
237 .thread_handle = undefined,108pub fn kill(child: *Child, io: Io) void {
238 .err_pipe = if (native_os == .windows) {} else null,109 if (child.id == null) {
239 .term = null,110 assert(child.stdin == null);
240 .env_map = null,111 assert(child.stdout == null);
241 .cwd = null,112 assert(child.stderr == null);
242 .uid = if (native_os == .windows or native_os == .wasi) {} else null,
243 .gid = if (native_os == .windows or native_os == .wasi) {} else null,
244 .pgid = if (native_os == .windows or native_os == .wasi) {} else null,
245 .stdin = null,
246 .stdout = null,
247 .stderr = null,
248 .stdin_behavior = .Inherit,
249 .stdout_behavior = .Inherit,
250 .stderr_behavior = .Inherit,
251 .expand_arg0 = .no_expand,
252 };
253}
254
255pub fn setUserName(self: *Child, name: []const u8) !void {
256 const user_info = try process.getUserInfo(name);
257 self.uid = user_info.uid;
258 self.gid = user_info.gid;
259}
260
261/// On success must call `kill` or `wait`.
262/// After spawning the `id` is available.
263pub fn spawn(self: *Child, io: Io) SpawnError!void {
264 if (!process.can_spawn) {
265 @compileError("the target operating system cannot spawn processes");
266 }
267
268 if (native_os == .windows) {
269 return self.spawnWindows(io);
270 } else {
271 return self.spawnPosix(io);
272 }
273}
274
275pub fn spawnAndWait(child: *Child, io: Io) SpawnError!Term {
276 try child.spawn(io);
277 return child.wait(io);
278}
279
280/// Forcibly terminates child process and then cleans up all resources.
281pub fn kill(self: *Child, io: Io) !Term {
282 if (native_os == .windows) {
283 return self.killWindows(io, 1);
284 } else {
285 return self.killPosix(io);
286 }
287}
288
289pub fn killWindows(self: *Child, io: Io, exit_code: windows.UINT) !Term {
290 if (self.term) |term| {
291 self.cleanupStreams(io);
292 return term;
293 }
294
295 windows.TerminateProcess(self.id, exit_code) catch |err| switch (err) {
296 error.AccessDenied => {
297 // Usually when TerminateProcess triggers a ACCESS_DENIED error, it
298 // indicates that the process has already exited, but there may be
299 // some rare edge cases where our process handle no longer has the
300 // PROCESS_TERMINATE access right, so let's do another check to make
301 // sure the process is really no longer running:
302 windows.WaitForSingleObjectEx(self.id, 0, false) catch return err;
303 return error.AlreadyTerminated;
304 },
305 else => return err,
306 };
307 try self.waitUnwrappedWindows(io);
308 return self.term.?;
309}
310
311pub fn killPosix(self: *Child, io: Io) !Term {
312 if (self.term) |term| {
313 self.cleanupStreams(io);
314 return term;
315 }
316 posix.kill(self.id, posix.SIG.TERM) catch |err| switch (err) {
317 error.ProcessNotFound => return error.AlreadyTerminated,
318 else => return err,
319 };
320 self.waitUnwrappedPosix(io);
321 return self.term.?;
322}
323
324pub const WaitError = SpawnError || std.os.windows.GetProcessMemoryInfoError;
325
326/// On some targets, `spawn` may not report all spawn errors, such as `error.InvalidExe`.
327/// This function will block until any spawn errors can be reported, and return them.
328pub fn waitForSpawn(self: *Child) SpawnError!void {
329 if (native_os == .windows) return; // `spawn` reports everything
330 if (self.term) |term| {
331 _ = term catch |spawn_err| return spawn_err;
332 return;113 return;
333 }114 }
334115 io.vtable.childKill(io.userdata, child);
335 const err_pipe = self.err_pipe orelse return;116 assert(child.id == null);
336 self.err_pipe = null;
337 // Wait for the child to report any errors in or before `execvpe`.
338 const report = readIntFd(err_pipe);
339 posix.close(err_pipe);
340 if (report) |child_err_int| {
341 const child_err: SpawnError = @errorCast(@errorFromInt(child_err_int));
342 self.term = child_err;
343 return child_err;
344 } else |read_err| switch (read_err) {
345 error.EndOfStream => {
346 // Write end closed by CLOEXEC at the time of the `execvpe` call,
347 // indicating success.
348 },
349 else => {
350 // Problem reading the error from the error reporting pipe. We
351 // don't know if the child is alive or dead. Better to assume it is
352 // alive so the resource does not risk being leaked.
353 },
354 }
355}117}
356118
119pub const WaitError = error{
120 AccessDenied,
121} || Io.Cancelable || Io.UnexpectedError;
122
357/// Blocks until child process terminates and then cleans up all resources.123/// Blocks until child process terminates and then cleans up all resources.
358pub fn wait(self: *Child, io: Io) WaitError!Term {124pub fn wait(child: *Child, io: Io) WaitError!Term {
359 try self.waitForSpawn(); // report spawn errors125 assert(child.id != null);
360 if (self.term) |term| {126 return io.vtable.childWait(io.userdata, child);
361 self.cleanupStreams(io);
362 return term;
363 }
364 switch (native_os) {
365 .windows => try self.waitUnwrappedWindows(io),
366 else => self.waitUnwrappedPosix(io),
367 }
368 self.id = undefined;
369 return self.term.?;
370}127}
371128
372pub const RunResult = struct {
373 term: Term,
374 stdout: []u8,
375 stderr: []u8,
376};
377
378/// Collect the output from the process's stdout and stderr. Will return once all output129/// Collect the output from the process's stdout and stderr. Will return once all output
379/// has been collected. This does not mean that the process has ended. `wait` should still130/// has been collected. This does not mean that the process has ended. `wait` should still
380/// be called to wait for and clean up the process.131/// be called to wait for and clean up the process.
381///132///
382/// The process must be started with stdout_behavior and stderr_behavior == .Pipe133/// The process must have been started with stdout and stderr set to
134/// `process.SpawnOptions.StdIo.pipe`.
383pub fn collectOutput(135pub fn collectOutput(
384 child: Child,136 child: *const Child,
385 /// Used for `stdout` and `stderr`.137 /// Used for `stdout` and `stderr`.
386 allocator: Allocator,138 allocator: Allocator,
387 stdout: *ArrayList(u8),139 stdout: *ArrayList(u8),
388 stderr: *ArrayList(u8),140 stderr: *ArrayList(u8),
389 max_output_bytes: usize,141 max_output_bytes: usize,
390) !void {142) !void {
391 assert(child.stdout_behavior == .Pipe);
392 assert(child.stderr_behavior == .Pipe);
393
394 var poller = std.Io.poll(allocator, enum { stdout, stderr }, .{143 var poller = std.Io.poll(allocator, enum { stdout, stderr }, .{
395 .stdout = child.stdout.?,144 .stdout = child.stdout.?,
396 .stderr = child.stderr.?,145 .stderr = child.stderr.?,
...@@ -427,1473 +176,3 @@ pub fn collectOutput(...@@ -427,1473 +176,3 @@ pub fn collectOutput(
427 return error.StderrStreamTooLong;176 return error.StderrStreamTooLong;
428 }177 }
429}178}
430
431pub const RunError = posix.GetCwdError || posix.ReadError || SpawnError || posix.PollError || error{
432 StdoutStreamTooLong,
433 StderrStreamTooLong,
434};
435
436/// Spawns a child process, waits for it, collecting stdout and stderr, and then returns.
437/// If it succeeds, the caller owns result.stdout and result.stderr memory.
438pub fn run(allocator: Allocator, io: Io, args: struct {
439 argv: []const []const u8,
440 cwd: ?[]const u8 = null,
441 cwd_dir: ?Io.Dir = null,
442 /// Required if unable to access the current env map (e.g. building a
443 /// library on some platforms).
444 env_map: ?*const EnvMap = null,
445 max_output_bytes: usize = 50 * 1024,
446 expand_arg0: Arg0Expand = .no_expand,
447 progress_node: std.Progress.Node = std.Progress.Node.none,
448}) RunError!RunResult {
449 var child = Child.init(args.argv, allocator);
450 child.stdin_behavior = .Ignore;
451 child.stdout_behavior = .Pipe;
452 child.stderr_behavior = .Pipe;
453 child.cwd = args.cwd;
454 child.cwd_dir = args.cwd_dir;
455 child.env_map = args.env_map;
456 child.expand_arg0 = args.expand_arg0;
457 child.progress_node = args.progress_node;
458
459 var stdout: ArrayList(u8) = .empty;
460 defer stdout.deinit(allocator);
461 var stderr: ArrayList(u8) = .empty;
462 defer stderr.deinit(allocator);
463
464 try child.spawn(io);
465 errdefer {
466 _ = child.kill(io) catch {};
467 }
468 try child.collectOutput(allocator, &stdout, &stderr, args.max_output_bytes);
469
470 return .{
471 .stdout = try stdout.toOwnedSlice(allocator),
472 .stderr = try stderr.toOwnedSlice(allocator),
473 .term = try child.wait(io),
474 };
475}
476
477fn waitUnwrappedWindows(self: *Child, io: Io) WaitError!void {
478 const result = windows.WaitForSingleObjectEx(self.id, windows.INFINITE, false);
479
480 self.term = @as(SpawnError!Term, x: {
481 var exit_code: windows.DWORD = undefined;
482 if (windows.kernel32.GetExitCodeProcess(self.id, &exit_code) == 0) {
483 break :x Term{ .Unknown = 0 };
484 } else {
485 break :x Term{ .Exited = @as(u8, @truncate(exit_code)) };
486 }
487 });
488
489 if (self.request_resource_usage_statistics) {
490 self.resource_usage_statistics.rusage = try windows.GetProcessMemoryInfo(self.id);
491 }
492
493 posix.close(self.id);
494 posix.close(self.thread_handle);
495 self.cleanupStreams(io);
496 return result;
497}
498
499fn waitUnwrappedPosix(self: *Child, io: Io) void {
500 const res: posix.WaitPidResult = res: {
501 if (self.request_resource_usage_statistics) {
502 switch (native_os) {
503 .dragonfly,
504 .freebsd,
505 .netbsd,
506 .openbsd,
507 .illumos,
508 .linux,
509 .serenity,
510 .driverkit,
511 .ios,
512 .maccatalyst,
513 .macos,
514 .tvos,
515 .visionos,
516 .watchos,
517 => {
518 var ru: posix.rusage = undefined;
519 const res = posix.wait4(self.id, 0, &ru);
520 self.resource_usage_statistics.rusage = ru;
521 break :res res;
522 },
523 else => {},
524 }
525 }
526
527 break :res posix.waitpid(self.id, 0);
528 };
529 const status = res.status;
530 self.cleanupStreams(io);
531 self.handleWaitResult(status);
532}
533
534fn handleWaitResult(self: *Child, status: u32) void {
535 self.term = statusToTerm(status);
536}
537
538fn cleanupStreams(self: *Child, io: Io) void {
539 if (self.stdin) |*stdin| {
540 stdin.close(io);
541 self.stdin = null;
542 }
543 if (self.stdout) |*stdout| {
544 stdout.close(io);
545 self.stdout = null;
546 }
547 if (self.stderr) |*stderr| {
548 stderr.close(io);
549 self.stderr = null;
550 }
551}
552
553fn statusToTerm(status: u32) Term {
554 return if (posix.W.IFEXITED(status))
555 Term{ .Exited = posix.W.EXITSTATUS(status) }
556 else if (posix.W.IFSIGNALED(status))
557 Term{ .Signal = posix.W.TERMSIG(status) }
558 else if (posix.W.IFSTOPPED(status))
559 Term{ .Stopped = posix.W.STOPSIG(status) }
560 else
561 Term{ .Unknown = status };
562}
563
564fn spawnPosix(self: *Child, io: Io) SpawnError!void {
565 // The child process does need to access (one end of) these pipes. However,
566 // we must initially set CLOEXEC to avoid a race condition. If another thread
567 // is racing to spawn a different child process, we don't want it to inherit
568 // these FDs in any scenario; that would mean that, for instance, calls to
569 // `poll` from the parent would not report the child's stdout as closing when
570 // expected, since the other child may retain a reference to the write end of
571 // the pipe. So, we create the pipes with CLOEXEC initially. After fork, we
572 // need to do something in the new child to make sure we preserve the reference
573 // we want. We could use `fcntl` to remove CLOEXEC from the FD, but as it
574 // turns out, we `dup2` everything anyway, so there's no need!
575 const pipe_flags: posix.O = .{ .CLOEXEC = true };
576
577 const stdin_pipe = if (self.stdin_behavior == .Pipe) try posix.pipe2(pipe_flags) else undefined;
578 errdefer if (self.stdin_behavior == .Pipe) {
579 destroyPipe(stdin_pipe);
580 };
581
582 const stdout_pipe = if (self.stdout_behavior == .Pipe) try posix.pipe2(pipe_flags) else undefined;
583 errdefer if (self.stdout_behavior == .Pipe) {
584 destroyPipe(stdout_pipe);
585 };
586
587 const stderr_pipe = if (self.stderr_behavior == .Pipe) try posix.pipe2(pipe_flags) else undefined;
588 errdefer if (self.stderr_behavior == .Pipe) {
589 destroyPipe(stderr_pipe);
590 };
591
592 const any_ignore = (self.stdin_behavior == .Ignore or self.stdout_behavior == .Ignore or self.stderr_behavior == .Ignore);
593 const dev_null_fd = if (any_ignore)
594 posix.openZ("/dev/null", .{ .ACCMODE = .RDWR }, 0) catch |err| switch (err) {
595 error.PathAlreadyExists => unreachable,
596 error.NoSpaceLeft => unreachable,
597 error.FileTooBig => unreachable,
598 error.DeviceBusy => unreachable,
599 error.FileLocksUnsupported => unreachable,
600 error.BadPathName => unreachable, // Windows-only
601 error.WouldBlock => unreachable,
602 error.NetworkNotFound => unreachable, // Windows-only
603 error.Canceled => unreachable, // temporarily in the posix error set
604 error.SharingViolation => unreachable, // Windows-only
605 error.PipeBusy => unreachable, // not a pipe
606 error.AntivirusInterference => unreachable, // Windows-only
607 else => |e| return e,
608 }
609 else
610 undefined;
611 defer {
612 if (any_ignore) posix.close(dev_null_fd);
613 }
614
615 const prog_pipe: [2]posix.fd_t = p: {
616 if (self.progress_node.index == .none) {
617 break :p .{ -1, -1 };
618 } else {
619 // We use CLOEXEC for the same reason as in `pipe_flags`.
620 break :p try posix.pipe2(.{ .NONBLOCK = true, .CLOEXEC = true });
621 }
622 };
623 errdefer destroyPipe(prog_pipe);
624
625 var arena_allocator = std.heap.ArenaAllocator.init(self.allocator);
626 defer arena_allocator.deinit();
627 const arena = arena_allocator.allocator();
628
629 // The POSIX standard does not allow malloc() between fork() and execve(),
630 // and `self.allocator` may be a libc allocator.
631 // I have personally observed the child process deadlocking when it tries
632 // to call malloc() due to a heap allocation between fork() and execve(),
633 // in musl v1.1.24.
634 // Additionally, we want to reduce the number of possible ways things
635 // can fail between fork() and execve().
636 // Therefore, we do all the allocation for the execve() before the fork().
637 // This means we must do the null-termination of argv and env vars here.
638 const argv_buf = try arena.allocSentinel(?[*:0]const u8, self.argv.len, null);
639 for (self.argv, 0..) |arg, i| argv_buf[i] = (try arena.dupeZ(u8, arg)).ptr;
640
641 const prog_fileno = 3;
642 comptime assert(@max(posix.STDIN_FILENO, posix.STDOUT_FILENO, posix.STDERR_FILENO) + 1 == prog_fileno);
643
644 const envp: [*:null]const ?[*:0]const u8 = m: {
645 const prog_fd: i32 = if (prog_pipe[1] == -1) -1 else prog_fileno;
646 if (self.env_map) |env_map| {
647 break :m (try process.createEnvironFromMap(arena, env_map, .{
648 .zig_progress_fd = prog_fd,
649 })).ptr;
650 } else if (builtin.link_libc) {
651 break :m (try process.createEnvironFromExisting(arena, std.c.environ, .{
652 .zig_progress_fd = prog_fd,
653 })).ptr;
654 } else if (builtin.output_mode == .Exe) {
655 // Then we have Zig start code and this works.
656 // TODO type-safety for null-termination of `os.environ`.
657 break :m (try process.createEnvironFromExisting(arena, @ptrCast(std.os.environ.ptr), .{
658 .zig_progress_fd = prog_fd,
659 })).ptr;
660 } else {
661 // TODO come up with a solution for this.
662 @panic("missing std lib enhancement: std.process.Child implementation has no way to collect the environment variables to forward to the child process");
663 }
664 };
665
666 // This pipe communicates to the parent errors in the child between `fork` and `execvpe`.
667 // It is closed by the child (via CLOEXEC) without writing if `execvpe` succeeds.
668 const err_pipe: [2]posix.fd_t = try posix.pipe2(.{ .CLOEXEC = true });
669 errdefer destroyPipe(err_pipe);
670
671 const pid_result = try posix.fork();
672 if (pid_result == 0) {
673 // we are the child
674 setUpChildIo(self.stdin_behavior, stdin_pipe[0], posix.STDIN_FILENO, dev_null_fd) catch |err| forkChildErrReport(io, err_pipe[1], err);
675 setUpChildIo(self.stdout_behavior, stdout_pipe[1], posix.STDOUT_FILENO, dev_null_fd) catch |err| forkChildErrReport(io, err_pipe[1], err);
676 setUpChildIo(self.stderr_behavior, stderr_pipe[1], posix.STDERR_FILENO, dev_null_fd) catch |err| forkChildErrReport(io, err_pipe[1], err);
677
678 if (self.cwd_dir) |cwd| {
679 posix.fchdir(cwd.handle) catch |err| forkChildErrReport(io, err_pipe[1], err);
680 } else if (self.cwd) |cwd| {
681 posix.chdir(cwd) catch |err| forkChildErrReport(io, err_pipe[1], err);
682 }
683
684 // Must happen after fchdir above, the cwd file descriptor might be
685 // equal to prog_fileno and be clobbered by this dup2 call.
686 if (prog_pipe[1] != -1) posix.dup2(prog_pipe[1], prog_fileno) catch |err| forkChildErrReport(io, err_pipe[1], err);
687
688 if (self.gid) |gid| {
689 posix.setregid(gid, gid) catch |err| forkChildErrReport(io, err_pipe[1], err);
690 }
691
692 if (self.uid) |uid| {
693 posix.setreuid(uid, uid) catch |err| forkChildErrReport(io, err_pipe[1], err);
694 }
695
696 if (self.pgid) |pid| {
697 posix.setpgid(0, pid) catch |err| forkChildErrReport(io, err_pipe[1], err);
698 }
699
700 if (self.start_suspended) {
701 posix.kill(posix.getpid(), .STOP) catch |err| forkChildErrReport(io, err_pipe[1], err);
702 }
703
704 const err = switch (self.expand_arg0) {
705 .expand => posix.execvpeZ_expandArg0(.expand, argv_buf.ptr[0].?, argv_buf.ptr, envp),
706 .no_expand => posix.execvpeZ_expandArg0(.no_expand, argv_buf.ptr[0].?, argv_buf.ptr, envp),
707 };
708 forkChildErrReport(io, err_pipe[1], err);
709 }
710
711 // we are the parent
712 errdefer comptime unreachable; // The child is forked; we must not error from now on
713
714 posix.close(err_pipe[1]); // make sure only the child holds the write end open
715 self.err_pipe = err_pipe[0];
716
717 const pid: i32 = @intCast(pid_result);
718 if (self.stdin_behavior == .Pipe) {
719 self.stdin = .{ .handle = stdin_pipe[1] };
720 } else {
721 self.stdin = null;
722 }
723 if (self.stdout_behavior == .Pipe) {
724 self.stdout = .{ .handle = stdout_pipe[0] };
725 } else {
726 self.stdout = null;
727 }
728 if (self.stderr_behavior == .Pipe) {
729 self.stderr = .{ .handle = stderr_pipe[0] };
730 } else {
731 self.stderr = null;
732 }
733
734 self.id = pid;
735 self.term = null;
736
737 if (self.stdin_behavior == .Pipe) {
738 posix.close(stdin_pipe[0]);
739 }
740 if (self.stdout_behavior == .Pipe) {
741 posix.close(stdout_pipe[1]);
742 }
743 if (self.stderr_behavior == .Pipe) {
744 posix.close(stderr_pipe[1]);
745 }
746
747 if (prog_pipe[1] != -1) {
748 posix.close(prog_pipe[1]);
749 }
750 self.progress_node.setIpcFd(prog_pipe[0]);
751}
752
753fn spawnWindows(self: *Child, io: Io) SpawnError!void {
754 var saAttr = windows.SECURITY_ATTRIBUTES{
755 .nLength = @sizeOf(windows.SECURITY_ATTRIBUTES),
756 .bInheritHandle = windows.TRUE,
757 .lpSecurityDescriptor = null,
758 };
759
760 const any_ignore = (self.stdin_behavior == StdIo.Ignore or self.stdout_behavior == StdIo.Ignore or self.stderr_behavior == StdIo.Ignore);
761
762 const nul_handle = if (any_ignore)
763 // "\Device\Null" or "\??\NUL"
764 windows.OpenFile(&[_]u16{ '\\', 'D', 'e', 'v', 'i', 'c', 'e', '\\', 'N', 'u', 'l', 'l' }, .{
765 .access_mask = .{
766 .STANDARD = .{ .SYNCHRONIZE = true },
767 .GENERIC = .{ .WRITE = true, .READ = true },
768 },
769 .sa = &saAttr,
770 .creation = .OPEN,
771 }) catch |err| switch (err) {
772 error.PathAlreadyExists => return error.Unexpected, // not possible for "NUL"
773 error.PipeBusy => return error.Unexpected, // not possible for "NUL"
774 error.NoDevice => return error.Unexpected, // not possible for "NUL"
775 error.FileNotFound => return error.Unexpected, // not possible for "NUL"
776 error.AccessDenied => return error.Unexpected, // not possible for "NUL"
777 error.NameTooLong => return error.Unexpected, // not possible for "NUL"
778 error.WouldBlock => return error.Unexpected, // not possible for "NUL"
779 error.NetworkNotFound => return error.Unexpected, // not possible for "NUL"
780 error.AntivirusInterference => return error.Unexpected, // not possible for "NUL"
781 error.OperationCanceled => return error.Unexpected, // we're not canceling the operation
782 else => |e| return e,
783 }
784 else
785 undefined;
786 defer {
787 if (any_ignore) posix.close(nul_handle);
788 }
789
790 var g_hChildStd_IN_Rd: ?windows.HANDLE = null;
791 var g_hChildStd_IN_Wr: ?windows.HANDLE = null;
792 switch (self.stdin_behavior) {
793 StdIo.Pipe => {
794 try windowsMakePipeIn(&g_hChildStd_IN_Rd, &g_hChildStd_IN_Wr, &saAttr);
795 },
796 StdIo.Ignore => {
797 g_hChildStd_IN_Rd = nul_handle;
798 },
799 StdIo.Inherit => {
800 g_hChildStd_IN_Rd = windows.GetStdHandle(windows.STD_INPUT_HANDLE) catch null;
801 },
802 StdIo.Close => {
803 g_hChildStd_IN_Rd = null;
804 },
805 }
806 errdefer if (self.stdin_behavior == StdIo.Pipe) {
807 windowsDestroyPipe(g_hChildStd_IN_Rd, g_hChildStd_IN_Wr);
808 };
809
810 var g_hChildStd_OUT_Rd: ?windows.HANDLE = null;
811 var g_hChildStd_OUT_Wr: ?windows.HANDLE = null;
812 switch (self.stdout_behavior) {
813 StdIo.Pipe => {
814 try windowsMakeAsyncPipe(&g_hChildStd_OUT_Rd, &g_hChildStd_OUT_Wr, &saAttr);
815 },
816 StdIo.Ignore => {
817 g_hChildStd_OUT_Wr = nul_handle;
818 },
819 StdIo.Inherit => {
820 g_hChildStd_OUT_Wr = windows.GetStdHandle(windows.STD_OUTPUT_HANDLE) catch null;
821 },
822 StdIo.Close => {
823 g_hChildStd_OUT_Wr = null;
824 },
825 }
826 errdefer if (self.stdout_behavior == StdIo.Pipe) {
827 windowsDestroyPipe(g_hChildStd_OUT_Rd, g_hChildStd_OUT_Wr);
828 };
829
830 var g_hChildStd_ERR_Rd: ?windows.HANDLE = null;
831 var g_hChildStd_ERR_Wr: ?windows.HANDLE = null;
832 switch (self.stderr_behavior) {
833 StdIo.Pipe => {
834 try windowsMakeAsyncPipe(&g_hChildStd_ERR_Rd, &g_hChildStd_ERR_Wr, &saAttr);
835 },
836 StdIo.Ignore => {
837 g_hChildStd_ERR_Wr = nul_handle;
838 },
839 StdIo.Inherit => {
840 g_hChildStd_ERR_Wr = windows.GetStdHandle(windows.STD_ERROR_HANDLE) catch null;
841 },
842 StdIo.Close => {
843 g_hChildStd_ERR_Wr = null;
844 },
845 }
846 errdefer if (self.stderr_behavior == StdIo.Pipe) {
847 windowsDestroyPipe(g_hChildStd_ERR_Rd, g_hChildStd_ERR_Wr);
848 };
849
850 var siStartInfo = windows.STARTUPINFOW{
851 .cb = @sizeOf(windows.STARTUPINFOW),
852 .hStdError = g_hChildStd_ERR_Wr,
853 .hStdOutput = g_hChildStd_OUT_Wr,
854 .hStdInput = g_hChildStd_IN_Rd,
855 .dwFlags = windows.STARTF_USESTDHANDLES,
856
857 .lpReserved = null,
858 .lpDesktop = null,
859 .lpTitle = null,
860 .dwX = 0,
861 .dwY = 0,
862 .dwXSize = 0,
863 .dwYSize = 0,
864 .dwXCountChars = 0,
865 .dwYCountChars = 0,
866 .dwFillAttribute = 0,
867 .wShowWindow = 0,
868 .cbReserved2 = 0,
869 .lpReserved2 = null,
870 };
871 var piProcInfo: windows.PROCESS_INFORMATION = undefined;
872
873 const cwd_w = if (self.cwd) |cwd| try unicode.wtf8ToWtf16LeAllocZ(self.allocator, cwd) else null;
874 defer if (cwd_w) |cwd| self.allocator.free(cwd);
875 const cwd_w_ptr = if (cwd_w) |cwd| cwd.ptr else null;
876
877 const maybe_envp_buf = if (self.env_map) |env_map| try process.createWindowsEnvBlock(self.allocator, env_map) else null;
878 defer if (maybe_envp_buf) |envp_buf| self.allocator.free(envp_buf);
879 const envp_ptr = if (maybe_envp_buf) |envp_buf| envp_buf.ptr else null;
880
881 const app_name_wtf8 = self.argv[0];
882 const app_name_is_absolute = fs.path.isAbsolute(app_name_wtf8);
883
884 // the cwd set in Child is in effect when choosing the executable path
885 // to match posix semantics
886 var cwd_path_w_needs_free = false;
887 const cwd_path_w = x: {
888 // If the app name is absolute, then we need to use its dirname as the cwd
889 if (app_name_is_absolute) {
890 cwd_path_w_needs_free = true;
891 const dir = fs.path.dirname(app_name_wtf8).?;
892 break :x try unicode.wtf8ToWtf16LeAllocZ(self.allocator, dir);
893 } else if (self.cwd) |cwd| {
894 cwd_path_w_needs_free = true;
895 break :x try unicode.wtf8ToWtf16LeAllocZ(self.allocator, cwd);
896 } else {
897 break :x &[_:0]u16{}; // empty for cwd
898 }
899 };
900 defer if (cwd_path_w_needs_free) self.allocator.free(cwd_path_w);
901
902 // If the app name has more than just a filename, then we need to separate that
903 // into the basename and dirname and use the dirname as an addition to the cwd
904 // path. This is because NtQueryDirectoryFile cannot accept FileName params with
905 // path separators.
906 const app_basename_wtf8 = fs.path.basename(app_name_wtf8);
907 // If the app name is absolute, then the cwd will already have the app's dirname in it,
908 // so only populate app_dirname if app name is a relative path with > 0 path separators.
909 const maybe_app_dirname_wtf8 = if (!app_name_is_absolute) fs.path.dirname(app_name_wtf8) else null;
910 const app_dirname_w: ?[:0]u16 = x: {
911 if (maybe_app_dirname_wtf8) |app_dirname_wtf8| {
912 break :x try unicode.wtf8ToWtf16LeAllocZ(self.allocator, app_dirname_wtf8);
913 }
914 break :x null;
915 };
916 defer if (app_dirname_w != null) self.allocator.free(app_dirname_w.?);
917
918 const app_name_w = try unicode.wtf8ToWtf16LeAllocZ(self.allocator, app_basename_wtf8);
919 defer self.allocator.free(app_name_w);
920
921 const flags: windows.CreateProcessFlags = .{
922 .create_suspended = self.start_suspended,
923 .create_unicode_environment = true,
924 .create_no_window = self.create_no_window,
925 };
926
927 run: {
928 const PATH: [:0]const u16 = process.getenvW(unicode.utf8ToUtf16LeStringLiteral("PATH")) orelse &[_:0]u16{};
929 const PATHEXT: [:0]const u16 = process.getenvW(unicode.utf8ToUtf16LeStringLiteral("PATHEXT")) orelse &[_:0]u16{};
930
931 // In case the command ends up being a .bat/.cmd script, we need to escape things using the cmd.exe rules
932 // and invoke cmd.exe ourselves in order to mitigate arbitrary command execution from maliciously
933 // constructed arguments.
934 //
935 // We'll need to wait until we're actually trying to run the command to know for sure
936 // if the resolved command has the `.bat` or `.cmd` extension, so we defer actually
937 // serializing the command line until we determine how it should be serialized.
938 var cmd_line_cache = WindowsCommandLineCache.init(self.allocator, self.argv);
939 defer cmd_line_cache.deinit();
940
941 var app_buf: ArrayList(u16) = .empty;
942 defer app_buf.deinit(self.allocator);
943
944 try app_buf.appendSlice(self.allocator, app_name_w);
945
946 var dir_buf: ArrayList(u16) = .empty;
947 defer dir_buf.deinit(self.allocator);
948
949 if (cwd_path_w.len > 0) {
950 try dir_buf.appendSlice(self.allocator, cwd_path_w);
951 }
952 if (app_dirname_w) |app_dir| {
953 if (dir_buf.items.len > 0) try dir_buf.append(self.allocator, fs.path.sep);
954 try dir_buf.appendSlice(self.allocator, app_dir);
955 }
956
957 windowsCreateProcessPathExt(self.allocator, io, &dir_buf, &app_buf, PATHEXT, &cmd_line_cache, envp_ptr, cwd_w_ptr, flags, &siStartInfo, &piProcInfo) catch |no_path_err| {
958 const original_err = switch (no_path_err) {
959 // argv[0] contains unsupported characters that will never resolve to a valid exe.
960 error.InvalidArg0 => return error.FileNotFound,
961 error.FileNotFound, error.InvalidExe, error.AccessDenied => |e| e,
962 error.UnrecoverableInvalidExe => return error.InvalidExe,
963 else => |e| return e,
964 };
965
966 // If the app name had path separators, that disallows PATH searching,
967 // and there's no need to search the PATH if the app name is absolute.
968 // We still search the path if the cwd is absolute because of the
969 // "cwd set in Child is in effect when choosing the executable path
970 // to match posix semantics" behavior--we don't want to skip searching
971 // the PATH just because we were trying to set the cwd of the child process.
972 if (app_dirname_w != null or app_name_is_absolute) {
973 return original_err;
974 }
975
976 var it = mem.tokenizeScalar(u16, PATH, ';');
977 while (it.next()) |search_path| {
978 dir_buf.clearRetainingCapacity();
979 try dir_buf.appendSlice(self.allocator, search_path);
980
981 if (windowsCreateProcessPathExt(self.allocator, io, &dir_buf, &app_buf, PATHEXT, &cmd_line_cache, envp_ptr, cwd_w_ptr, flags, &siStartInfo, &piProcInfo)) {
982 break :run;
983 } else |err| switch (err) {
984 // argv[0] contains unsupported characters that will never resolve to a valid exe.
985 error.InvalidArg0 => return error.FileNotFound,
986 error.FileNotFound, error.AccessDenied, error.InvalidExe => continue,
987 error.UnrecoverableInvalidExe => return error.InvalidExe,
988 else => |e| return e,
989 }
990 } else {
991 return original_err;
992 }
993 };
994 }
995
996 if (g_hChildStd_IN_Wr) |h| {
997 self.stdin = File{ .handle = h };
998 } else {
999 self.stdin = null;
1000 }
1001 if (g_hChildStd_OUT_Rd) |h| {
1002 self.stdout = File{ .handle = h };
1003 } else {
1004 self.stdout = null;
1005 }
1006 if (g_hChildStd_ERR_Rd) |h| {
1007 self.stderr = File{ .handle = h };
1008 } else {
1009 self.stderr = null;
1010 }
1011
1012 self.id = piProcInfo.hProcess;
1013 self.thread_handle = piProcInfo.hThread;
1014 self.term = null;
1015
1016 if (self.stdin_behavior == StdIo.Pipe) {
1017 posix.close(g_hChildStd_IN_Rd.?);
1018 }
1019 if (self.stderr_behavior == StdIo.Pipe) {
1020 posix.close(g_hChildStd_ERR_Wr.?);
1021 }
1022 if (self.stdout_behavior == StdIo.Pipe) {
1023 posix.close(g_hChildStd_OUT_Wr.?);
1024 }
1025}
1026
1027fn setUpChildIo(stdio: StdIo, pipe_fd: i32, std_fileno: i32, dev_null_fd: i32) !void {
1028 switch (stdio) {
1029 .Pipe => try posix.dup2(pipe_fd, std_fileno),
1030 .Close => posix.close(std_fileno),
1031 .Inherit => {},
1032 .Ignore => try posix.dup2(dev_null_fd, std_fileno),
1033 }
1034}
1035
1036fn destroyPipe(pipe: [2]posix.fd_t) void {
1037 if (pipe[0] != -1) posix.close(pipe[0]);
1038 if (pipe[0] != pipe[1]) posix.close(pipe[1]);
1039}
1040
1041// Child of fork calls this to report an error to the fork parent.
1042// Then the child exits.
1043fn forkChildErrReport(io: Io, fd: i32, err: Child.SpawnError) noreturn {
1044 writeIntFd(io, fd, @as(ErrInt, @intFromError(err))) catch {};
1045 // If we're linking libc, some naughty applications may have registered atexit handlers
1046 // which we really do not want to run in the fork child. I caught LLVM doing this and
1047 // it caused a deadlock instead of doing an exit syscall. In the words of Avril Lavigne,
1048 // "Why'd you have to go and make things so complicated?"
1049 if (builtin.link_libc) {
1050 // The _exit(2) function does nothing but make the exit syscall, unlike exit(3)
1051 std.c._exit(1);
1052 }
1053 posix.system.exit(1);
1054}
1055
1056fn writeIntFd(io: Io, fd: i32, value: ErrInt) !void {
1057 var buffer: [8]u8 = undefined;
1058 var fw: File.Writer = .initStreaming(.{ .handle = fd }, io, &buffer);
1059 fw.interface.writeInt(u64, value, .little) catch unreachable;
1060 fw.interface.flush() catch return error.SystemResources;
1061}
1062
1063fn readIntFd(fd: i32) !ErrInt {
1064 var buffer: [8]u8 = undefined;
1065 var i: usize = 0;
1066 while (i < buffer.len) {
1067 const n = try std.posix.read(fd, buffer[i..]);
1068 if (n == 0) return error.EndOfStream;
1069 i += n;
1070 }
1071 const int = mem.readInt(u64, &buffer, .little);
1072 return @intCast(int);
1073}
1074
1075const ErrInt = std.meta.Int(.unsigned, @sizeOf(anyerror) * 8);
1076
1077/// Expects `app_buf` to contain exactly the app name, and `dir_buf` to contain exactly the dir path.
1078/// After return, `app_buf` will always contain exactly the app name and `dir_buf` will always contain exactly the dir path.
1079/// Note: `app_buf` should not contain any leading path separators.
1080/// Note: If the dir is the cwd, dir_buf should be empty (len = 0).
1081fn windowsCreateProcessPathExt(
1082 allocator: Allocator,
1083 io: Io,
1084 dir_buf: *ArrayList(u16),
1085 app_buf: *ArrayList(u16),
1086 pathext: [:0]const u16,
1087 cmd_line_cache: *WindowsCommandLineCache,
1088 envp_ptr: ?[*]u16,
1089 cwd_ptr: ?[*:0]u16,
1090 flags: windows.CreateProcessFlags,
1091 lpStartupInfo: *windows.STARTUPINFOW,
1092 lpProcessInformation: *windows.PROCESS_INFORMATION,
1093) !void {
1094 const app_name_len = app_buf.items.len;
1095 const dir_path_len = dir_buf.items.len;
1096
1097 if (app_name_len == 0) return error.FileNotFound;
1098
1099 defer app_buf.shrinkRetainingCapacity(app_name_len);
1100 defer dir_buf.shrinkRetainingCapacity(dir_path_len);
1101
1102 // The name of the game here is to avoid CreateProcessW calls at all costs,
1103 // and only ever try calling it when we have a real candidate for execution.
1104 // Secondarily, we want to minimize the number of syscalls used when checking
1105 // for each PATHEXT-appended version of the app name.
1106 //
1107 // An overview of the technique used:
1108 // - Open the search directory for iteration (either cwd or a path from PATH)
1109 // - Use NtQueryDirectoryFile with a wildcard filename of `<app name>*` to
1110 // check if anything that could possibly match either the unappended version
1111 // of the app name or any of the versions with a PATHEXT value appended exists.
1112 // - If the wildcard NtQueryDirectoryFile call found nothing, we can exit early
1113 // without needing to use PATHEXT at all.
1114 //
1115 // This allows us to use a <open dir, NtQueryDirectoryFile, close dir> sequence
1116 // for any directory that doesn't contain any possible matches, instead of having
1117 // to use a separate look up for each individual filename combination (unappended +
1118 // each PATHEXT appended). For directories where the wildcard *does* match something,
1119 // we iterate the matches and take note of any that are either the unappended version,
1120 // or a version with a supported PATHEXT appended. We then try calling CreateProcessW
1121 // with the found versions in the appropriate order.
1122
1123 // In the future, child process execution needs to move to Io implementation.
1124 // Under those conditions, here we will have access to lower level directory
1125 // opening function knowing which implementation we are in. Here, we imitate
1126 // that scenario.
1127 var dir = dir: {
1128 // needs to be null-terminated
1129 try dir_buf.append(allocator, 0);
1130 defer dir_buf.shrinkRetainingCapacity(dir_path_len);
1131 const dir_path_z = dir_buf.items[0 .. dir_buf.items.len - 1 :0];
1132 const prefixed_path = try windows.wToPrefixedFileW(null, dir_path_z);
1133 break :dir Io.Threaded.dirOpenDirWindows(.cwd(), prefixed_path.span(), .{
1134 .iterate = true,
1135 }) catch return error.FileNotFound;
1136 };
1137 defer dir.close(io);
1138
1139 // Add wildcard and null-terminator
1140 try app_buf.append(allocator, '*');
1141 try app_buf.append(allocator, 0);
1142 const app_name_wildcard = app_buf.items[0 .. app_buf.items.len - 1 :0];
1143
1144 // This 2048 is arbitrary, we just want it to be large enough to get multiple FILE_DIRECTORY_INFORMATION entries
1145 // returned per NtQueryDirectoryFile call.
1146 var file_information_buf: [2048]u8 align(@alignOf(windows.FILE_DIRECTORY_INFORMATION)) = undefined;
1147 const file_info_maximum_single_entry_size = @sizeOf(windows.FILE_DIRECTORY_INFORMATION) + (windows.NAME_MAX * 2);
1148 if (file_information_buf.len < file_info_maximum_single_entry_size) {
1149 @compileError("file_information_buf must be large enough to contain at least one maximum size FILE_DIRECTORY_INFORMATION entry");
1150 }
1151 var io_status: windows.IO_STATUS_BLOCK = undefined;
1152
1153 const num_supported_pathext = @typeInfo(WindowsExtension).@"enum".fields.len;
1154 var pathext_seen = [_]bool{false} ** num_supported_pathext;
1155 var any_pathext_seen = false;
1156 var unappended_exists = false;
1157
1158 // Fully iterate the wildcard matches via NtQueryDirectoryFile and take note of all versions
1159 // of the app_name we should try to spawn.
1160 // Note: This is necessary because the order of the files returned is filesystem-dependent:
1161 // On NTFS, `blah.exe*` will always return `blah.exe` first if it exists.
1162 // On FAT32, it's possible for something like `blah.exe.obj` to be returned first.
1163 while (true) {
1164 const app_name_len_bytes = std.math.cast(u16, app_name_wildcard.len * 2) orelse return error.NameTooLong;
1165 var app_name_unicode_string = windows.UNICODE_STRING{
1166 .Length = app_name_len_bytes,
1167 .MaximumLength = app_name_len_bytes,
1168 .Buffer = @constCast(app_name_wildcard.ptr),
1169 };
1170 const rc = windows.ntdll.NtQueryDirectoryFile(
1171 dir.handle,
1172 null,
1173 null,
1174 null,
1175 &io_status,
1176 &file_information_buf,
1177 file_information_buf.len,
1178 .Directory,
1179 windows.FALSE, // single result
1180 &app_name_unicode_string,
1181 windows.FALSE, // restart iteration
1182 );
1183
1184 // If we get nothing with the wildcard, then we can just bail out
1185 // as we know appending PATHEXT will not yield anything.
1186 switch (rc) {
1187 .SUCCESS => {},
1188 .NO_SUCH_FILE => return error.FileNotFound,
1189 .NO_MORE_FILES => break,
1190 .ACCESS_DENIED => return error.AccessDenied,
1191 else => return windows.unexpectedStatus(rc),
1192 }
1193
1194 // According to the docs, this can only happen if there is not enough room in the
1195 // buffer to write at least one complete FILE_DIRECTORY_INFORMATION entry.
1196 // Therefore, this condition should not be possible to hit with the buffer size we use.
1197 std.debug.assert(io_status.Information != 0);
1198
1199 var it = windows.FileInformationIterator(windows.FILE_DIRECTORY_INFORMATION){ .buf = &file_information_buf };
1200 while (it.next()) |info| {
1201 // Skip directories
1202 if (info.FileAttributes.DIRECTORY) continue;
1203 const filename = @as([*]u16, @ptrCast(&info.FileName))[0 .. info.FileNameLength / 2];
1204 // Because all results start with the app_name since we're using the wildcard `app_name*`,
1205 // if the length is equal to app_name then this is an exact match
1206 if (filename.len == app_name_len) {
1207 // Note: We can't break early here because it's possible that the unappended version
1208 // fails to spawn, in which case we still want to try the PATHEXT appended versions.
1209 unappended_exists = true;
1210 } else if (windowsCreateProcessSupportsExtension(filename[app_name_len..])) |pathext_ext| {
1211 pathext_seen[@intFromEnum(pathext_ext)] = true;
1212 any_pathext_seen = true;
1213 }
1214 }
1215 }
1216
1217 const unappended_err = unappended: {
1218 if (unappended_exists) {
1219 if (dir_path_len != 0) switch (dir_buf.items[dir_buf.items.len - 1]) {
1220 '/', '\\' => {},
1221 else => try dir_buf.append(allocator, fs.path.sep),
1222 };
1223 try dir_buf.appendSlice(allocator, app_buf.items[0..app_name_len]);
1224 try dir_buf.append(allocator, 0);
1225 const full_app_name = dir_buf.items[0 .. dir_buf.items.len - 1 :0];
1226
1227 const is_bat_or_cmd = bat_or_cmd: {
1228 const app_name = app_buf.items[0..app_name_len];
1229 const ext_start = std.mem.lastIndexOfScalar(u16, app_name, '.') orelse break :bat_or_cmd false;
1230 const ext = app_name[ext_start..];
1231 const ext_enum = windowsCreateProcessSupportsExtension(ext) orelse break :bat_or_cmd false;
1232 switch (ext_enum) {
1233 .cmd, .bat => break :bat_or_cmd true,
1234 else => break :bat_or_cmd false,
1235 }
1236 };
1237 const cmd_line_w = if (is_bat_or_cmd)
1238 try cmd_line_cache.scriptCommandLine(full_app_name)
1239 else
1240 try cmd_line_cache.commandLine();
1241 const app_name_w = if (is_bat_or_cmd)
1242 try cmd_line_cache.cmdExePath()
1243 else
1244 full_app_name;
1245
1246 if (windowsCreateProcess(app_name_w.ptr, cmd_line_w.ptr, envp_ptr, cwd_ptr, flags, lpStartupInfo, lpProcessInformation)) |_| {
1247 return;
1248 } else |err| switch (err) {
1249 error.FileNotFound,
1250 error.AccessDenied,
1251 => break :unappended err,
1252 error.InvalidExe => {
1253 // On InvalidExe, if the extension of the app name is .exe then
1254 // it's treated as an unrecoverable error. Otherwise, it'll be
1255 // skipped as normal.
1256 const app_name = app_buf.items[0..app_name_len];
1257 const ext_start = std.mem.lastIndexOfScalar(u16, app_name, '.') orelse break :unappended err;
1258 const ext = app_name[ext_start..];
1259 if (windows.eqlIgnoreCaseWtf16(ext, unicode.utf8ToUtf16LeStringLiteral(".EXE"))) {
1260 return error.UnrecoverableInvalidExe;
1261 }
1262 break :unappended err;
1263 },
1264 else => return err,
1265 }
1266 }
1267 break :unappended error.FileNotFound;
1268 };
1269
1270 if (!any_pathext_seen) return unappended_err;
1271
1272 // Now try any PATHEXT appended versions that we've seen
1273 var ext_it = mem.tokenizeScalar(u16, pathext, ';');
1274 while (ext_it.next()) |ext| {
1275 const ext_enum = windowsCreateProcessSupportsExtension(ext) orelse continue;
1276 if (!pathext_seen[@intFromEnum(ext_enum)]) continue;
1277
1278 dir_buf.shrinkRetainingCapacity(dir_path_len);
1279 if (dir_path_len != 0) switch (dir_buf.items[dir_buf.items.len - 1]) {
1280 '/', '\\' => {},
1281 else => try dir_buf.append(allocator, fs.path.sep),
1282 };
1283 try dir_buf.appendSlice(allocator, app_buf.items[0..app_name_len]);
1284 try dir_buf.appendSlice(allocator, ext);
1285 try dir_buf.append(allocator, 0);
1286 const full_app_name = dir_buf.items[0 .. dir_buf.items.len - 1 :0];
1287
1288 const is_bat_or_cmd = switch (ext_enum) {
1289 .cmd, .bat => true,
1290 else => false,
1291 };
1292 const cmd_line_w = if (is_bat_or_cmd)
1293 try cmd_line_cache.scriptCommandLine(full_app_name)
1294 else
1295 try cmd_line_cache.commandLine();
1296 const app_name_w = if (is_bat_or_cmd)
1297 try cmd_line_cache.cmdExePath()
1298 else
1299 full_app_name;
1300
1301 if (windowsCreateProcess(app_name_w.ptr, cmd_line_w.ptr, envp_ptr, cwd_ptr, flags, lpStartupInfo, lpProcessInformation)) |_| {
1302 return;
1303 } else |err| switch (err) {
1304 error.FileNotFound => continue,
1305 error.AccessDenied => continue,
1306 error.InvalidExe => {
1307 // On InvalidExe, if the extension of the app name is .exe then
1308 // it's treated as an unrecoverable error. Otherwise, it'll be
1309 // skipped as normal.
1310 if (windows.eqlIgnoreCaseWtf16(ext, unicode.utf8ToUtf16LeStringLiteral(".EXE"))) {
1311 return error.UnrecoverableInvalidExe;
1312 }
1313 continue;
1314 },
1315 else => return err,
1316 }
1317 }
1318
1319 return unappended_err;
1320}
1321
1322fn windowsCreateProcess(
1323 app_name: [*:0]u16,
1324 cmd_line: [*:0]u16,
1325 envp_ptr: ?[*]u16,
1326 cwd_ptr: ?[*:0]u16,
1327 flags: windows.CreateProcessFlags,
1328 lpStartupInfo: *windows.STARTUPINFOW,
1329 lpProcessInformation: *windows.PROCESS_INFORMATION,
1330) !void {
1331 // TODO the docs for environment pointer say:
1332 // > A pointer to the environment block for the new process. If this parameter
1333 // > is NULL, the new process uses the environment of the calling process.
1334 // > ...
1335 // > An environment block can contain either Unicode or ANSI characters. If
1336 // > the environment block pointed to by lpEnvironment contains Unicode
1337 // > characters, be sure that dwCreationFlags includes CREATE_UNICODE_ENVIRONMENT.
1338 // > If this parameter is NULL and the environment block of the parent process
1339 // > contains Unicode characters, you must also ensure that dwCreationFlags
1340 // > includes CREATE_UNICODE_ENVIRONMENT.
1341 // This seems to imply that we have to somehow know whether our process parent passed
1342 // CREATE_UNICODE_ENVIRONMENT if we want to pass NULL for the environment parameter.
1343 // Since we do not know this information that would imply that we must not pass NULL
1344 // for the parameter.
1345 // However this would imply that programs compiled with -DUNICODE could not pass
1346 // environment variables to programs that were not, which seems unlikely.
1347 // More investigation is needed.
1348 return windows.CreateProcessW(
1349 app_name,
1350 cmd_line,
1351 null,
1352 null,
1353 windows.TRUE,
1354 flags,
1355 @as(?*anyopaque, @ptrCast(envp_ptr)),
1356 cwd_ptr,
1357 lpStartupInfo,
1358 lpProcessInformation,
1359 );
1360}
1361
1362fn windowsMakePipeIn(rd: *?windows.HANDLE, wr: *?windows.HANDLE, sattr: *const windows.SECURITY_ATTRIBUTES) !void {
1363 var rd_h: windows.HANDLE = undefined;
1364 var wr_h: windows.HANDLE = undefined;
1365 try windows.CreatePipe(&rd_h, &wr_h, sattr);
1366 errdefer windowsDestroyPipe(rd_h, wr_h);
1367 try windows.SetHandleInformation(wr_h, windows.HANDLE_FLAG_INHERIT, 0);
1368 rd.* = rd_h;
1369 wr.* = wr_h;
1370}
1371
1372fn windowsDestroyPipe(rd: ?windows.HANDLE, wr: ?windows.HANDLE) void {
1373 if (rd) |h| posix.close(h);
1374 if (wr) |h| posix.close(h);
1375}
1376
1377fn windowsMakeAsyncPipe(rd: *?windows.HANDLE, wr: *?windows.HANDLE, sattr: *const windows.SECURITY_ATTRIBUTES) !void {
1378 var tmp_bufw: [128]u16 = undefined;
1379
1380 // Anonymous pipes are built upon Named pipes.
1381 // https://docs.microsoft.com/en-us/windows/win32/api/namedpipeapi/nf-namedpipeapi-createpipe
1382 // Asynchronous (overlapped) read and write operations are not supported by anonymous pipes.
1383 // https://docs.microsoft.com/en-us/windows/win32/ipc/anonymous-pipe-operations
1384 const pipe_path = blk: {
1385 var tmp_buf: [128]u8 = undefined;
1386 // Forge a random path for the pipe.
1387 const pipe_path = std.fmt.bufPrintSentinel(
1388 &tmp_buf,
1389 "\\\\.\\pipe\\zig-childprocess-{d}-{d}",
1390 .{ windows.GetCurrentProcessId(), pipe_name_counter.fetchAdd(1, .monotonic) },
1391 0,
1392 ) catch unreachable;
1393 const len = std.unicode.wtf8ToWtf16Le(&tmp_bufw, pipe_path) catch unreachable;
1394 tmp_bufw[len] = 0;
1395 break :blk tmp_bufw[0..len :0];
1396 };
1397
1398 // Create the read handle that can be used with overlapped IO ops.
1399 const read_handle = windows.kernel32.CreateNamedPipeW(
1400 pipe_path.ptr,
1401 windows.PIPE_ACCESS_INBOUND | windows.FILE_FLAG_OVERLAPPED,
1402 windows.PIPE_TYPE_BYTE,
1403 1,
1404 4096,
1405 4096,
1406 0,
1407 sattr,
1408 );
1409 if (read_handle == windows.INVALID_HANDLE_VALUE) {
1410 switch (windows.GetLastError()) {
1411 else => |err| return windows.unexpectedError(err),
1412 }
1413 }
1414 errdefer posix.close(read_handle);
1415
1416 var sattr_copy = sattr.*;
1417 const write_handle = windows.kernel32.CreateFileW(
1418 pipe_path.ptr,
1419 .{ .GENERIC = .{ .WRITE = true } },
1420 0,
1421 &sattr_copy,
1422 windows.OPEN_EXISTING,
1423 @bitCast(windows.FILE.ATTRIBUTE{ .NORMAL = true }),
1424 null,
1425 );
1426 if (write_handle == windows.INVALID_HANDLE_VALUE) {
1427 switch (windows.GetLastError()) {
1428 else => |err| return windows.unexpectedError(err),
1429 }
1430 }
1431 errdefer posix.close(write_handle);
1432
1433 try windows.SetHandleInformation(read_handle, windows.HANDLE_FLAG_INHERIT, 0);
1434
1435 rd.* = read_handle;
1436 wr.* = write_handle;
1437}
1438
1439var pipe_name_counter = std.atomic.Value(u32).init(1);
1440
1441/// File name extensions supported natively by `CreateProcess()` on Windows.
1442// Should be kept in sync with `windowsCreateProcessSupportsExtension`.
1443pub const WindowsExtension = enum {
1444 bat,
1445 cmd,
1446 com,
1447 exe,
1448};
1449
1450/// Case-insensitive WTF-16 lookup
1451fn windowsCreateProcessSupportsExtension(ext: []const u16) ?WindowsExtension {
1452 if (ext.len != 4) return null;
1453 const State = enum {
1454 start,
1455 dot,
1456 b,
1457 ba,
1458 c,
1459 cm,
1460 co,
1461 e,
1462 ex,
1463 };
1464 var state: State = .start;
1465 for (ext) |c| switch (state) {
1466 .start => switch (c) {
1467 '.' => state = .dot,
1468 else => return null,
1469 },
1470 .dot => switch (c) {
1471 'b', 'B' => state = .b,
1472 'c', 'C' => state = .c,
1473 'e', 'E' => state = .e,
1474 else => return null,
1475 },
1476 .b => switch (c) {
1477 'a', 'A' => state = .ba,
1478 else => return null,
1479 },
1480 .c => switch (c) {
1481 'm', 'M' => state = .cm,
1482 'o', 'O' => state = .co,
1483 else => return null,
1484 },
1485 .e => switch (c) {
1486 'x', 'X' => state = .ex,
1487 else => return null,
1488 },
1489 .ba => switch (c) {
1490 't', 'T' => return .bat,
1491 else => return null,
1492 },
1493 .cm => switch (c) {
1494 'd', 'D' => return .cmd,
1495 else => return null,
1496 },
1497 .co => switch (c) {
1498 'm', 'M' => return .com,
1499 else => return null,
1500 },
1501 .ex => switch (c) {
1502 'e', 'E' => return .exe,
1503 else => return null,
1504 },
1505 };
1506 return null;
1507}
1508
1509test windowsCreateProcessSupportsExtension {
1510 try std.testing.expectEqual(WindowsExtension.exe, windowsCreateProcessSupportsExtension(&[_]u16{ '.', 'e', 'X', 'e' }).?);
1511 try std.testing.expect(windowsCreateProcessSupportsExtension(&[_]u16{ '.', 'e', 'X', 'e', 'c' }) == null);
1512}
1513
1514/// Serializes argv into a WTF-16 encoded command-line string for use with CreateProcessW.
1515///
1516/// Serialization is done on-demand and the result is cached in order to allow for:
1517/// - Only serializing the particular type of command line needed (`.bat`/`.cmd`
1518/// command line serialization is different from `.exe`/etc)
1519/// - Reusing the serialized command lines if necessary (i.e. if the execution
1520/// of a command fails and the PATH is going to be continued to be searched
1521/// for more candidates)
1522const WindowsCommandLineCache = struct {
1523 cmd_line: ?[:0]u16 = null,
1524 script_cmd_line: ?[:0]u16 = null,
1525 cmd_exe_path: ?[:0]u16 = null,
1526 argv: []const []const u8,
1527 allocator: Allocator,
1528
1529 fn init(allocator: Allocator, argv: []const []const u8) WindowsCommandLineCache {
1530 return .{
1531 .allocator = allocator,
1532 .argv = argv,
1533 };
1534 }
1535
1536 fn deinit(self: *WindowsCommandLineCache) void {
1537 if (self.cmd_line) |cmd_line| self.allocator.free(cmd_line);
1538 if (self.script_cmd_line) |script_cmd_line| self.allocator.free(script_cmd_line);
1539 if (self.cmd_exe_path) |cmd_exe_path| self.allocator.free(cmd_exe_path);
1540 }
1541
1542 fn commandLine(self: *WindowsCommandLineCache) ![:0]u16 {
1543 if (self.cmd_line == null) {
1544 self.cmd_line = try argvToCommandLineWindows(self.allocator, self.argv);
1545 }
1546 return self.cmd_line.?;
1547 }
1548
1549 /// Not cached, since the path to the batch script will change during PATH searching.
1550 /// `script_path` should be as qualified as possible, e.g. if the PATH is being searched,
1551 /// then script_path should include both the search path and the script filename
1552 /// (this allows avoiding cmd.exe having to search the PATH again).
1553 fn scriptCommandLine(self: *WindowsCommandLineCache, script_path: []const u16) ![:0]u16 {
1554 if (self.script_cmd_line) |v| self.allocator.free(v);
1555 self.script_cmd_line = try argvToScriptCommandLineWindows(
1556 self.allocator,
1557 script_path,
1558 self.argv[1..],
1559 );
1560 return self.script_cmd_line.?;
1561 }
1562
1563 fn cmdExePath(self: *WindowsCommandLineCache) ![:0]u16 {
1564 if (self.cmd_exe_path == null) {
1565 self.cmd_exe_path = try windowsCmdExePath(self.allocator);
1566 }
1567 return self.cmd_exe_path.?;
1568 }
1569};
1570
1571/// Returns the absolute path of `cmd.exe` within the Windows system directory.
1572/// The caller owns the returned slice.
1573fn windowsCmdExePath(allocator: Allocator) error{ OutOfMemory, Unexpected }![:0]u16 {
1574 var buf = try ArrayList(u16).initCapacity(allocator, 128);
1575 errdefer buf.deinit(allocator);
1576 while (true) {
1577 const unused_slice = buf.unusedCapacitySlice();
1578 // TODO: Get the system directory from PEB.ReadOnlyStaticServerData
1579 const len = windows.kernel32.GetSystemDirectoryW(@ptrCast(unused_slice), @intCast(unused_slice.len));
1580 if (len == 0) {
1581 switch (windows.GetLastError()) {
1582 else => |err| return windows.unexpectedError(err),
1583 }
1584 }
1585 if (len > unused_slice.len) {
1586 try buf.ensureUnusedCapacity(allocator, len);
1587 } else {
1588 buf.items.len = len;
1589 break;
1590 }
1591 }
1592 switch (buf.items[buf.items.len - 1]) {
1593 '/', '\\' => {},
1594 else => try buf.append(allocator, fs.path.sep),
1595 }
1596 try buf.appendSlice(allocator, unicode.utf8ToUtf16LeStringLiteral("cmd.exe"));
1597 return try buf.toOwnedSliceSentinel(allocator, 0);
1598}
1599
1600const ArgvToCommandLineError = error{ OutOfMemory, InvalidWtf8, InvalidArg0 };
1601
1602/// Serializes `argv` to a Windows command-line string suitable for passing to a child process and
1603/// parsing by the `CommandLineToArgvW` algorithm. The caller owns the returned slice.
1604///
1605/// To avoid arbitrary command execution, this function should not be used when spawning `.bat`/`.cmd` scripts.
1606/// https://flatt.tech/research/posts/batbadbut-you-cant-securely-execute-commands-on-windows/
1607///
1608/// When executing `.bat`/`.cmd` scripts, use `argvToScriptCommandLineWindows` instead.
1609fn argvToCommandLineWindows(
1610 allocator: Allocator,
1611 argv: []const []const u8,
1612) ArgvToCommandLineError![:0]u16 {
1613 var buf = std.array_list.Managed(u8).init(allocator);
1614 defer buf.deinit();
1615
1616 if (argv.len != 0) {
1617 const arg0 = argv[0];
1618
1619 // The first argument must be quoted if it contains spaces or ASCII control characters
1620 // (excluding DEL). It also follows special quoting rules where backslashes have no special
1621 // interpretation, which makes it impossible to pass certain first arguments containing
1622 // double quotes to a child process without characters from the first argument leaking into
1623 // subsequent ones (which could have security implications).
1624 //
1625 // Empty arguments technically don't need quotes, but we quote them anyway for maximum
1626 // compatibility with different implementations of the 'CommandLineToArgvW' algorithm.
1627 //
1628 // Double quotes are illegal in paths on Windows, so for the sake of simplicity we reject
1629 // all first arguments containing double quotes, even ones that we could theoretically
1630 // serialize in unquoted form.
1631 var needs_quotes = arg0.len == 0;
1632 for (arg0) |c| {
1633 if (c <= ' ') {
1634 needs_quotes = true;
1635 } else if (c == '"') {
1636 return error.InvalidArg0;
1637 }
1638 }
1639 if (needs_quotes) {
1640 try buf.append('"');
1641 try buf.appendSlice(arg0);
1642 try buf.append('"');
1643 } else {
1644 try buf.appendSlice(arg0);
1645 }
1646
1647 for (argv[1..]) |arg| {
1648 try buf.append(' ');
1649
1650 // Subsequent arguments must be quoted if they contain spaces, tabs or double quotes,
1651 // or if they are empty. For simplicity and for maximum compatibility with different
1652 // implementations of the 'CommandLineToArgvW' algorithm, we also quote all ASCII
1653 // control characters (again, excluding DEL).
1654 needs_quotes = for (arg) |c| {
1655 if (c <= ' ' or c == '"') {
1656 break true;
1657 }
1658 } else arg.len == 0;
1659 if (!needs_quotes) {
1660 try buf.appendSlice(arg);
1661 continue;
1662 }
1663
1664 try buf.append('"');
1665 var backslash_count: usize = 0;
1666 for (arg) |byte| {
1667 switch (byte) {
1668 '\\' => {
1669 backslash_count += 1;
1670 },
1671 '"' => {
1672 try buf.appendNTimes('\\', backslash_count * 2 + 1);
1673 try buf.append('"');
1674 backslash_count = 0;
1675 },
1676 else => {
1677 try buf.appendNTimes('\\', backslash_count);
1678 try buf.append(byte);
1679 backslash_count = 0;
1680 },
1681 }
1682 }
1683 try buf.appendNTimes('\\', backslash_count * 2);
1684 try buf.append('"');
1685 }
1686 }
1687
1688 return try unicode.wtf8ToWtf16LeAllocZ(allocator, buf.items);
1689}
1690
1691test argvToCommandLineWindows {
1692 const t = testArgvToCommandLineWindows;
1693
1694 try t(&.{
1695 \\C:\Program Files\zig\zig.exe
1696 ,
1697 \\run
1698 ,
1699 \\.\src\main.zig
1700 ,
1701 \\-target
1702 ,
1703 \\x86_64-windows-gnu
1704 ,
1705 \\-O
1706 ,
1707 \\ReleaseSafe
1708 ,
1709 \\--
1710 ,
1711 \\--emoji=🗿
1712 ,
1713 \\--eval=new Regex("Dwayne \"The Rock\" Johnson")
1714 ,
1715 },
1716 \\"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\")"
1717 );
1718
1719 try t(&.{}, "");
1720 try t(&.{""}, "\"\"");
1721 try t(&.{" "}, "\" \"");
1722 try t(&.{"\t"}, "\"\t\"");
1723 try t(&.{"\x07"}, "\"\x07\"");
1724 try t(&.{"🦎"}, "🦎");
1725
1726 try t(
1727 &.{ "zig", "aa aa", "bb\tbb", "cc\ncc", "dd\r\ndd", "ee\x7Fee" },
1728 "zig \"aa aa\" \"bb\tbb\" \"cc\ncc\" \"dd\r\ndd\" ee\x7Fee",
1729 );
1730
1731 try t(
1732 &.{ "\\\\foo bar\\foo bar\\", "\\\\zig zag\\zig zag\\" },
1733 "\"\\\\foo bar\\foo bar\\\" \"\\\\zig zag\\zig zag\\\\\"",
1734 );
1735
1736 try std.testing.expectError(
1737 error.InvalidArg0,
1738 argvToCommandLineWindows(std.testing.allocator, &.{"\"quotes\"quotes\""}),
1739 );
1740 try std.testing.expectError(
1741 error.InvalidArg0,
1742 argvToCommandLineWindows(std.testing.allocator, &.{"quotes\"quotes"}),
1743 );
1744 try std.testing.expectError(
1745 error.InvalidArg0,
1746 argvToCommandLineWindows(std.testing.allocator, &.{"q u o t e s \" q u o t e s"}),
1747 );
1748}
1749
1750fn testArgvToCommandLineWindows(argv: []const []const u8, expected_cmd_line: []const u8) !void {
1751 const cmd_line_w = try argvToCommandLineWindows(std.testing.allocator, argv);
1752 defer std.testing.allocator.free(cmd_line_w);
1753
1754 const cmd_line = try unicode.wtf16LeToWtf8Alloc(std.testing.allocator, cmd_line_w);
1755 defer std.testing.allocator.free(cmd_line);
1756
1757 try std.testing.expectEqualStrings(expected_cmd_line, cmd_line);
1758}
1759
1760const ArgvToScriptCommandLineError = error{
1761 OutOfMemory,
1762 InvalidWtf8,
1763 /// NUL (U+0000), LF (U+000A), CR (U+000D) are not allowed
1764 /// within arguments when executing a `.bat`/`.cmd` script.
1765 /// - NUL/LF signifiies end of arguments, so anything afterwards
1766 /// would be lost after execution.
1767 /// - CR is stripped by `cmd.exe`, so any CR codepoints
1768 /// would be lost after execution.
1769 InvalidBatchScriptArg,
1770};
1771
1772/// Serializes `argv` to a Windows command-line string that uses `cmd.exe /c` and `cmd.exe`-specific
1773/// escaping rules. The caller owns the returned slice.
1774///
1775/// Escapes `argv` using the suggested mitigation against arbitrary command execution from:
1776/// https://flatt.tech/research/posts/batbadbut-you-cant-securely-execute-commands-on-windows/
1777///
1778/// The return of this function will look like
1779/// `cmd.exe /d /e:ON /v:OFF /c "<escaped command line>"`
1780/// and should be used as the `lpCommandLine` of `CreateProcessW`, while the
1781/// return of `windowsCmdExePath` should be used as `lpApplicationName`.
1782///
1783/// Should only be used when spawning `.bat`/`.cmd` scripts, see `argvToCommandLineWindows` otherwise.
1784/// The `.bat`/`.cmd` file must be known to both have the `.bat`/`.cmd` extension and exist on the filesystem.
1785fn argvToScriptCommandLineWindows(
1786 allocator: Allocator,
1787 /// Path to the `.bat`/`.cmd` script. If this path is relative, it is assumed to be relative to the CWD.
1788 /// The script must have been verified to exist at this path before calling this function.
1789 script_path: []const u16,
1790 /// Arguments, not including the script name itself. Expected to be encoded as WTF-8.
1791 script_args: []const []const u8,
1792) ArgvToScriptCommandLineError![:0]u16 {
1793 var buf = try std.array_list.Managed(u8).initCapacity(allocator, 64);
1794 defer buf.deinit();
1795
1796 // `/d` disables execution of AutoRun commands.
1797 // `/e:ON` and `/v:OFF` are needed for BatBadBut mitigation:
1798 // > If delayed expansion is enabled via the registry value DelayedExpansion,
1799 // > it must be disabled by explicitly calling cmd.exe with the /V:OFF option.
1800 // > Escaping for % requires the command extension to be enabled.
1801 // > If it’s disabled via the registry value EnableExtensions, it must be enabled with the /E:ON option.
1802 // https://flatt.tech/research/posts/batbadbut-you-cant-securely-execute-commands-on-windows/
1803 buf.appendSliceAssumeCapacity("cmd.exe /d /e:ON /v:OFF /c \"");
1804
1805 // Always quote the path to the script arg
1806 buf.appendAssumeCapacity('"');
1807 // We always want the path to the batch script to include a path separator in order to
1808 // avoid cmd.exe searching the PATH for the script. This is not part of the arbitrary
1809 // command execution mitigation, we just know exactly what script we want to execute
1810 // at this point, and potentially making cmd.exe re-find it is unnecessary.
1811 //
1812 // If the script path does not have a path separator, then we know its relative to CWD and
1813 // we can just put `.\` in the front.
1814 if (mem.findAny(u16, script_path, &[_]u16{ mem.nativeToLittle(u16, '\\'), mem.nativeToLittle(u16, '/') }) == null) {
1815 try buf.appendSlice(".\\");
1816 }
1817 // Note that we don't do any escaping/mitigations for this argument, since the relevant
1818 // characters (", %, etc) are illegal in file paths and this function should only be called
1819 // with script paths that have been verified to exist.
1820 try unicode.wtf16LeToWtf8ArrayList(&buf, script_path);
1821 buf.appendAssumeCapacity('"');
1822
1823 for (script_args) |arg| {
1824 // Literal carriage returns get stripped when run through cmd.exe
1825 // and NUL/newlines act as 'end of command.' Because of this, it's basically
1826 // always a mistake to include these characters in argv, so it's
1827 // an error condition in order to ensure that the return of this
1828 // function can always roundtrip through cmd.exe.
1829 if (std.mem.findAny(u8, arg, "\x00\r\n") != null) {
1830 return error.InvalidBatchScriptArg;
1831 }
1832
1833 // Separate args with a space.
1834 try buf.append(' ');
1835
1836 // Need to quote if the argument is empty (otherwise the arg would just be lost)
1837 // or if the last character is a `\`, since then something like "%~2" in a .bat
1838 // script would cause the closing " to be escaped which we don't want.
1839 var needs_quotes = arg.len == 0 or arg[arg.len - 1] == '\\';
1840 if (!needs_quotes) {
1841 for (arg) |c| {
1842 switch (c) {
1843 // Known good characters that don't need to be quoted
1844 'A'...'Z', 'a'...'z', '0'...'9', '#', '$', '*', '+', '-', '.', '/', ':', '?', '@', '\\', '_' => {},
1845 // When in doubt, quote
1846 else => {
1847 needs_quotes = true;
1848 break;
1849 },
1850 }
1851 }
1852 }
1853 if (needs_quotes) {
1854 try buf.append('"');
1855 }
1856 var backslashes: usize = 0;
1857 for (arg) |c| {
1858 switch (c) {
1859 '\\' => {
1860 backslashes += 1;
1861 },
1862 '"' => {
1863 try buf.appendNTimes('\\', backslashes);
1864 try buf.append('"');
1865 backslashes = 0;
1866 },
1867 // Replace `%` with `%%cd:~,%`.
1868 //
1869 // cmd.exe allows extracting a substring from an environment
1870 // variable with the syntax: `%foo:~<start_index>,<end_index>%`.
1871 // Therefore, `%cd:~,%` will always expand to an empty string
1872 // since both the start and end index are blank, and it is assumed
1873 // that `%cd%` is always available since it is a built-in variable
1874 // that corresponds to the current directory.
1875 //
1876 // This means that replacing `%foo%` with `%%cd:~,%foo%%cd:~,%`
1877 // will stop `%foo%` from being expanded and *after* expansion
1878 // we'll still be left with `%foo%` (the literal string).
1879 '%' => {
1880 // the trailing `%` is appended outside the switch
1881 try buf.appendSlice("%%cd:~,");
1882 backslashes = 0;
1883 },
1884 else => {
1885 backslashes = 0;
1886 },
1887 }
1888 try buf.append(c);
1889 }
1890 if (needs_quotes) {
1891 try buf.appendNTimes('\\', backslashes);
1892 try buf.append('"');
1893 }
1894 }
1895
1896 try buf.append('"');
1897
1898 return try unicode.wtf8ToWtf16LeAllocZ(allocator, buf.items);
1899}
lib/std/process/Environ.zig created+822
...@@ -0,0 +1,822 @@
1const Environ = @This();
2
3const builtin = @import("builtin");
4const native_os = builtin.os.tag;
5
6const std = @import("../std.zig");
7const Allocator = std.mem.Allocator;
8const assert = std.debug.assert;
9const testing = std.testing;
10const unicode = std.unicode;
11const posix = std.posix;
12const mem = std.mem;
13
14/// Unmodified, unprocessed data provided by the operating system.
15block: Block,
16
17pub const empty: Environ = .{
18 .block = switch (Block) {
19 void => {},
20 else => &.{},
21 },
22};
23
24/// On WASI without libc, this is `void` because the environment has to be
25/// queried and heap-allocated at runtime.
26///
27/// On Windows, the memory pointed at by the PEB changes when the environment
28/// is modified, so a long-lived pointer cannot be used. Therefore, on this
29/// operating system `void` is also used.
30pub const Block = switch (native_os) {
31 .windows => void,
32 .wasi => switch (builtin.link_libc) {
33 false => void,
34 true => [:null]const ?[*:0]const u8,
35 },
36 .freestanding, .other => void,
37 else => [:null]const ?[*:0]const u8,
38};
39
40pub const Map = struct {
41 array_hash_map: ArrayHashMap,
42 allocator: Allocator,
43
44 const ArrayHashMap = std.ArrayHashMapUnmanaged([]const u8, []const u8, EnvNameHashContext, false);
45
46 pub const Size = usize;
47
48 pub const EnvNameHashContext = struct {
49 fn upcase(c: u21) u21 {
50 if (c <= std.math.maxInt(u16))
51 return std.os.windows.ntdll.RtlUpcaseUnicodeChar(@as(u16, @intCast(c)));
52 return c;
53 }
54
55 pub fn hash(self: @This(), s: []const u8) u32 {
56 _ = self;
57 if (native_os == .windows) {
58 var h = std.hash.Wyhash.init(0);
59 var it = unicode.Wtf8View.initUnchecked(s).iterator();
60 while (it.nextCodepoint()) |cp| {
61 const cp_upper = upcase(cp);
62 h.update(&[_]u8{
63 @as(u8, @intCast((cp_upper >> 16) & 0xff)),
64 @as(u8, @intCast((cp_upper >> 8) & 0xff)),
65 @as(u8, @intCast((cp_upper >> 0) & 0xff)),
66 });
67 }
68 return @truncate(h.final());
69 }
70 return std.array_hash_map.hashString(s);
71 }
72
73 pub fn eql(self: @This(), a: []const u8, b: []const u8, b_index: usize) bool {
74 _ = self;
75 _ = b_index;
76 if (native_os == .windows) {
77 var it_a = unicode.Wtf8View.initUnchecked(a).iterator();
78 var it_b = unicode.Wtf8View.initUnchecked(b).iterator();
79 while (true) {
80 const c_a = it_a.nextCodepoint() orelse break;
81 const c_b = it_b.nextCodepoint() orelse return false;
82 if (upcase(c_a) != upcase(c_b))
83 return false;
84 }
85 return if (it_b.nextCodepoint()) |_| false else true;
86 }
87 return std.array_hash_map.eqlString(a, b);
88 }
89 };
90
91 /// Create a Map backed by a specific allocator.
92 /// That allocator will be used for both backing allocations
93 /// and string deduplication.
94 pub fn init(allocator: Allocator) Map {
95 return .{ .array_hash_map = .empty, .allocator = allocator };
96 }
97
98 /// Free the backing storage of the map, as well as all
99 /// of the stored keys and values.
100 pub fn deinit(self: *Map) void {
101 const gpa = self.allocator;
102 var it = self.array_hash_map.iterator();
103 while (it.next()) |entry| {
104 gpa.free(entry.key_ptr.*);
105 gpa.free(entry.value_ptr.*);
106 }
107 self.array_hash_map.deinit(gpa);
108 self.* = undefined;
109 }
110
111 pub fn keys(m: *const Map) [][]const u8 {
112 return m.array_hash_map.keys();
113 }
114
115 pub fn values(m: *const Map) [][]const u8 {
116 return m.array_hash_map.values();
117 }
118
119 /// Same as `put` but the key and value become owned by the Map rather
120 /// than being copied.
121 /// If `putMove` fails, the ownership of key and value does not transfer.
122 /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string.
123 pub fn putMove(self: *Map, key: []u8, value: []u8) !void {
124 const gpa = self.allocator;
125 assert(unicode.wtf8ValidateSlice(key));
126 const get_or_put = try self.array_hash_map.getOrPut(gpa, key);
127 if (get_or_put.found_existing) {
128 gpa.free(get_or_put.key_ptr.*);
129 gpa.free(get_or_put.value_ptr.*);
130 get_or_put.key_ptr.* = key;
131 }
132 get_or_put.value_ptr.* = value;
133 }
134
135 /// `key` and `value` are copied into the Map.
136 /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string.
137 pub fn put(self: *Map, key: []const u8, value: []const u8) !void {
138 assert(unicode.wtf8ValidateSlice(key));
139 const gpa = self.allocator;
140 const value_copy = try gpa.dupe(u8, value);
141 errdefer gpa.free(value_copy);
142 const get_or_put = try self.array_hash_map.getOrPut(gpa, key);
143 errdefer {
144 if (!get_or_put.found_existing) assert(self.array_hash_map.pop() != null);
145 }
146 if (get_or_put.found_existing) {
147 gpa.free(get_or_put.value_ptr.*);
148 } else {
149 get_or_put.key_ptr.* = try gpa.dupe(u8, key);
150 }
151 get_or_put.value_ptr.* = value_copy;
152 }
153
154 /// Find the address of the value associated with a key.
155 /// The returned pointer is invalidated if the map resizes.
156 /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string.
157 pub fn getPtr(self: Map, key: []const u8) ?*[]const u8 {
158 assert(unicode.wtf8ValidateSlice(key));
159 return self.array_hash_map.getPtr(key);
160 }
161
162 /// Return the map's copy of the value associated with
163 /// a key. The returned string is invalidated if this
164 /// key is removed from the map.
165 /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string.
166 pub fn get(self: Map, key: []const u8) ?[]const u8 {
167 assert(unicode.wtf8ValidateSlice(key));
168 return self.array_hash_map.get(key);
169 }
170
171 pub fn contains(m: *const Map, key: []const u8) bool {
172 return m.array_hash_map.contains(key);
173 }
174
175 /// If there is an entry with a matching key, it is deleted from the hash
176 /// map. The entry is removed from the underlying array by swapping it with
177 /// the last element.
178 ///
179 /// Returns true if an entry was removed, false otherwise.
180 ///
181 /// This invalidates the value returned by get() for this key.
182 /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string.
183 pub fn swapRemove(self: *Map, key: []const u8) bool {
184 assert(unicode.wtf8ValidateSlice(key));
185 const kv = self.array_hash_map.fetchSwapRemove(key) orelse return false;
186 const gpa = self.allocator;
187 gpa.free(kv.key);
188 gpa.free(kv.value);
189 return true;
190 }
191
192 /// If there is an entry with a matching key, it is deleted from the map.
193 /// The entry is removed from the underlying array by shifting all elements
194 /// forward, thereby maintaining the current ordering.
195 ///
196 /// Returns true if an entry was removed, false otherwise.
197 ///
198 /// This invalidates the value returned by get() for this key.
199 /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string.
200 pub fn orderedRemove(self: *Map, key: []const u8) bool {
201 assert(unicode.wtf8ValidateSlice(key));
202 const kv = self.array_hash_map.fetchOrderedRemove(key) orelse return false;
203 const gpa = self.allocator;
204 gpa.free(kv.key);
205 gpa.free(kv.value);
206 return true;
207 }
208
209 /// Returns the number of KV pairs stored in the map.
210 pub fn count(self: Map) Size {
211 return self.array_hash_map.count();
212 }
213
214 /// Returns an iterator over entries in the map.
215 pub fn iterator(self: *const Map) ArrayHashMap.Iterator {
216 return self.array_hash_map.iterator();
217 }
218
219 /// Returns a full copy of `em` allocated with `gpa`, which is not necessarily
220 /// the same allocator used to allocate `em`.
221 pub fn clone(m: *const Map, gpa: Allocator) Allocator.Error!Map {
222 // Since we need to dupe the keys and values, the only way for error handling to not be a
223 // nightmare is to add keys to an empty map one-by-one. This could be avoided if this
224 // abstraction were a bit less... OOP-esque.
225 var new: Map = .init(gpa);
226 errdefer new.deinit();
227 try new.array_hash_map.ensureUnusedCapacity(gpa, m.array_hash_map.count());
228 for (m.array_hash_map.keys(), m.array_hash_map.values()) |key, value| {
229 try new.put(key, value);
230 }
231 return new;
232 }
233
234 /// Creates a null-delimited environment variable block in the format
235 /// expected by POSIX, from a hash map plus options.
236 pub fn createBlockPosix(
237 map: *const Map,
238 arena: Allocator,
239 options: CreateBlockPosixOptions,
240 ) Allocator.Error![:null]?[*:0]u8 {
241 const ZigProgressAction = enum { nothing, edit, delete, add };
242 const zig_progress_action: ZigProgressAction = a: {
243 const fd = options.zig_progress_fd orelse break :a .nothing;
244 const exists = map.get("ZIG_PROGRESS") != null;
245 if (fd >= 0) {
246 break :a if (exists) .edit else .add;
247 } else {
248 if (exists) break :a .delete;
249 }
250 break :a .nothing;
251 };
252
253 const envp_count: usize = c: {
254 var c: usize = map.count();
255 switch (zig_progress_action) {
256 .add => c += 1,
257 .delete => c -= 1,
258 .nothing, .edit => {},
259 }
260 break :c c;
261 };
262
263 const envp_buf = try arena.allocSentinel(?[*:0]u8, envp_count, null);
264 var i: usize = 0;
265
266 if (zig_progress_action == .add) {
267 envp_buf[i] = try std.fmt.allocPrintSentinel(arena, "ZIG_PROGRESS={d}", .{options.zig_progress_fd.?}, 0);
268 i += 1;
269 }
270
271 {
272 var it = map.iterator();
273 while (it.next()) |pair| {
274 if (mem.eql(u8, pair.key_ptr.*, "ZIG_PROGRESS")) switch (zig_progress_action) {
275 .add => unreachable,
276 .delete => continue,
277 .edit => {
278 envp_buf[i] = try std.fmt.allocPrintSentinel(arena, "{s}={d}", .{
279 pair.key_ptr.*, options.zig_progress_fd.?,
280 }, 0);
281 i += 1;
282 continue;
283 },
284 .nothing => {},
285 };
286
287 envp_buf[i] = try std.fmt.allocPrintSentinel(arena, "{s}={s}", .{ pair.key_ptr.*, pair.value_ptr.* }, 0);
288 i += 1;
289 }
290 }
291
292 assert(i == envp_count);
293 return envp_buf;
294 }
295
296 /// Caller owns result.
297 pub fn createBlockWindows(map: *const Map, gpa: Allocator) error{ OutOfMemory, InvalidWtf8 }![:0]u16 {
298 // count bytes needed
299 const max_chars_needed = x: {
300 // Only need 2 trailing NUL code units for an empty environment
301 var max_chars_needed: usize = if (map.count() == 0) 2 else 1;
302 var it = map.iterator();
303 while (it.next()) |pair| {
304 // +1 for '='
305 // +1 for null byte
306 max_chars_needed += pair.key_ptr.len + pair.value_ptr.len + 2;
307 }
308 break :x max_chars_needed;
309 };
310 const result = try gpa.alloc(u16, max_chars_needed);
311 errdefer gpa.free(result);
312
313 var it = map.iterator();
314 var i: usize = 0;
315 while (it.next()) |pair| {
316 i += try unicode.wtf8ToWtf16Le(result[i..], pair.key_ptr.*);
317 result[i] = '=';
318 i += 1;
319 i += try unicode.wtf8ToWtf16Le(result[i..], pair.value_ptr.*);
320 result[i] = 0;
321 i += 1;
322 }
323 result[i] = 0;
324 i += 1;
325 // An empty environment is a special case that requires a redundant
326 // NUL terminator. CreateProcess will read the second code unit even
327 // though theoretically the first should be enough to recognize that the
328 // environment is empty (see https://nullprogram.com/blog/2023/08/23/)
329 if (map.count() == 0) {
330 result[i] = 0;
331 i += 1;
332 }
333 const reallocated = try gpa.realloc(result, i);
334 return reallocated[0 .. i - 1 :0];
335 }
336};
337
338pub const CreateMapError = error{
339 OutOfMemory,
340 /// WASI-only. `environ_sizes_get` or `environ_get` failed for an
341 /// unanticipated, undocumented reason.
342 Unexpected,
343};
344
345/// Allocates a `Map` and copies environment block into it.
346pub fn createMap(env: Environ, allocator: Allocator) CreateMapError!Map {
347 if (native_os == .windows)
348 return createMapWide(std.os.windows.peb().ProcessParameters.Environment, allocator);
349
350 var result = Map.init(allocator);
351 errdefer result.deinit();
352
353 if (native_os == .wasi and !builtin.link_libc) {
354 var environ_count: usize = undefined;
355 var environ_buf_size: usize = undefined;
356
357 const environ_sizes_get_ret = std.os.wasi.environ_sizes_get(&environ_count, &environ_buf_size);
358 if (environ_sizes_get_ret != .SUCCESS) {
359 return posix.unexpectedErrno(environ_sizes_get_ret);
360 }
361
362 if (environ_count == 0) {
363 return result;
364 }
365
366 const environ = try allocator.alloc([*:0]u8, environ_count);
367 defer allocator.free(environ);
368 const environ_buf = try allocator.alloc(u8, environ_buf_size);
369 defer allocator.free(environ_buf);
370
371 const environ_get_ret = std.os.wasi.environ_get(environ.ptr, environ_buf.ptr);
372 if (environ_get_ret != .SUCCESS) {
373 return posix.unexpectedErrno(environ_get_ret);
374 }
375
376 for (environ) |line| {
377 const pair = mem.sliceTo(line, 0);
378 var parts = mem.splitScalar(u8, pair, '=');
379 const key = parts.first();
380 const value = parts.rest();
381 try result.put(key, value);
382 }
383 return result;
384 } else {
385 for (env.block) |opt_line| {
386 const line = opt_line.?;
387 var line_i: usize = 0;
388 while (line[line_i] != 0 and line[line_i] != '=') : (line_i += 1) {}
389 const key = line[0..line_i];
390
391 var end_i: usize = line_i;
392 while (line[end_i] != 0) : (end_i += 1) {}
393 const value = line[line_i + 1 .. end_i];
394
395 try result.put(key, value);
396 }
397 return result;
398 }
399}
400
401pub fn createMapWide(ptr: [*:0]u16, gpa: Allocator) CreateMapError!Map {
402 var result = Map.init(gpa);
403 errdefer result.deinit();
404
405 var i: usize = 0;
406 while (ptr[i] != 0) {
407 const key_start = i;
408
409 // There are some special environment variables that start with =,
410 // so we need a special case to not treat = as a key/value separator
411 // if it's the first character.
412 // https://devblogs.microsoft.com/oldnewthing/20100506-00/?p=14133
413 if (ptr[key_start] == '=') i += 1;
414
415 while (ptr[i] != 0 and ptr[i] != '=') : (i += 1) {}
416 const key_w = ptr[key_start..i];
417 const key = try unicode.wtf16LeToWtf8Alloc(gpa, key_w);
418 errdefer gpa.free(key);
419
420 if (ptr[i] == '=') i += 1;
421
422 const value_start = i;
423 while (ptr[i] != 0) : (i += 1) {}
424 const value_w = ptr[value_start..i];
425 const value = try unicode.wtf16LeToWtf8Alloc(gpa, value_w);
426 errdefer gpa.free(value);
427
428 i += 1; // skip over null byte
429
430 try result.putMove(key, value);
431 }
432 return result;
433}
434
435pub const ContainsError = error{
436 OutOfMemory,
437 /// On Windows, environment variable keys provided by the user must be
438 /// valid [WTF-8](https://wtf-8.codeberg.page/). This error is unreachable
439 /// if the key is statically known to be valid.
440 InvalidWtf8,
441 /// WASI-only. `environ_sizes_get` or `environ_get` failed for an
442 /// unexpected reason.
443 Unexpected,
444};
445
446/// On Windows, if `key` is not valid [WTF-8](https://wtf-8.codeberg.page/),
447/// then `error.InvalidWtf8` is returned.
448///
449/// See also:
450/// * `createMap`
451/// * `containsConstant`
452/// * `containsUnempty`
453pub fn contains(environ: Environ, gpa: Allocator, key: []const u8) ContainsError!bool {
454 var map = try createMap(environ, gpa);
455 defer map.deinit();
456 return map.contains(key);
457}
458
459/// On Windows, if `key` is not valid [WTF-8](https://wtf-8.codeberg.page/),
460/// then `error.InvalidWtf8` is returned.
461///
462/// See also:
463/// * `createMap`
464/// * `containsUnemptyConstant`
465/// * `contains`
466pub fn containsUnempty(environ: Environ, gpa: Allocator, key: []const u8) ContainsError!bool {
467 var map = try createMap(environ, gpa);
468 defer map.deinit();
469 const value = map.get(key) orelse return false;
470 return value.len != 0;
471}
472
473/// This function is unavailable on WASI without libc due to the memory
474/// allocation requirement.
475///
476/// On Windows, `key` must be valid [WTF-8](https://wtf-8.codeberg.page/),
477///
478/// See also:
479/// * `contains`
480/// * `containsUnemptyConstant`
481/// * `createMap`
482pub inline fn containsConstant(environ: Environ, comptime key: []const u8) bool {
483 if (native_os == .windows) {
484 const key_w = comptime unicode.wtf8ToWtf16LeStringLiteral(key);
485 return getWindows(environ, key_w) != null;
486 } else {
487 return getPosix(environ, key) != null;
488 }
489}
490
491/// This function is unavailable on WASI without libc due to the memory
492/// allocation requirement.
493///
494/// On Windows, `key` must be valid [WTF-8](https://wtf-8.codeberg.page/),
495///
496/// See also:
497/// * `containsUnempty`
498/// * `containsConstant`
499/// * `createMap`
500pub inline fn containsUnemptyConstant(environ: Environ, comptime key: []const u8) bool {
501 if (native_os == .windows) {
502 const key_w = comptime unicode.wtf8ToWtf16LeStringLiteral(key);
503 const value = getWindows(environ, key_w) orelse return false;
504 return value.len != 0;
505 } else {
506 const value = getPosix(environ, key) orelse return false;
507 return value.len != 0;
508 }
509}
510
511/// This function is unavailable on WASI without libc due to the memory
512/// allocation requirement.
513///
514/// See also:
515/// * `getWindows`
516/// * `createMap`
517pub fn getPosix(environ: Environ, key: []const u8) ?[:0]const u8 {
518 if (mem.findScalar(u8, key, '=') != null) return null;
519 for (environ.block) |opt_line| {
520 const line = opt_line.?;
521 var line_i: usize = 0;
522 while (line[line_i] != 0) : (line_i += 1) {
523 if (line_i == key.len) break;
524 if (line[line_i] != key[line_i]) break;
525 }
526 if ((line_i != key.len) or (line[line_i] != '=')) continue;
527
528 return mem.sliceTo(line + line_i + 1, 0);
529 }
530 return null;
531}
532
533/// Windows-only. Get an environment variable with a null-terminated, WTF-16
534/// encoded name.
535///
536/// This function performs a Unicode-aware case-insensitive lookup using
537/// RtlEqualUnicodeString.
538///
539/// See also:
540/// * `createMap`
541/// * `containsConstant`
542/// * `contains`
543pub fn getWindows(environ: Environ, key: [*:0]const u16) ?[:0]const u16 {
544 comptime assert(native_os == .windows);
545 comptime assert(@TypeOf(environ.block) == void);
546
547 // '=' anywhere but the start makes this an invalid environment variable name.
548 const key_slice = mem.sliceTo(key, 0);
549 if (key_slice.len > 0 and mem.findScalar(u16, key_slice[1..], '=') != null) return null;
550
551 const ptr = std.os.windows.peb().ProcessParameters.Environment;
552
553 var i: usize = 0;
554 while (ptr[i] != 0) {
555 const key_value = mem.sliceTo(ptr[i..], 0);
556
557 // There are some special environment variables that start with =,
558 // so we need a special case to not treat = as a key/value separator
559 // if it's the first character.
560 // https://devblogs.microsoft.com/oldnewthing/20100506-00/?p=14133
561 const equal_search_start: usize = if (key_value[0] == '=') 1 else 0;
562 const equal_index = mem.findScalarPos(u16, key_value, equal_search_start, '=') orelse {
563 // This is enforced by CreateProcess.
564 // If violated, CreateProcess will fail with INVALID_PARAMETER.
565 unreachable; // must contain a =
566 };
567
568 const this_key = key_value[0..equal_index];
569 if (std.os.windows.eqlIgnoreCaseWtf16(key_slice, this_key)) {
570 return key_value[equal_index + 1 ..];
571 }
572
573 // skip past the NUL terminator
574 i += key_value.len + 1;
575 }
576 return null;
577}
578
579pub const GetAllocError = error{
580 OutOfMemory,
581 EnvironmentVariableMissing,
582 /// On Windows, environment variable keys provided by the user must be
583 /// valid [WTF-8](https://wtf-8.codeberg.page/). This error is unreachable
584 /// if the key is statically known to be valid.
585 InvalidWtf8,
586};
587
588/// Caller owns returned memory.
589///
590/// On Windows:
591/// * If `key` is not valid [WTF-8](https://wtf-8.codeberg.page/), then
592/// `error.InvalidWtf8` is returned.
593/// * The returned value is encoded as [WTF-8](https://wtf-8.codeberg.page/).
594///
595/// On other platforms, the value is an opaque sequence of bytes with no
596/// particular encoding.
597///
598/// See also:
599/// * `createMap`
600pub fn getAlloc(environ: Environ, gpa: Allocator, key: []const u8) GetAllocError![]u8 {
601 var map = createMap(environ, gpa) catch return error.OutOfMemory;
602 defer map.deinit();
603 const val = map.get(key) orelse return error.EnvironmentVariableMissing;
604 return gpa.dupe(u8, val);
605}
606
607pub const CreateBlockPosixOptions = struct {
608 /// `null` means to leave the `ZIG_PROGRESS` environment variable unmodified.
609 /// If non-null, negative means to remove the environment variable, and >= 0
610 /// means to provide it with the given integer.
611 zig_progress_fd: ?i32 = null,
612};
613
614/// Creates a null-delimited environment variable block in the format expected
615/// by POSIX, from a different one.
616pub fn createBlockPosix(
617 existing: Environ,
618 arena: Allocator,
619 options: CreateBlockPosixOptions,
620) Allocator.Error![:null]?[*:0]u8 {
621 const contains_zig_progress = for (existing.block) |opt_line| {
622 if (mem.eql(u8, mem.sliceTo(opt_line.?, '='), "ZIG_PROGRESS")) break true;
623 } else false;
624
625 const ZigProgressAction = enum { nothing, edit, delete, add };
626 const zig_progress_action: ZigProgressAction = a: {
627 const fd = options.zig_progress_fd orelse break :a .nothing;
628 if (fd >= 0) {
629 break :a if (contains_zig_progress) .edit else .add;
630 } else {
631 if (contains_zig_progress) break :a .delete;
632 }
633 break :a .nothing;
634 };
635
636 const envp_count: usize = c: {
637 var count: usize = existing.block.len;
638 switch (zig_progress_action) {
639 .add => count += 1,
640 .delete => count -= 1,
641 .nothing, .edit => {},
642 }
643 break :c count;
644 };
645
646 const envp_buf = try arena.allocSentinel(?[*:0]u8, envp_count, null);
647 var i: usize = 0;
648 var existing_index: usize = 0;
649
650 if (zig_progress_action == .add) {
651 envp_buf[i] = try std.fmt.allocPrintSentinel(arena, "ZIG_PROGRESS={d}", .{options.zig_progress_fd.?}, 0);
652 i += 1;
653 }
654
655 while (existing.block[existing_index]) |line| : (existing_index += 1) {
656 if (mem.eql(u8, mem.sliceTo(line, '='), "ZIG_PROGRESS")) switch (zig_progress_action) {
657 .add => unreachable,
658 .delete => continue,
659 .edit => {
660 envp_buf[i] = try std.fmt.allocPrintSentinel(arena, "ZIG_PROGRESS={d}", .{options.zig_progress_fd.?}, 0);
661 i += 1;
662 continue;
663 },
664 .nothing => {},
665 };
666 envp_buf[i] = try arena.dupeZ(u8, mem.span(line));
667 i += 1;
668 }
669
670 assert(i == envp_count);
671 return envp_buf;
672}
673
674test "Map.createBlock" {
675 const allocator = testing.allocator;
676 var envmap = Map.init(allocator);
677 defer envmap.deinit();
678
679 try envmap.put("HOME", "/home/ifreund");
680 try envmap.put("WAYLAND_DISPLAY", "wayland-1");
681 try envmap.put("DISPLAY", ":1");
682 try envmap.put("DEBUGINFOD_URLS", " ");
683 try envmap.put("XCURSOR_SIZE", "24");
684
685 var arena = std.heap.ArenaAllocator.init(allocator);
686 defer arena.deinit();
687 const environ = try envmap.createBlockPosix(arena.allocator(), .{});
688
689 try testing.expectEqual(@as(usize, 5), environ.len);
690
691 inline for (.{
692 "HOME=/home/ifreund",
693 "WAYLAND_DISPLAY=wayland-1",
694 "DISPLAY=:1",
695 "DEBUGINFOD_URLS= ",
696 "XCURSOR_SIZE=24",
697 }) |target| {
698 for (environ) |variable| {
699 if (mem.eql(u8, mem.span(variable orelse continue), target)) break;
700 } else {
701 try testing.expect(false); // Environment variable not found
702 }
703 }
704}
705
706test Map {
707 var env = Map.init(testing.allocator);
708 defer env.deinit();
709
710 try env.put("SOMETHING_NEW", "hello");
711 try testing.expectEqualStrings("hello", env.get("SOMETHING_NEW").?);
712 try testing.expectEqual(@as(Map.Size, 1), env.count());
713
714 // overwrite
715 try env.put("SOMETHING_NEW", "something");
716 try testing.expectEqualStrings("something", env.get("SOMETHING_NEW").?);
717 try testing.expectEqual(@as(Map.Size, 1), env.count());
718
719 // a new longer name to test the Windows-specific conversion buffer
720 try env.put("SOMETHING_NEW_AND_LONGER", "1");
721 try testing.expectEqualStrings("1", env.get("SOMETHING_NEW_AND_LONGER").?);
722 try testing.expectEqual(@as(Map.Size, 2), env.count());
723
724 // case insensitivity on Windows only
725 if (native_os == .windows) {
726 try testing.expectEqualStrings("1", env.get("something_New_aNd_LONGER").?);
727 } else {
728 try testing.expect(null == env.get("something_New_aNd_LONGER"));
729 }
730
731 var it = env.iterator();
732 var count: Map.Size = 0;
733 while (it.next()) |entry| {
734 const is_an_expected_name = mem.eql(u8, "SOMETHING_NEW", entry.key_ptr.*) or mem.eql(u8, "SOMETHING_NEW_AND_LONGER", entry.key_ptr.*);
735 try testing.expect(is_an_expected_name);
736 count += 1;
737 }
738 try testing.expectEqual(@as(Map.Size, 2), count);
739
740 try testing.expect(env.swapRemove("SOMETHING_NEW"));
741 try testing.expect(!env.swapRemove("SOMETHING_NEW"));
742 try testing.expect(env.get("SOMETHING_NEW") == null);
743
744 try testing.expectEqual(@as(Map.Size, 1), env.count());
745
746 if (native_os == .windows) {
747 // test Unicode case-insensitivity on Windows
748 try env.put("КИРиллИЦА", "something else");
749 try testing.expectEqualStrings("something else", env.get("кириллица").?);
750
751 // and WTF-8 that's not valid UTF-8
752 const wtf8_with_surrogate_pair = try unicode.wtf16LeToWtf8Alloc(testing.allocator, &[_]u16{
753 mem.nativeToLittle(u16, 0xD83D), // unpaired high surrogate
754 });
755 defer testing.allocator.free(wtf8_with_surrogate_pair);
756
757 try env.put(wtf8_with_surrogate_pair, wtf8_with_surrogate_pair);
758 try testing.expectEqualSlices(u8, wtf8_with_surrogate_pair, env.get(wtf8_with_surrogate_pair).?);
759 }
760}
761
762test "convert from Environ to Map and back again" {
763 if (native_os == .windows) return;
764 if (native_os == .wasi and !builtin.link_libc) return;
765
766 const gpa = testing.allocator;
767
768 var map: Map = .init(gpa);
769 defer map.deinit();
770 try map.put("FOO", "BAR");
771 try map.put("A", "");
772 try map.put("", "B");
773
774 var arena_allocator = std.heap.ArenaAllocator.init(gpa);
775 defer arena_allocator.deinit();
776 const arena = arena_allocator.allocator();
777
778 const environ: Environ = .{ .block = try map.createBlockPosix(arena, .{}) };
779
780 try testing.expectEqual(true, environ.contains(gpa, "FOO"));
781 try testing.expectEqual(false, environ.contains(gpa, "BAR"));
782 try testing.expectEqual(true, environ.contains(gpa, "A"));
783 try testing.expectEqual(true, environ.containsConstant("A"));
784 try testing.expectEqual(false, environ.containsUnempty(gpa, "A"));
785 try testing.expectEqual(false, environ.containsUnemptyConstant("A"));
786 try testing.expectEqual(true, environ.contains(gpa, ""));
787 try testing.expectEqual(false, environ.contains(gpa, "B"));
788
789 try testing.expectError(error.EnvironmentVariableMissing, environ.getAlloc(gpa, "BOGUS"));
790 {
791 const value = try environ.getAlloc(gpa, "FOO");
792 defer gpa.free(value);
793 try testing.expectEqualStrings("BAR", value);
794 }
795
796 var map2 = try environ.createMap(gpa);
797 defer map2.deinit();
798
799 try testing.expectEqualDeep(map.keys(), map2.keys());
800 try testing.expectEqualDeep(map.values(), map2.values());
801}
802
803test createMapWide {
804 if (builtin.cpu.arch.endian() == .big) return error.SkipZigTest; // TODO
805
806 const gpa = testing.allocator;
807
808 var map: Map = .init(gpa);
809 defer map.deinit();
810 try map.put("FOO", "BAR");
811 try map.put("A", "");
812 try map.put("", "B");
813
814 const environ: [:0]u16 = try map.createBlockWindows(gpa);
815 defer gpa.free(environ);
816
817 var map2 = try createMapWide(environ, gpa);
818 defer map2.deinit();
819
820 try testing.expectEqualDeep(&[_][]const u8{ "FOO", "A", "=B" }, map2.keys());
821 try testing.expectEqualDeep(&[_][]const u8{ "BAR", "", "" }, map2.values());
822}
lib/std/start.zig+165-168
...@@ -1,128 +1,76 @@...@@ -1,128 +1,76 @@
1// This file is included in the compilation unit when exporting an executable.1// This file is included in the compilation unit when exporting an executable.
22
3const root = @import("root");
4const std = @import("std.zig");
5const builtin = @import("builtin");3const builtin = @import("builtin");
4const native_arch = builtin.cpu.arch;
5const native_os = builtin.os.tag;
6const is_wasm = native_arch.isWasm();
7
8const std = @import("std.zig");
6const assert = std.debug.assert;9const assert = std.debug.assert;
7const uefi = std.os.uefi;10const uefi = std.os.uefi;
8const elf = std.elf;11const elf = std.elf;
9const native_arch = builtin.cpu.arch;
10const native_os = builtin.os.tag;
1112
12const start_sym_name = if (native_arch.isMIPS()) "__start" else "_start";13const root = @import("root");
1314
14// The self-hosted compiler is not fully capable of handling all of this start.zig file.15const start_sym_name = if (native_arch.isMIPS()) "__start" else "_start";
15// Until then, we have simplified logic here for self-hosted. TODO remove this once
16// self-hosted is capable enough to handle all of the real start.zig logic.
17pub const simplified_logic = switch (builtin.zig_backend) {
18 .stage2_aarch64,
19 .stage2_arm,
20 .stage2_powerpc,
21 .stage2_sparc64,
22 .stage2_spirv,
23 .stage2_x86,
24 => true,
25 else => false,
26};
2716
28comptime {17comptime {
29 // No matter what, we import the root file, so that any export, test, comptime18 // No matter what, we import the root file, so that any export, test, comptime
30 // decls there get run.19 // decls there get run.
31 _ = root;20 _ = root;
3221
33 if (simplified_logic) {22 if (builtin.output_mode == .Lib and builtin.link_mode == .dynamic) {
34 if (builtin.output_mode == .Exe) {23 if (native_os == .windows and !@hasDecl(root, "_DllMainCRTStartup")) {
35 if ((builtin.link_libc or builtin.object_format == .c) and @hasDecl(root, "main")) {24 @export(&_DllMainCRTStartup, .{ .name = "_DllMainCRTStartup" });
36 if (!@typeInfo(@TypeOf(root.main)).@"fn".calling_convention.eql(.c)) {
37 @export(&main2, .{ .name = "main" });
38 }
39 } else if (builtin.os.tag == .windows) {
40 if (!@hasDecl(root, "wWinMainCRTStartup") and !@hasDecl(root, "mainCRTStartup")) {
41 @export(&wWinMainCRTStartup2, .{ .name = "wWinMainCRTStartup" });
42 }
43 } else if (builtin.os.tag == .opencl or builtin.os.tag == .vulkan) {
44 if (@hasDecl(root, "main"))
45 @export(&spirvMain2, .{ .name = "main" });
46 } else {
47 if (!@hasDecl(root, "_start")) {
48 @export(&_start2, .{ .name = "_start" });
49 }
50 }
51 }25 }
52 } else {26 } else if (builtin.output_mode == .Exe or @hasDecl(root, "main")) {
53 if (builtin.output_mode == .Lib and builtin.link_mode == .dynamic) {27 if (builtin.link_libc and @hasDecl(root, "main")) {
54 if (native_os == .windows and !@hasDecl(root, "_DllMainCRTStartup")) {28 if (is_wasm) {
55 @export(&_DllMainCRTStartup, .{ .name = "_DllMainCRTStartup" });29 @export(&mainWithoutEnv, .{ .name = "__main_argc_argv" });
30 } else if (!@typeInfo(@TypeOf(root.main)).@"fn".calling_convention.eql(.c)) {
31 @export(&main, .{ .name = "main" });
56 }32 }
57 } else if (builtin.output_mode == .Exe or @hasDecl(root, "main")) {33 } else if (native_os == .windows and builtin.link_libc and @hasDecl(root, "wWinMain")) {
58 if (builtin.link_libc and @hasDecl(root, "main")) {34 if (!@typeInfo(@TypeOf(root.wWinMain)).@"fn".calling_convention.eql(.c)) {
59 if (native_arch.isWasm()) {35 @export(&wWinMain, .{ .name = "wWinMain" });
60 @export(&mainWithoutEnv, .{ .name = "__main_argc_argv" });36 }
61 } else if (!@typeInfo(@TypeOf(root.main)).@"fn".calling_convention.eql(.c)) {37 } else if (native_os == .windows) {
62 @export(&main, .{ .name = "main" });38 if (!@hasDecl(root, "WinMain") and !@hasDecl(root, "WinMainCRTStartup") and
63 }39 !@hasDecl(root, "wWinMain") and !@hasDecl(root, "wWinMainCRTStartup"))
64 } else if (native_os == .windows and builtin.link_libc and @hasDecl(root, "wWinMain")) {40 {
65 if (!@typeInfo(@TypeOf(root.wWinMain)).@"fn".calling_convention.eql(.c)) {41 @export(&WinStartup, .{ .name = "wWinMainCRTStartup" });
66 @export(&wWinMain, .{ .name = "wWinMain" });42 } else if (@hasDecl(root, "WinMain") and !@hasDecl(root, "WinMainCRTStartup") and
67 }43 !@hasDecl(root, "wWinMain") and !@hasDecl(root, "wWinMainCRTStartup"))
68 } else if (native_os == .windows) {44 {
69 if (!@hasDecl(root, "WinMain") and !@hasDecl(root, "WinMainCRTStartup") and45 @compileError("WinMain not supported; declare wWinMain or main instead");
70 !@hasDecl(root, "wWinMain") and !@hasDecl(root, "wWinMainCRTStartup"))46 } else if (@hasDecl(root, "wWinMain") and !@hasDecl(root, "wWinMainCRTStartup") and
71 {47 !@hasDecl(root, "WinMain") and !@hasDecl(root, "WinMainCRTStartup"))
72 @export(&WinStartup, .{ .name = "wWinMainCRTStartup" });48 {
73 } else if (@hasDecl(root, "WinMain") and !@hasDecl(root, "WinMainCRTStartup") and49 @export(&wWinMainCRTStartup, .{ .name = "wWinMainCRTStartup" });
74 !@hasDecl(root, "wWinMain") and !@hasDecl(root, "wWinMainCRTStartup"))50 }
75 {51 } else if (native_os == .uefi) {
76 @compileError("WinMain not supported; declare wWinMain or main instead");52 if (!@hasDecl(root, "EfiMain")) @export(&EfiMain, .{ .name = "EfiMain" });
77 } else if (@hasDecl(root, "wWinMain") and !@hasDecl(root, "wWinMainCRTStartup") and53 } else if (native_os == .wasi) {
78 !@hasDecl(root, "WinMain") and !@hasDecl(root, "WinMainCRTStartup"))54 const wasm_start_sym = switch (builtin.wasi_exec_model) {
79 {55 .reactor => "_initialize",
80 @export(&wWinMainCRTStartup, .{ .name = "wWinMainCRTStartup" });56 .command => "_start",
81 }57 };
82 } else if (native_os == .uefi) {58 if (!@hasDecl(root, wasm_start_sym) and @hasDecl(root, "main")) {
83 if (!@hasDecl(root, "EfiMain")) @export(&EfiMain, .{ .name = "EfiMain" });
84 } else if (native_os == .wasi) {
85 const wasm_start_sym = switch (builtin.wasi_exec_model) {
86 .reactor => "_initialize",
87 .command => "_start",
88 };
89 if (!@hasDecl(root, wasm_start_sym) and @hasDecl(root, "main")) {
90 // Only call main when defined. For WebAssembly it's allowed to pass `-fno-entry` in which
91 // case it's not required to provide an entrypoint such as main.
92 @export(&wasi_start, .{ .name = wasm_start_sym });
93 }
94 } else if (native_arch.isWasm() and native_os == .freestanding) {
95 // Only call main when defined. For WebAssembly it's allowed to pass `-fno-entry` in which59 // Only call main when defined. For WebAssembly it's allowed to pass `-fno-entry` in which
96 // case it's not required to provide an entrypoint such as main.60 // case it's not required to provide an entrypoint such as main.
97 if (!@hasDecl(root, start_sym_name) and @hasDecl(root, "main")) @export(&wasm_freestanding_start, .{ .name = start_sym_name });61 @export(&startWasi, .{ .name = wasm_start_sym });
98 } else switch (native_os) {
99 .other, .freestanding, .@"3ds", .vita => {},
100 else => if (!@hasDecl(root, start_sym_name)) @export(&_start, .{ .name = start_sym_name }),
101 }62 }
63 } else if (is_wasm and native_os == .freestanding) {
64 // Only call main when defined. For WebAssembly it's allowed to pass `-fno-entry` in which
65 // case it's not required to provide an entrypoint such as main.
66 if (!@hasDecl(root, start_sym_name) and @hasDecl(root, "main")) @export(&wasm_freestanding_start, .{ .name = start_sym_name });
67 } else switch (native_os) {
68 .other, .freestanding, .@"3ds", .vita => {},
69 else => if (!@hasDecl(root, start_sym_name)) @export(&_start, .{ .name = start_sym_name }),
102 }70 }
103 }71 }
104}72}
10573
106// Simplified start code for stage2 until it supports more language features ///
107
108fn main2() callconv(.c) c_int {
109 return callMain();
110}
111
112fn _start2() callconv(.withStackAlign(.c, 1)) noreturn {
113 std.process.exit(callMain());
114}
115
116fn spirvMain2() callconv(.kernel) void {
117 root.main();
118}
119
120fn wWinMainCRTStartup2() callconv(.c) noreturn {
121 std.process.exit(callMain());
122}
123
124////////////////////////////////////////////////////////////////////////////////
125
126fn _DllMainCRTStartup(74fn _DllMainCRTStartup(
127 hinstDLL: std.os.windows.HINSTANCE,75 hinstDLL: std.os.windows.HINSTANCE,
128 fdwReason: std.os.windows.DWORD,76 fdwReason: std.os.windows.DWORD,
...@@ -142,15 +90,15 @@ fn _DllMainCRTStartup(...@@ -142,15 +90,15 @@ fn _DllMainCRTStartup(
142fn wasm_freestanding_start() callconv(.c) void {90fn wasm_freestanding_start() callconv(.c) void {
143 // This is marked inline because for some reason LLVM in91 // This is marked inline because for some reason LLVM in
144 // release mode fails to inline it, and we want fewer call frames in stack traces.92 // release mode fails to inline it, and we want fewer call frames in stack traces.
145 _ = @call(.always_inline, callMain, .{});93 _ = @call(.always_inline, callMain, .{ {}, {} });
146}94}
14795
148fn wasi_start() callconv(.c) void {96fn startWasi() callconv(.c) void {
149 // The function call is marked inline because for some reason LLVM in97 // The function call is marked inline because for some reason LLVM in
150 // release mode fails to inline it, and we want fewer call frames in stack traces.98 // release mode fails to inline it, and we want fewer call frames in stack traces.
151 switch (builtin.wasi_exec_model) {99 switch (builtin.wasi_exec_model) {
152 .reactor => _ = @call(.always_inline, callMain, .{}),100 .reactor => _ = @call(.always_inline, callMain, .{ {}, {} }),
153 .command => std.os.wasi.proc_exit(@call(.always_inline, callMain, .{})),101 .command => std.os.wasi.proc_exit(@call(.always_inline, callMain, .{ {}, {} })),
154 }102 }
155}103}
156104
...@@ -524,7 +472,10 @@ fn WinStartup() callconv(.withStackAlign(.c, 1)) noreturn {...@@ -524,7 +472,10 @@ fn WinStartup() callconv(.withStackAlign(.c, 1)) noreturn {
524472
525 std.debug.maybeEnableSegfaultHandler();473 std.debug.maybeEnableSegfaultHandler();
526474
527 std.os.windows.ntdll.RtlExitUserProcess(callMain());475 const cmd_line = std.os.windows.peb().ProcessParameters.CommandLine;
476 const cmd_line_w = cmd_line.Buffer.?[0..@divExact(cmd_line.Length, 2)];
477
478 std.os.windows.ntdll.RtlExitUserProcess(callMain(cmd_line_w, {}));
528}479}
529480
530fn wWinMainCRTStartup() callconv(.withStackAlign(.c, 1)) noreturn {481fn wWinMainCRTStartup() callconv(.withStackAlign(.c, 1)) noreturn {
...@@ -556,7 +507,7 @@ fn posixCallMainAndExit(argc_argv_ptr: [*]usize) callconv(.c) noreturn {...@@ -556,7 +507,7 @@ fn posixCallMainAndExit(argc_argv_ptr: [*]usize) callconv(.c) noreturn {
556 const envp_optional: [*:null]?[*:0]u8 = @ptrCast(@alignCast(argv + argc + 1));507 const envp_optional: [*:null]?[*:0]u8 = @ptrCast(@alignCast(argv + argc + 1));
557 var envp_count: usize = 0;508 var envp_count: usize = 0;
558 while (envp_optional[envp_count]) |_| : (envp_count += 1) {}509 while (envp_optional[envp_count]) |_| : (envp_count += 1) {}
559 const envp = @as([*][*:0]u8, @ptrCast(envp_optional))[0..envp_count];510 const envp = envp_optional[0..envp_count :null];
560511
561 // Find the beginning of the auxiliary vector512 // Find the beginning of the auxiliary vector
562 const auxv: [*]elf.Auxv = @ptrCast(@alignCast(envp.ptr + envp_count + 1));513 const auxv: [*]elf.Auxv = @ptrCast(@alignCast(envp.ptr + envp_count + 1));
...@@ -631,6 +582,7 @@ fn posixCallMainAndExit(argc_argv_ptr: [*]usize) callconv(.c) noreturn {...@@ -631,6 +582,7 @@ fn posixCallMainAndExit(argc_argv_ptr: [*]usize) callconv(.c) noreturn {
631}582}
632583
633fn expandStackSize(phdrs: []elf.Phdr) void {584fn expandStackSize(phdrs: []elf.Phdr) void {
585 @disableInstrumentation();
634 for (phdrs) |*phdr| {586 for (phdrs) |*phdr| {
635 switch (phdr.p_type) {587 switch (phdr.p_type) {
636 elf.PT_GNU_STACK => {588 elf.PT_GNU_STACK => {
...@@ -665,92 +617,137 @@ fn expandStackSize(phdrs: []elf.Phdr) void {...@@ -665,92 +617,137 @@ fn expandStackSize(phdrs: []elf.Phdr) void {
665 }617 }
666}618}
667619
668inline fn callMainWithArgs(argc: usize, argv: [*][*:0]u8, envp: [][*:0]u8) u8 {620inline fn callMainWithArgs(argc: usize, argv: [*][*:0]u8, envp: [:null]?[*:0]u8) u8 {
669 std.os.argv = argv[0..argc];
670 std.os.environ = envp;
671
672 if (std.Options.debug_threaded_io) |t| {621 if (std.Options.debug_threaded_io) |t| {
673 if (@sizeOf(std.Io.Threaded.Argv0) != 0) t.argv0.value = argv[0];622 if (@sizeOf(std.Io.Threaded.Argv0) != 0) t.argv0.value = argv[0];
674 t.environ = .{ .block = envp };623 t.environ = .{ .process_environ = .{ .block = envp } };
675 }624 }
676
677 std.debug.maybeEnableSegfaultHandler();625 std.debug.maybeEnableSegfaultHandler();
678626 return callMain(argv[0..argc], envp);
679 return callMain();
680}627}
681628
682fn main(c_argc: c_int, c_argv: [*][*:0]c_char, c_envp: [*:null]?[*:0]c_char) callconv(.c) c_int {629fn main(c_argc: c_int, c_argv: [*][*:0]c_char, c_envp: [*:null]?[*:0]c_char) callconv(.c) c_int {
683 var env_count: usize = 0;630 var env_count: usize = 0;
684 while (c_envp[env_count] != null) : (env_count += 1) {}631 while (c_envp[env_count] != null) : (env_count += 1) {}
685 const envp = @as([*][*:0]u8, @ptrCast(c_envp))[0..env_count];632 const envp = c_envp[0..env_count :null];
686633
687 if (builtin.os.tag == .linux) {634 switch (builtin.os.tag) {
688 const at_phdr = std.c.getauxval(elf.AT_PHDR);635 .linux => {
689 const at_phnum = std.c.getauxval(elf.AT_PHNUM);636 const at_phdr = std.c.getauxval(elf.AT_PHDR);
690 const phdrs = (@as([*]elf.Phdr, @ptrFromInt(at_phdr)))[0..at_phnum];637 const at_phnum = std.c.getauxval(elf.AT_PHNUM);
691 expandStackSize(phdrs);638 const phdrs = (@as([*]elf.Phdr, @ptrFromInt(at_phdr)))[0..at_phnum];
639 expandStackSize(phdrs);
640 },
641 .windows => {
642 // On Windows, we ignore libc environment and argv and get those
643 // values in their intended encoding from the PEB instead.
644 std.debug.maybeEnableSegfaultHandler();
645 const cmd_line = std.os.windows.peb().ProcessParameters.CommandLine;
646 const cmd_line_w = cmd_line.Buffer.?[0..@divExact(cmd_line.Length, 2)];
647 return callMain(cmd_line_w, {});
648 },
649 else => {},
692 }650 }
693651
694 return callMainWithArgs(@as(usize, @intCast(c_argc)), @as([*][*:0]u8, @ptrCast(c_argv)), envp);652 return callMainWithArgs(@as(usize, @intCast(c_argc)), @as([*][*:0]u8, @ptrCast(c_argv)), @ptrCast(envp));
695}653}
696654
697fn mainWithoutEnv(c_argc: c_int, c_argv: [*][*:0]c_char) callconv(.c) c_int {655fn mainWithoutEnv(c_argc: c_int, c_argv: [*][*:0]c_char) callconv(.c) c_int {
698 std.os.argv = @as([*][*:0]u8, @ptrCast(c_argv))[0..@intCast(c_argc)];656 const argv = @as([*][*:0]u8, @ptrCast(c_argv))[0..@intCast(c_argc)];
699
700 if (@sizeOf(std.Io.Threaded.Argv0) != 0) {657 if (@sizeOf(std.Io.Threaded.Argv0) != 0) {
701 if (std.Options.debug_threaded_io) |t| t.argv0.value = std.os.argv[0];658 if (std.Options.debug_threaded_io) |t| t.argv0.value = argv[0];
702 }659 }
703660 return callMain(argv, &.{});
704 return callMain();
705}661}
706662
707// General error message for a malformed return type663/// General error message for a malformed return type
708const bad_main_ret = "expected return type of main to be 'void', '!void', 'noreturn', 'u8', or '!u8'";664const bad_main_ret = "expected return type of main to be 'void', '!void', 'noreturn', 'u8', or '!u8'";
709665
710pub inline fn callMain() u8 {666const use_debug_allocator = !is_wasm and switch (builtin.mode) {
711 const ReturnType = @typeInfo(@TypeOf(root.main)).@"fn".return_type.?;667 .Debug => true,
668 .ReleaseSafe => !builtin.link_libc, // Not ideal, but the best we have for now.
669 .ReleaseFast, .ReleaseSmall => !builtin.link_libc and builtin.single_threaded, // Also not ideal.
670};
671var debug_allocator: std.heap.DebugAllocator(.{}) = .init;
672
673inline fn callMain(args: std.process.Args.Vector, environ: std.process.Environ.Block) u8 {
674 const fn_info = @typeInfo(@TypeOf(root.main)).@"fn";
675 if (fn_info.params.len == 0) return wrapMain(root.main());
676 if (fn_info.params[0].type.? == std.process.Init.Minimal) return wrapMain(root.main(.{
677 .args = .{ .vector = args },
678 .environ = .{ .block = environ },
679 }));
680
681 const gpa = if (use_debug_allocator)
682 debug_allocator.allocator()
683 else if (builtin.link_libc)
684 std.heap.c_allocator
685 else if (is_wasm)
686 std.heap.wasm_allocator
687 else if (!builtin.single_threaded)
688 std.heap.smp_allocator
689 else
690 comptime unreachable;
691
692 defer if (use_debug_allocator) {
693 _ = debug_allocator.deinit(); // Leaks do not affect return code.
694 };
712695
713 switch (ReturnType) {696 const arena_backing_allocator = if (is_wasm) gpa else std.heap.page_allocator;
714 void => {
715 root.main();
716 return 0;
717 },
718 noreturn, u8 => {
719 return root.main();
720 },
721 else => {
722 if (@typeInfo(ReturnType) != .error_union) @compileError(bad_main_ret);
723
724 const result = root.main() catch |err| {
725 switch (builtin.zig_backend) {
726 .stage2_powerpc,
727 .stage2_riscv64,
728 => {
729 _ = std.posix.write(std.posix.STDERR_FILENO, "error: failed with error\n") catch {};
730 return 1;
731 },
732 else => {},
733 }
734 std.log.err("{s}", .{@errorName(err)});
735 switch (native_os) {
736 .freestanding, .other => {},
737 else => if (@errorReturnTrace()) |trace| {
738 std.debug.dumpStackTrace(trace);
739 },
740 }
741 return 1;
742 };
743697
744 return switch (@TypeOf(result)) {698 var arena_allocator = std.heap.ArenaAllocator.init(arena_backing_allocator);
745 void => 0,699 defer arena_allocator.deinit();
746 u8 => result,700
747 else => @compileError(bad_main_ret),701 var threaded: std.Io.Threaded = .init(gpa, .{
748 };702 .argv0 = .init(.{ .vector = args }),
703 .environ = .{ .block = environ },
704 });
705 defer threaded.deinit();
706
707 var environ_map = std.process.Environ.createMap(.{ .block = environ }, gpa) catch |err|
708 std.process.fatal("failed to parse environment variables: {t}", .{err});
709 defer environ_map.deinit();
710
711 return wrapMain(root.main(.{
712 .minimal = .{
713 .args = .{ .vector = args },
714 .environ = .{ .block = environ },
749 },715 },
716 .arena = &arena_allocator,
717 .gpa = gpa,
718 .io = threaded.io(),
719 .environ_map = &environ_map,
720 }));
721}
722
723inline fn wrapMain(result: anytype) u8 {
724 const ReturnType = @TypeOf(result);
725 switch (ReturnType) {
726 void => return 0,
727 noreturn => unreachable,
728 u8 => return result,
729 else => {},
750 }730 }
731 if (@typeInfo(ReturnType) != .error_union) @compileError(bad_main_ret);
732
733 const unwrapped_result = result catch |err| {
734 std.log.err("{t}", .{err});
735 switch (native_os) {
736 .freestanding, .other => {},
737 else => if (@errorReturnTrace()) |trace| std.debug.dumpStackTrace(trace),
738 }
739 return 1;
740 };
741
742 return switch (@TypeOf(unwrapped_result)) {
743 noreturn => unreachable,
744 void => 0,
745 u8 => unwrapped_result,
746 else => @compileError(bad_main_ret),
747 };
751}748}
752749
753pub fn call_wWinMain() std.os.windows.INT {750fn call_wWinMain() std.os.windows.INT {
754 const peb = std.os.windows.peb();751 const peb = std.os.windows.peb();
755 const MAIN_HINSTANCE = @typeInfo(@TypeOf(root.wWinMain)).@"fn".params[0].type.?;752 const MAIN_HINSTANCE = @typeInfo(@TypeOf(root.wWinMain)).@"fn".params[0].type.?;
756 const hInstance: MAIN_HINSTANCE = @ptrCast(peb.ImageBaseAddress);753 const hInstance: MAIN_HINSTANCE = @ptrCast(peb.ImageBaseAddress);
lib/std/std.zig+14-3
...@@ -114,9 +114,6 @@ pub const options: Options = if (@hasDecl(root, "std_options")) root.std_options...@@ -114,9 +114,6 @@ pub const options: Options = if (@hasDecl(root, "std_options")) root.std_options
114pub const Options = struct {114pub const Options = struct {
115 enable_segfault_handler: bool = debug.default_enable_segfault_handler,115 enable_segfault_handler: bool = debug.default_enable_segfault_handler,
116116
117 /// Function used to implement `std.Io.Dir.cwd` for WASI.
118 wasiCwd: fn () os.wasi.fd_t = os.defaultWasiCwd,
119
120 /// The current log level.117 /// The current log level.
121 log_level: log.Level = log.default_level,118 log_level: log.Level = log.default_level,
122119
...@@ -176,10 +173,21 @@ pub const Options = struct {...@@ -176,10 +173,21 @@ pub const Options = struct {
176 /// stack traces will just print an error to the relevant `Io.Writer` and return.173 /// stack traces will just print an error to the relevant `Io.Writer` and return.
177 allow_stack_tracing: bool = !@import("builtin").strip_debug_info,174 allow_stack_tracing: bool = !@import("builtin").strip_debug_info,
178175
176 /// TODO This is a separate decl instead of a field as a workaround around
177 /// compilation errors due to zig not being lazy enough.
178 pub const elf_debug_info_search_paths: ?fn (exe_path: []const u8) switch (@import("builtin").object_format) {
179 .elf => debug.ElfFile.DebugInfoSearchPaths,
180 else => void,
181 } = if (@hasDecl(root, "std_options_elf_debug_info_search_paths"))
182 root.std_options_elf_debug_info_search_paths
183 else
184 null;
185
179 pub const debug_threaded_io: ?*Io.Threaded = if (@hasDecl(root, "std_options_debug_threaded_io"))186 pub const debug_threaded_io: ?*Io.Threaded = if (@hasDecl(root, "std_options_debug_threaded_io"))
180 root.std_options_debug_threaded_io187 root.std_options_debug_threaded_io
181 else188 else
182 Io.Threaded.global_single_threaded;189 Io.Threaded.global_single_threaded;
190
183 /// The `Io` instance that `std.debug` uses for `std.debug.print`,191 /// The `Io` instance that `std.debug` uses for `std.debug.print`,
184 /// capturing stack traces, loading debug info, finding the executable's192 /// capturing stack traces, loading debug info, finding the executable's
185 /// own path, and environment variables that affect terminal mode193 /// own path, and environment variables that affect terminal mode
...@@ -193,6 +201,9 @@ pub const Options = struct {...@@ -193,6 +201,9 @@ pub const Options = struct {
193201
194 /// Overrides `std.Io.File.Permissions`.202 /// Overrides `std.Io.File.Permissions`.
195 pub const FilePermissions: ?type = if (@hasDecl(root, "std_options_FilePermissions")) root.std_options_FilePermissions else null;203 pub const FilePermissions: ?type = if (@hasDecl(root, "std_options_FilePermissions")) root.std_options_FilePermissions else null;
204
205 /// Overrides `std.Io.Dir.cwd`.
206 pub const cwd: ?fn () Io.Dir = if (@hasDecl(root, "std_options_cwd")) root.std_options_cwd else null;
196};207};
197208
198// This forces the start.zig file to be imported, and the comptime logic inside that209// This forces the start.zig file to be imported, and the comptime logic inside that
lib/std/zig.zig+23-12
...@@ -739,28 +739,39 @@ pub const EnvVar = enum {...@@ -739,28 +739,39 @@ pub const EnvVar = enum {
739 ZIG_VERBOSE_CC,739 ZIG_VERBOSE_CC,
740 ZIG_BTRFS_WORKAROUND,740 ZIG_BTRFS_WORKAROUND,
741 ZIG_DEBUG_CMD,741 ZIG_DEBUG_CMD,
742 ZIG_IS_DETECTING_LIBC_PATHS,
743 ZIG_IS_TRYING_TO_NOT_CALL_ITSELF,
744
745 // C toolchain integration
746 NIX_CFLAGS_COMPILE,
747 NIX_CFLAGS_LINK,
748 NIX_LDFLAGS,
749 C_INCLUDE_PATH,
750 CPLUS_INCLUDE_PATH,
751 LIBRARY_PATH,
742 CC,752 CC,
753
754 // Terminal integration
743 NO_COLOR,755 NO_COLOR,
744 CLICOLOR_FORCE,756 CLICOLOR_FORCE,
757
758 // Debug info integration
745 XDG_CACHE_HOME,759 XDG_CACHE_HOME,
746 LOCALAPPDATA,760 LOCALAPPDATA,
747 HOME,761 HOME,
748762
749 pub fn isSet(comptime ev: EnvVar) bool {763 // Windows SDK integration
750 return std.process.hasNonEmptyEnvVarConstant(@tagName(ev));764 PROGRAMDATA,
751 }
752765
753 pub fn get(ev: EnvVar, arena: std.mem.Allocator) !?[]u8 {766 // Homebrew integration
754 if (std.process.getEnvVarOwned(arena, @tagName(ev))) |value| {767 HOMEBREW_PREFIX,
755 return value;768
756 } else |err| switch (err) {769 pub fn isSet(ev: EnvVar, map: *const std.process.Environ.Map) bool {
757 error.EnvironmentVariableNotFound => return null,770 return map.contains(@tagName(ev));
758 else => |e| return e,
759 }
760 }771 }
761772
762 pub fn getPosix(comptime ev: EnvVar) ?[:0]const u8 {773 pub fn get(ev: EnvVar, map: *const std.process.Environ.Map) ?[]const u8 {
763 return std.posix.getenvZ(@tagName(ev));774 return map.get(@tagName(ev));
764 }775 }
765};776};
766777
lib/std/zig/LibCDirs.zig+11-3
...@@ -28,6 +28,7 @@ pub fn detect(...@@ -28,6 +28,7 @@ pub fn detect(
28 is_native_abi: bool,28 is_native_abi: bool,
29 link_libc: bool,29 link_libc: bool,
30 libc_installation: ?*const LibCInstallation,30 libc_installation: ?*const LibCInstallation,
31 environ_map: *const std.process.Environ.Map,
31) LibCInstallation.FindError!LibCDirs {32) LibCInstallation.FindError!LibCDirs {
32 if (!link_libc) {33 if (!link_libc) {
33 return .{34 return .{
...@@ -47,7 +48,10 @@ pub fn detect(...@@ -47,7 +48,10 @@ pub fn detect(
47 // using the system libc installation.48 // using the system libc installation.
48 if (is_native_abi and !target.isMinGW()) {49 if (is_native_abi and !target.isMinGW()) {
49 const libc = try arena.create(LibCInstallation);50 const libc = try arena.create(LibCInstallation);
50 libc.* = LibCInstallation.findNative(arena, io, .{ .target = target }) catch |err| switch (err) {51 libc.* = LibCInstallation.findNative(arena, io, .{
52 .target = target,
53 .environ_map = environ_map,
54 }) catch |err| switch (err) {
51 error.CCompilerExitCode,55 error.CCompilerExitCode,
52 error.CCompilerCrashed,56 error.CCompilerCrashed,
53 error.CCompilerCannotFindHeaders,57 error.CCompilerCannotFindHeaders,
...@@ -84,12 +88,16 @@ pub fn detect(...@@ -84,12 +88,16 @@ pub fn detect(
8488
85 if (use_system_abi) {89 if (use_system_abi) {
86 const libc = try arena.create(LibCInstallation);90 const libc = try arena.create(LibCInstallation);
87 libc.* = try LibCInstallation.findNative(arena, io, .{ .verbose = true, .target = target });91 libc.* = try LibCInstallation.findNative(arena, io, .{
92 .verbose = true,
93 .target = target,
94 .environ_map = environ_map,
95 });
88 return detectFromInstallation(arena, target, libc);96 return detectFromInstallation(arena, target, libc);
89 }97 }
9098
91 return .{99 return .{
92 .libc_include_dir_list = &[0][]u8{},100 .libc_include_dir_list = &.{},
93 .libc_installation = null,101 .libc_installation = null,
94 .libc_framework_dir_list = &.{},102 .libc_framework_dir_list = &.{},
95 .sysroot = null,103 .sysroot = null,
lib/std/zig/LibCInstallation.zig+41-39
...@@ -13,6 +13,7 @@ const fs = std.fs;...@@ -13,6 +13,7 @@ const fs = std.fs;
13const Allocator = std.mem.Allocator;13const Allocator = std.mem.Allocator;
14const Path = std.Build.Cache.Path;14const Path = std.Build.Cache.Path;
15const log = std.log.scoped(.libc_installation);15const log = std.log.scoped(.libc_installation);
16const Environ = std.process.Environ;
1617
17include_dir: ?[]const u8 = null,18include_dir: ?[]const u8 = null,
18sys_include_dir: ?[]const u8 = null,19sys_include_dir: ?[]const u8 = null,
...@@ -167,6 +168,7 @@ pub fn render(self: LibCInstallation, out: *std.Io.Writer) !void {...@@ -167,6 +168,7 @@ pub fn render(self: LibCInstallation, out: *std.Io.Writer) !void {
167168
168pub const FindNativeOptions = struct {169pub const FindNativeOptions = struct {
169 target: *const std.Target,170 target: *const std.Target,
171 environ_map: *const Environ.Map,
170172
171 /// If enabled, will print human-friendly errors to stderr.173 /// If enabled, will print human-friendly errors to stderr.
172 verbose: bool = false,174 verbose: bool = false,
...@@ -191,7 +193,7 @@ pub fn findNative(gpa: Allocator, io: Io, args: FindNativeOptions) FindError!Lib...@@ -191,7 +193,7 @@ pub fn findNative(gpa: Allocator, io: Io, args: FindNativeOptions) FindError!Lib
191 });193 });
192 return self;194 return self;
193 } else if (is_windows) {195 } else if (is_windows) {
194 const sdk = std.zig.WindowsSdk.find(gpa, io, args.target.cpu.arch) catch |err| switch (err) {196 const sdk = std.zig.WindowsSdk.find(gpa, io, args.target.cpu.arch, args.environ_map) catch |err| switch (err) {
195 error.NotFound => return error.WindowsSdkNotFound,197 error.NotFound => return error.WindowsSdkNotFound,
196 error.PathTooLong => return error.WindowsSdkNotFound,198 error.PathTooLong => return error.WindowsSdkNotFound,
197 error.OutOfMemory => return error.OutOfMemory,199 error.OutOfMemory => return error.OutOfMemory,
...@@ -206,16 +208,16 @@ pub fn findNative(gpa: Allocator, io: Io, args: FindNativeOptions) FindError!Lib...@@ -206,16 +208,16 @@ pub fn findNative(gpa: Allocator, io: Io, args: FindNativeOptions) FindError!Lib
206 } else if (is_haiku) {208 } else if (is_haiku) {
207 try self.findNativeIncludeDirPosix(gpa, io, args);209 try self.findNativeIncludeDirPosix(gpa, io, args);
208 try self.findNativeGccDirHaiku(gpa, io, args);210 try self.findNativeGccDirHaiku(gpa, io, args);
209 self.crt_dir = try gpa.dupeZ(u8, "/system/develop/lib");211 self.crt_dir = try gpa.dupe(u8, "/system/develop/lib");
210 } else if (builtin.target.os.tag == .illumos) {212 } else if (builtin.target.os.tag == .illumos) {
211 // There is only one libc, and its headers/libraries are always in the same spot.213 // There is only one libc, and its headers/libraries are always in the same spot.
212 self.include_dir = try gpa.dupeZ(u8, "/usr/include");214 self.include_dir = try gpa.dupe(u8, "/usr/include");
213 self.sys_include_dir = try gpa.dupeZ(u8, "/usr/include");215 self.sys_include_dir = try gpa.dupe(u8, "/usr/include");
214 self.crt_dir = try gpa.dupeZ(u8, "/usr/lib/64");216 self.crt_dir = try gpa.dupe(u8, "/usr/lib/64");
215 } else if (std.process.can_spawn) {217 } else if (std.process.can_spawn) {
216 try self.findNativeIncludeDirPosix(gpa, io, args);218 try self.findNativeIncludeDirPosix(gpa, io, args);
217 switch (builtin.target.os.tag) {219 switch (builtin.target.os.tag) {
218 .freebsd, .netbsd, .openbsd, .dragonfly => self.crt_dir = try gpa.dupeZ(u8, "/usr/lib"),220 .freebsd, .netbsd, .openbsd, .dragonfly => self.crt_dir = try gpa.dupe(u8, "/usr/lib"),
219 .linux => try self.findNativeCrtDirPosix(gpa, io, args),221 .linux => try self.findNativeCrtDirPosix(gpa, io, args),
220 else => {},222 else => {},
221 }223 }
...@@ -238,20 +240,17 @@ pub fn deinit(self: *LibCInstallation, allocator: Allocator) void {...@@ -238,20 +240,17 @@ pub fn deinit(self: *LibCInstallation, allocator: Allocator) void {
238240
239fn findNativeIncludeDirPosix(self: *LibCInstallation, gpa: Allocator, io: Io, args: FindNativeOptions) FindError!void {241fn findNativeIncludeDirPosix(self: *LibCInstallation, gpa: Allocator, io: Io, args: FindNativeOptions) FindError!void {
240 // Detect infinite loops.242 // Detect infinite loops.
241 var env_map = std.process.getEnvMap(gpa) catch |err| switch (err) {243 var environ_map = try args.environ_map.clone(gpa);
242 error.Unexpected => unreachable, // WASI-only244 defer environ_map.deinit();
243 else => |e| return e,245 const skip_cc_env_var = if (environ_map.get(inf_loop_env_key)) |phase| blk: {
244 };
245 defer env_map.deinit();
246 const skip_cc_env_var = if (env_map.get(inf_loop_env_key)) |phase| blk: {
247 if (std.mem.eql(u8, phase, "1")) {246 if (std.mem.eql(u8, phase, "1")) {
248 try env_map.put(inf_loop_env_key, "2");247 try environ_map.put(inf_loop_env_key, "2");
249 break :blk true;248 break :blk true;
250 } else {249 } else {
251 return error.ZigIsTheCCompiler;250 return error.ZigIsTheCCompiler;
252 }251 }
253 } else blk: {252 } else blk: {
254 try env_map.put(inf_loop_env_key, "1");253 try environ_map.put(inf_loop_env_key, "1");
255 break :blk false;254 break :blk false;
256 };255 };
257256
...@@ -260,7 +259,7 @@ fn findNativeIncludeDirPosix(self: *LibCInstallation, gpa: Allocator, io: Io, ar...@@ -260,7 +259,7 @@ fn findNativeIncludeDirPosix(self: *LibCInstallation, gpa: Allocator, io: Io, ar
260 var argv = std.array_list.Managed([]const u8).init(gpa);259 var argv = std.array_list.Managed([]const u8).init(gpa);
261 defer argv.deinit();260 defer argv.deinit();
262261
263 try appendCcExe(&argv, skip_cc_env_var);262 try appendCcExe(&argv, skip_cc_env_var, &environ_map);
264 try argv.appendSlice(&.{263 try argv.appendSlice(&.{
265 "-E",264 "-E",
266 "-Wp,-v",265 "-Wp,-v",
...@@ -268,10 +267,10 @@ fn findNativeIncludeDirPosix(self: *LibCInstallation, gpa: Allocator, io: Io, ar...@@ -268,10 +267,10 @@ fn findNativeIncludeDirPosix(self: *LibCInstallation, gpa: Allocator, io: Io, ar
268 dev_null,267 dev_null,
269 });268 });
270269
271 const run_res = std.process.Child.run(gpa, io, .{270 const run_res = std.process.run(gpa, io, .{
272 .argv = argv.items,
273 .max_output_bytes = 1024 * 1024,271 .max_output_bytes = 1024 * 1024,
274 .env_map = &env_map,272 .argv = argv.items,
273 .environ_map = &environ_map,
275 // Some C compilers, such as Clang, are known to rely on argv[0] to find the path274 // Some C compilers, such as Clang, are known to rely on argv[0] to find the path
276 // to their own executable, without even bothering to resolve PATH. This results in the message:275 // to their own executable, without even bothering to resolve PATH. This results in the message:
277 // error: unable to execute command: Executable "" doesn't exist!276 // error: unable to execute command: Executable "" doesn't exist!
...@@ -289,7 +288,7 @@ fn findNativeIncludeDirPosix(self: *LibCInstallation, gpa: Allocator, io: Io, ar...@@ -289,7 +288,7 @@ fn findNativeIncludeDirPosix(self: *LibCInstallation, gpa: Allocator, io: Io, ar
289 gpa.free(run_res.stderr);288 gpa.free(run_res.stderr);
290 }289 }
291 switch (run_res.term) {290 switch (run_res.term) {
292 .Exited => |code| if (code != 0) {291 .exited => |code| if (code != 0) {
293 printVerboseInvocation(argv.items, null, args.verbose, run_res.stderr);292 printVerboseInvocation(argv.items, null, args.verbose, run_res.stderr);
294 return error.CCompilerExitCode;293 return error.CCompilerExitCode;
295 },294 },
...@@ -336,7 +335,7 @@ fn findNativeIncludeDirPosix(self: *LibCInstallation, gpa: Allocator, io: Io, ar...@@ -336,7 +335,7 @@ fn findNativeIncludeDirPosix(self: *LibCInstallation, gpa: Allocator, io: Io, ar
336335
337 if (self.include_dir == null) {336 if (self.include_dir == null) {
338 if (search_dir.access(io, include_dir_example_file, .{})) |_| {337 if (search_dir.access(io, include_dir_example_file, .{})) |_| {
339 self.include_dir = try gpa.dupeZ(u8, search_path);338 self.include_dir = try gpa.dupe(u8, search_path);
340 } else |err| switch (err) {339 } else |err| switch (err) {
341 error.FileNotFound => {},340 error.FileNotFound => {},
342 else => return error.FileSystem,341 else => return error.FileSystem,
...@@ -345,7 +344,7 @@ fn findNativeIncludeDirPosix(self: *LibCInstallation, gpa: Allocator, io: Io, ar...@@ -345,7 +344,7 @@ fn findNativeIncludeDirPosix(self: *LibCInstallation, gpa: Allocator, io: Io, ar
345344
346 if (self.sys_include_dir == null) {345 if (self.sys_include_dir == null) {
347 if (search_dir.access(io, sys_include_dir_example_file, .{})) |_| {346 if (search_dir.access(io, sys_include_dir_example_file, .{})) |_| {
348 self.sys_include_dir = try gpa.dupeZ(u8, search_path);347 self.sys_include_dir = try gpa.dupe(u8, search_path);
349 } else |err| switch (err) {348 } else |err| switch (err) {
350 error.FileNotFound => {},349 error.FileNotFound => {},
351 else => return error.FileSystem,350 else => return error.FileSystem,
...@@ -447,6 +446,7 @@ fn findNativeCrtDirWindows(...@@ -447,6 +446,7 @@ fn findNativeCrtDirWindows(
447446
448fn findNativeCrtDirPosix(self: *LibCInstallation, gpa: Allocator, io: Io, args: FindNativeOptions) FindError!void {447fn findNativeCrtDirPosix(self: *LibCInstallation, gpa: Allocator, io: Io, args: FindNativeOptions) FindError!void {
449 self.crt_dir = try ccPrintFileName(gpa, io, .{448 self.crt_dir = try ccPrintFileName(gpa, io, .{
449 .environ_map = args.environ_map,
450 .search_basename = switch (args.target.os.tag) {450 .search_basename = switch (args.target.os.tag) {
451 .linux => if (args.target.abi.isAndroid()) "crtbegin_dynamic.o" else "crt1.o",451 .linux => if (args.target.abi.isAndroid()) "crtbegin_dynamic.o" else "crt1.o",
452 else => "crt1.o",452 else => "crt1.o",
...@@ -551,28 +551,26 @@ fn findNativeMsvcLibDir(...@@ -551,28 +551,26 @@ fn findNativeMsvcLibDir(
551}551}
552552
553pub const CCPrintFileNameOptions = struct {553pub const CCPrintFileNameOptions = struct {
554 environ_map: *const Environ.Map,
554 search_basename: []const u8,555 search_basename: []const u8,
555 want_dirname: enum { full_path, only_dir },556 want_dirname: enum { full_path, only_dir },
556 verbose: bool = false,557 verbose: bool = false,
557};558};
558559
559/// caller owns returned memory560/// caller owns returned memory
560fn ccPrintFileName(gpa: Allocator, io: Io, args: CCPrintFileNameOptions) ![:0]u8 {561fn ccPrintFileName(gpa: Allocator, io: Io, args: CCPrintFileNameOptions) ![]u8 {
561 // Detect infinite loops.562 // Detect infinite loops.
562 var env_map = std.process.getEnvMap(gpa) catch |err| switch (err) {563 var environ_map = try args.environ_map.clone(gpa);
563 error.Unexpected => unreachable, // WASI-only564 defer environ_map.deinit();
564 else => |e| return e,565 const skip_cc_env_var = if (environ_map.get(inf_loop_env_key)) |phase| blk: {
565 };
566 defer env_map.deinit();
567 const skip_cc_env_var = if (env_map.get(inf_loop_env_key)) |phase| blk: {
568 if (std.mem.eql(u8, phase, "1")) {566 if (std.mem.eql(u8, phase, "1")) {
569 try env_map.put(inf_loop_env_key, "2");567 try environ_map.put(inf_loop_env_key, "2");
570 break :blk true;568 break :blk true;
571 } else {569 } else {
572 return error.ZigIsTheCCompiler;570 return error.ZigIsTheCCompiler;
573 }571 }
574 } else blk: {572 } else blk: {
575 try env_map.put(inf_loop_env_key, "1");573 try environ_map.put(inf_loop_env_key, "1");
576 break :blk false;574 break :blk false;
577 };575 };
578576
...@@ -582,13 +580,13 @@ fn ccPrintFileName(gpa: Allocator, io: Io, args: CCPrintFileNameOptions) ![:0]u8...@@ -582,13 +580,13 @@ fn ccPrintFileName(gpa: Allocator, io: Io, args: CCPrintFileNameOptions) ![:0]u8
582 const arg1 = try std.fmt.allocPrint(gpa, "-print-file-name={s}", .{args.search_basename});580 const arg1 = try std.fmt.allocPrint(gpa, "-print-file-name={s}", .{args.search_basename});
583 defer gpa.free(arg1);581 defer gpa.free(arg1);
584582
585 try appendCcExe(&argv, skip_cc_env_var);583 try appendCcExe(&argv, skip_cc_env_var, &environ_map);
586 try argv.append(arg1);584 try argv.append(arg1);
587585
588 const run_res = std.process.Child.run(gpa, io, .{586 const run_res = std.process.run(gpa, io, .{
589 .argv = argv.items,
590 .max_output_bytes = 1024 * 1024,587 .max_output_bytes = 1024 * 1024,
591 .env_map = &env_map,588 .argv = argv.items,
589 .environ_map = &environ_map,
592 // Some C compilers, such as Clang, are known to rely on argv[0] to find the path590 // Some C compilers, such as Clang, are known to rely on argv[0] to find the path
593 // to their own executable, without even bothering to resolve PATH. This results in the message:591 // to their own executable, without even bothering to resolve PATH. This results in the message:
594 // error: unable to execute command: Executable "" doesn't exist!592 // error: unable to execute command: Executable "" doesn't exist!
...@@ -603,7 +601,7 @@ fn ccPrintFileName(gpa: Allocator, io: Io, args: CCPrintFileNameOptions) ![:0]u8...@@ -603,7 +601,7 @@ fn ccPrintFileName(gpa: Allocator, io: Io, args: CCPrintFileNameOptions) ![:0]u8
603 gpa.free(run_res.stderr);601 gpa.free(run_res.stderr);
604 }602 }
605 switch (run_res.term) {603 switch (run_res.term) {
606 .Exited => |code| if (code != 0) {604 .exited => |code| if (code != 0) {
607 printVerboseInvocation(argv.items, args.search_basename, args.verbose, run_res.stderr);605 printVerboseInvocation(argv.items, args.search_basename, args.verbose, run_res.stderr);
608 return error.CCompilerExitCode;606 return error.CCompilerExitCode;
609 },607 },
...@@ -619,10 +617,10 @@ fn ccPrintFileName(gpa: Allocator, io: Io, args: CCPrintFileNameOptions) ![:0]u8...@@ -619,10 +617,10 @@ fn ccPrintFileName(gpa: Allocator, io: Io, args: CCPrintFileNameOptions) ![:0]u8
619 // So we detect failure by checking if the output matches exactly the input.617 // So we detect failure by checking if the output matches exactly the input.
620 if (std.mem.eql(u8, line, args.search_basename)) return error.LibCRuntimeNotFound;618 if (std.mem.eql(u8, line, args.search_basename)) return error.LibCRuntimeNotFound;
621 switch (args.want_dirname) {619 switch (args.want_dirname) {
622 .full_path => return gpa.dupeZ(u8, line),620 .full_path => return gpa.dupe(u8, line),
623 .only_dir => {621 .only_dir => {
624 const dirname = fs.path.dirname(line) orelse return error.LibCRuntimeNotFound;622 const dirname = fs.path.dirname(line) orelse return error.LibCRuntimeNotFound;
625 return gpa.dupeZ(u8, dirname);623 return gpa.dupe(u8, dirname);
626 },624 },
627 }625 }
628}626}
...@@ -668,14 +666,18 @@ fn fillInstallations(...@@ -668,14 +666,18 @@ fn fillInstallations(
668666
669const inf_loop_env_key = "ZIG_IS_DETECTING_LIBC_PATHS";667const inf_loop_env_key = "ZIG_IS_DETECTING_LIBC_PATHS";
670668
671fn appendCcExe(args: *std.array_list.Managed([]const u8), skip_cc_env_var: bool) !void {669fn appendCcExe(
670 args: *std.array_list.Managed([]const u8),
671 skip_cc_env_var: bool,
672 environ_map: *const Environ.Map,
673) !void {
672 const default_cc_exe = if (is_windows) "cc.exe" else "cc";674 const default_cc_exe = if (is_windows) "cc.exe" else "cc";
673 try args.ensureUnusedCapacity(1);675 try args.ensureUnusedCapacity(1);
674 if (skip_cc_env_var) {676 if (skip_cc_env_var) {
675 args.appendAssumeCapacity(default_cc_exe);677 args.appendAssumeCapacity(default_cc_exe);
676 return;678 return;
677 }679 }
678 const cc_env_var = std.zig.EnvVar.CC.getPosix() orelse {680 const cc_env_var = std.zig.EnvVar.CC.get(environ_map) orelse {
679 args.appendAssumeCapacity(default_cc_exe);681 args.appendAssumeCapacity(default_cc_exe);
680 return;682 return;
681 };683 };
lib/std/zig/WindowsSdk.zig+47-30
...@@ -6,6 +6,7 @@ const Io = std.Io;...@@ -6,6 +6,7 @@ const Io = std.Io;
6const Dir = std.Io.Dir;6const Dir = std.Io.Dir;
7const Writer = std.Io.Writer;7const Writer = std.Io.Writer;
8const Allocator = std.mem.Allocator;8const Allocator = std.mem.Allocator;
9const Environ = std.process.Environ;
910
10windows10sdk: ?Installation,11windows10sdk: ?Installation,
11windows81sdk: ?Installation,12windows81sdk: ?Installation,
...@@ -24,7 +25,12 @@ const product_version_max_length = version_major_minor_max_length + ".65535".len...@@ -24,7 +25,12 @@ const product_version_max_length = version_major_minor_max_length + ".65535".len
24/// Find path and version of Windows 10 SDK and Windows 8.1 SDK, and find path to MSVC's `lib/` directory.25/// Find path and version of Windows 10 SDK and Windows 8.1 SDK, and find path to MSVC's `lib/` directory.
25/// Caller owns the result's fields.26/// Caller owns the result's fields.
26/// Returns memory allocated by `gpa`27/// Returns memory allocated by `gpa`
27pub fn find(gpa: Allocator, io: Io, arch: std.Target.Cpu.Arch) error{ OutOfMemory, NotFound, PathTooLong }!WindowsSdk {28pub fn find(
29 gpa: Allocator,
30 io: Io,
31 arch: std.Target.Cpu.Arch,
32 environ_map: *const Environ.Map,
33) error{ OutOfMemory, NotFound, PathTooLong }!WindowsSdk {
28 if (builtin.os.tag != .windows) return error.NotFound;34 if (builtin.os.tag != .windows) return error.NotFound;
2935
30 //note(dimenus): If this key doesn't exist, neither the Win 8 SDK nor the Win 10 SDK is installed36 //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...@@ -49,7 +55,7 @@ pub fn find(gpa: Allocator, io: Io, arch: std.Target.Cpu.Arch) error{ OutOfMemor
49 };55 };
50 errdefer if (windows81sdk) |*w| w.free(gpa);56 errdefer if (windows81sdk) |*w| w.free(gpa);
5157
52 const msvc_lib_dir: ?[]const u8 = MsvcLibDir.find(gpa, io, arch) catch |err| switch (err) {58 const msvc_lib_dir: ?[]const u8 = MsvcLibDir.find(gpa, io, arch, environ_map) catch |err| switch (err) {
53 error.MsvcLibDirNotFound => null,59 error.MsvcLibDirNotFound => null,
54 error.OutOfMemory => return error.OutOfMemory,60 error.OutOfMemory => return error.OutOfMemory,
55 };61 };
...@@ -671,7 +677,11 @@ const MsvcLibDir = struct {...@@ -671,7 +677,11 @@ const MsvcLibDir = struct {
671 return Dir.openDirAbsolute(io, instances_path, .{ .iterate = true }) catch return error.PathNotFound;677 return Dir.openDirAbsolute(io, instances_path, .{ .iterate = true }) catch return error.PathNotFound;
672 }678 }
673679
674 fn findInstancesDir(gpa: Allocator, io: Io) error{ OutOfMemory, PathNotFound }!Dir {680 fn findInstancesDir(
681 gpa: Allocator,
682 io: Io,
683 environ_map: *const Environ.Map,
684 ) error{ OutOfMemory, PathNotFound }!Dir {
675 // First, try getting the packages cache path from the registry.685 // First, try getting the packages cache path from the registry.
676 // This only seems to exist when the path is different from the default.686 // This only seems to exist when the path is different from the default.
677 method1: {687 method1: {
...@@ -691,16 +701,13 @@ const MsvcLibDir = struct {...@@ -691,16 +701,13 @@ const MsvcLibDir = struct {
691 // If that can't be found, fall back to manually appending701 // If that can't be found, fall back to manually appending
692 // `Microsoft\VisualStudio\Packages\_Instances` to %PROGRAMDATA%702 // `Microsoft\VisualStudio\Packages\_Instances` to %PROGRAMDATA%
693 method3: {703 method3: {
694 const program_data = std.process.getEnvVarOwned(gpa, "PROGRAMDATA") catch |err| switch (err) {704 const program_data = std.zig.EnvVar.PROGRAMDATA.get(environ_map) orelse break :method3;
695 error.OutOfMemory => |e| return e,
696 error.InvalidWtf8 => unreachable,
697 error.EnvironmentVariableNotFound => break :method3,
698 };
699 defer gpa.free(program_data);
700705
701 if (!Dir.path.isAbsolute(program_data)) break :method3;706 if (!Dir.path.isAbsolute(program_data)) break :method3;
702707
703 const instances_path = try Dir.path.join(gpa, &.{ program_data, "Microsoft", "VisualStudio", "Packages", "_Instances" });708 const instances_path = try Dir.path.join(gpa, &.{
709 program_data, "Microsoft", "VisualStudio", "Packages", "_Instances",
710 });
704 defer gpa.free(instances_path);711 defer gpa.free(instances_path);
705712
706 return Dir.openDirAbsolute(io, instances_path, .{ .iterate = true }) catch break :method3;713 return Dir.openDirAbsolute(io, instances_path, .{ .iterate = true }) catch break :method3;
...@@ -754,12 +761,17 @@ const MsvcLibDir = struct {...@@ -754,12 +761,17 @@ const MsvcLibDir = struct {
754 ///761 ///
755 /// The logic in this function is intended to match what ISetupConfiguration does762 /// The logic in this function is intended to match what ISetupConfiguration does
756 /// under-the-hood, as verified using Procmon.763 /// under-the-hood, as verified using Procmon.
757 fn findViaCOM(gpa: Allocator, io: Io, arch: std.Target.Cpu.Arch) error{ OutOfMemory, PathNotFound }![]const u8 {764 fn findViaCOM(
765 gpa: Allocator,
766 io: Io,
767 arch: std.Target.Cpu.Arch,
768 environ_map: *const Environ.Map,
769 ) error{ OutOfMemory, PathNotFound }![]const u8 {
758 // Typically `%PROGRAMDATA%\Microsoft\VisualStudio\Packages\_Instances`770 // Typically `%PROGRAMDATA%\Microsoft\VisualStudio\Packages\_Instances`
759 // This will contain directories with names of instance IDs like 80a758ca,771 // This will contain directories with names of instance IDs like 80a758ca,
760 // which will contain `state.json` files that have the version and772 // which will contain `state.json` files that have the version and
761 // installation directory.773 // installation directory.
762 var instances_dir = try findInstancesDir(gpa, io);774 var instances_dir = try findInstancesDir(gpa, io, environ_map);
763 defer instances_dir.close(io);775 defer instances_dir.close(io);
764776
765 var state_subpath_buf: [Dir.max_name_bytes + 32]u8 = undefined;777 var state_subpath_buf: [Dir.max_name_bytes + 32]u8 = undefined;
...@@ -856,15 +868,16 @@ const MsvcLibDir = struct {...@@ -856,15 +868,16 @@ const MsvcLibDir = struct {
856 }868 }
857869
858 // https://learn.microsoft.com/en-us/visualstudio/install/tools-for-managing-visual-studio-instances?view=vs-2022#editing-the-registry-for-a-visual-studio-instance870 // 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
859 fn findViaRegistry(gpa: Allocator, io: Io, arch: std.Target.Cpu.Arch) error{ OutOfMemory, PathNotFound }![]const u8 {871 fn findViaRegistry(
872 gpa: Allocator,
873 io: Io,
874 arch: std.Target.Cpu.Arch,
875 environ_map: *const Environ.Map,
876 ) error{ OutOfMemory, PathNotFound }![]const u8 {
860877
861 // %localappdata%\Microsoft\VisualStudio\878 // %localappdata%\Microsoft\VisualStudio\
862 // %appdata%\Local\Microsoft\VisualStudio\879 // %appdata%\Local\Microsoft\VisualStudio\
863 const local_app_data_path = (std.zig.EnvVar.LOCALAPPDATA.get(gpa) catch |err| switch (err) {880 const local_app_data_path = std.zig.EnvVar.LOCALAPPDATA.get(environ_map) orelse return error.PathNotFound;
864 error.OutOfMemory => |e| return e,
865 error.InvalidWtf8 => return error.PathNotFound,
866 }) orelse return error.PathNotFound;
867 defer gpa.free(local_app_data_path);
868 const visualstudio_folder_path = try Dir.path.join(gpa, &.{881 const visualstudio_folder_path = try Dir.path.join(gpa, &.{
869 local_app_data_path, "Microsoft\\VisualStudio\\",882 local_app_data_path, "Microsoft\\VisualStudio\\",
870 });883 });
...@@ -951,16 +964,15 @@ const MsvcLibDir = struct {...@@ -951,16 +964,15 @@ const MsvcLibDir = struct {
951 return msvc_dir;964 return msvc_dir;
952 }965 }
953966
954 fn findViaVs7Key(gpa: Allocator, io: Io, arch: std.Target.Cpu.Arch) error{ OutOfMemory, PathNotFound }![]const u8 {967 fn findViaVs7Key(
968 gpa: Allocator,
969 io: Io,
970 arch: std.Target.Cpu.Arch,
971 environ_map: *const Environ.Map,
972 ) error{ OutOfMemory, PathNotFound }![]const u8 {
955 var base_path: std.array_list.Managed(u8) = base_path: {973 var base_path: std.array_list.Managed(u8) = base_path: {
956 try_env: {974 try_env: {
957 var env_map = std.process.getEnvMap(gpa) catch |err| switch (err) {975 if (environ_map.get("VS140COMNTOOLS")) |VS140COMNTOOLS| {
958 error.OutOfMemory => return error.OutOfMemory,
959 else => break :try_env,
960 };
961 defer env_map.deinit();
962
963 if (env_map.get("VS140COMNTOOLS")) |VS140COMNTOOLS| {
964 if (VS140COMNTOOLS.len < "C:\\Common7\\Tools".len) break :try_env;976 if (VS140COMNTOOLS.len < "C:\\Common7\\Tools".len) break :try_env;
965 if (!Dir.path.isAbsolute(VS140COMNTOOLS)) break :try_env;977 if (!Dir.path.isAbsolute(VS140COMNTOOLS)) break :try_env;
966 var list = std.array_list.Managed(u8).init(gpa);978 var list = std.array_list.Managed(u8).init(gpa);
...@@ -1030,12 +1042,17 @@ const MsvcLibDir = struct {...@@ -1030,12 +1042,17 @@ const MsvcLibDir = struct {
10301042
1031 /// Find path to MSVC's `lib/` directory.1043 /// Find path to MSVC's `lib/` directory.
1032 /// Caller owns the result.1044 /// Caller owns the result.
1033 pub fn find(gpa: Allocator, io: Io, arch: std.Target.Cpu.Arch) error{ OutOfMemory, MsvcLibDirNotFound }![]const u8 {1045 pub fn find(
1034 const full_path = MsvcLibDir.findViaCOM(gpa, io, arch) catch |err1| switch (err1) {1046 gpa: Allocator,
1047 io: Io,
1048 arch: std.Target.Cpu.Arch,
1049 environ_map: *const Environ.Map,
1050 ) error{ OutOfMemory, MsvcLibDirNotFound }![]const u8 {
1051 const full_path = MsvcLibDir.findViaCOM(gpa, io, arch, environ_map) catch |err1| switch (err1) {
1035 error.OutOfMemory => return error.OutOfMemory,1052 error.OutOfMemory => return error.OutOfMemory,
1036 error.PathNotFound => MsvcLibDir.findViaRegistry(gpa, io, arch) catch |err2| switch (err2) {1053 error.PathNotFound => MsvcLibDir.findViaRegistry(gpa, io, arch, environ_map) catch |err2| switch (err2) {
1037 error.OutOfMemory => return error.OutOfMemory,1054 error.OutOfMemory => return error.OutOfMemory,
1038 error.PathNotFound => MsvcLibDir.findViaVs7Key(gpa, io, arch) catch |err3| switch (err3) {1055 error.PathNotFound => MsvcLibDir.findViaVs7Key(gpa, io, arch, environ_map) catch |err3| switch (err3) {
1039 error.OutOfMemory => return error.OutOfMemory,1056 error.OutOfMemory => return error.OutOfMemory,
1040 error.PathNotFound => return error.MsvcLibDirNotFound,1057 error.PathNotFound => return error.MsvcLibDirNotFound,
1041 },1058 },
lib/std/zig/system/NativePaths.zig+17-22
...@@ -14,10 +14,16 @@ framework_dirs: std.ArrayList([]const u8) = .empty,...@@ -14,10 +14,16 @@ framework_dirs: std.ArrayList([]const u8) = .empty,
14rpaths: std.ArrayList([]const u8) = .empty,14rpaths: std.ArrayList([]const u8) = .empty,
15warnings: std.ArrayList([]const u8) = .empty,15warnings: std.ArrayList([]const u8) = .empty,
1616
17pub fn detect(arena: Allocator, io: Io, native_target: *const std.Target) !NativePaths {17pub fn detect(
18 arena: Allocator,
19 io: Io,
20 native_target: *const std.Target,
21 environ_map: *process.Environ.Map,
22) !NativePaths {
18 var self: NativePaths = .{ .arena = arena };23 var self: NativePaths = .{ .arena = arena };
19 var is_nix = false;24 var is_nix = false;
20 if (process.getEnvVarOwned(arena, "NIX_CFLAGS_COMPILE")) |nix_cflags_compile| {25
26 if (std.zig.EnvVar.NIX_CFLAGS_COMPILE.get(environ_map)) |nix_cflags_compile| {
21 is_nix = true;27 is_nix = true;
22 var it = mem.tokenizeScalar(u8, nix_cflags_compile, ' ');28 var it = mem.tokenizeScalar(u8, nix_cflags_compile, ' ');
23 while (true) {29 while (true) {
...@@ -41,12 +47,9 @@ pub fn detect(arena: Allocator, io: Io, native_target: *const std.Target) !Nativ...@@ -41,12 +47,9 @@ pub fn detect(arena: Allocator, io: Io, native_target: *const std.Target) !Nativ
41 try self.addWarningFmt("Unrecognized C flag from NIX_CFLAGS_COMPILE: {s}", .{word});47 try self.addWarningFmt("Unrecognized C flag from NIX_CFLAGS_COMPILE: {s}", .{word});
42 }48 }
43 }49 }
44 } else |err| switch (err) {
45 error.InvalidWtf8 => unreachable,
46 error.EnvironmentVariableNotFound => {},
47 error.OutOfMemory => |e| return e,
48 }50 }
49 if (process.getEnvVarOwned(arena, "NIX_LDFLAGS")) |nix_ldflags| {51
52 if (std.zig.EnvVar.NIX_LDFLAGS.get(environ_map)) |nix_ldflags| {
50 is_nix = true;53 is_nix = true;
51 var it = mem.tokenizeScalar(u8, nix_ldflags, ' ');54 var it = mem.tokenizeScalar(u8, nix_ldflags, ' ');
52 while (true) {55 while (true) {
...@@ -73,12 +76,9 @@ pub fn detect(arena: Allocator, io: Io, native_target: *const std.Target) !Nativ...@@ -73,12 +76,9 @@ pub fn detect(arena: Allocator, io: Io, native_target: *const std.Target) !Nativ
73 break;76 break;
74 }77 }
75 }78 }
76 } else |err| switch (err) {
77 error.InvalidWtf8 => unreachable,
78 error.EnvironmentVariableNotFound => {},
79 error.OutOfMemory => |e| return e,
80 }79 }
81 if (process.getEnvVarOwned(arena, "NIX_CFLAGS_LINK")) |nix_cflags_link| {80
81 if (std.zig.EnvVar.NIX_CFLAGS_LINK.get(environ_map)) |nix_cflags_link| {
82 is_nix = true;82 is_nix = true;
83 var it = mem.tokenizeScalar(u8, nix_cflags_link, ' ');83 var it = mem.tokenizeScalar(u8, nix_cflags_link, ' ');
84 while (true) {84 while (true) {
...@@ -105,11 +105,8 @@ pub fn detect(arena: Allocator, io: Io, native_target: *const std.Target) !Nativ...@@ -105,11 +105,8 @@ pub fn detect(arena: Allocator, io: Io, native_target: *const std.Target) !Nativ
105 break;105 break;
106 }106 }
107 }107 }
108 } else |err| switch (err) {
109 error.InvalidWtf8 => unreachable,
110 error.EnvironmentVariableNotFound => {},
111 error.OutOfMemory => |e| return e,
112 }108 }
109
113 if (is_nix) {110 if (is_nix) {
114 return self;111 return self;
115 }112 }
...@@ -124,7 +121,7 @@ pub fn detect(arena: Allocator, io: Io, native_target: *const std.Target) !Nativ...@@ -124,7 +121,7 @@ pub fn detect(arena: Allocator, io: Io, native_target: *const std.Target) !Nativ
124 }121 }
125122
126 // Check for homebrew paths123 // Check for homebrew paths
127 if (std.posix.getenv("HOMEBREW_PREFIX")) |prefix| {124 if (std.zig.EnvVar.HOMEBREW_PREFIX.get(environ_map)) |prefix| {
128 try self.addLibDir(try std.fs.path.join(arena, &.{ prefix, "/lib" }));125 try self.addLibDir(try std.fs.path.join(arena, &.{ prefix, "/lib" }));
129 try self.addIncludeDir(try std.fs.path.join(arena, &.{ prefix, "/include" }));126 try self.addIncludeDir(try std.fs.path.join(arena, &.{ prefix, "/include" }));
130 }127 }
...@@ -180,23 +177,21 @@ pub fn detect(arena: Allocator, io: Io, native_target: *const std.Target) !Nativ...@@ -180,23 +177,21 @@ pub fn detect(arena: Allocator, io: Io, native_target: *const std.Target) !Nativ
180177
181 // Distros like guix don't use FHS, so they rely on environment178 // Distros like guix don't use FHS, so they rely on environment
182 // variables to search for headers and libraries.179 // variables to search for headers and libraries.
183 // We use os.getenv here since this part won't be executed on180 if (std.zig.EnvVar.C_INCLUDE_PATH.get(environ_map)) |c_include_path| {
184 // windows, to get rid of unnecessary error handling.
185 if (std.posix.getenv("C_INCLUDE_PATH")) |c_include_path| {
186 var it = mem.tokenizeScalar(u8, c_include_path, ':');181 var it = mem.tokenizeScalar(u8, c_include_path, ':');
187 while (it.next()) |dir| {182 while (it.next()) |dir| {
188 try self.addIncludeDir(dir);183 try self.addIncludeDir(dir);
189 }184 }
190 }185 }
191186
192 if (std.posix.getenv("CPLUS_INCLUDE_PATH")) |cplus_include_path| {187 if (std.zig.EnvVar.CPLUS_INCLUDE_PATH.get(environ_map)) |cplus_include_path| {
193 var it = mem.tokenizeScalar(u8, cplus_include_path, ':');188 var it = mem.tokenizeScalar(u8, cplus_include_path, ':');
194 while (it.next()) |dir| {189 while (it.next()) |dir| {
195 try self.addIncludeDir(dir);190 try self.addIncludeDir(dir);
196 }191 }
197 }192 }
198193
199 if (std.posix.getenv("LIBRARY_PATH")) |library_path| {194 if (std.zig.EnvVar.LIBRARY_PATH.get(environ_map)) |library_path| {
200 var it = mem.tokenizeScalar(u8, library_path, ':');195 var it = mem.tokenizeScalar(u8, library_path, ':');
201 while (it.next()) |dir| {196 while (it.next()) |dir| {
202 try self.addLibDir(dir);197 try self.addLibDir(dir);
lib/std/zig/system/darwin.zig+4-4
...@@ -17,7 +17,7 @@ pub const macos = @import("darwin/macos.zig");...@@ -17,7 +17,7 @@ pub const macos = @import("darwin/macos.zig");
17///17///
18/// If error.OutOfMemory occurs in Allocator, this function returns null.18/// If error.OutOfMemory occurs in Allocator, this function returns null.
19pub fn isSdkInstalled(gpa: Allocator, io: Io) bool {19pub fn isSdkInstalled(gpa: Allocator, io: Io) bool {
20 const result = std.process.Child.run(gpa, io, .{20 const result = std.process.run(gpa, io, .{
21 .argv = &.{ "xcode-select", "--print-path" },21 .argv = &.{ "xcode-select", "--print-path" },
22 }) catch return false;22 }) catch return false;
23 defer {23 defer {
...@@ -25,7 +25,7 @@ pub fn isSdkInstalled(gpa: Allocator, io: Io) bool {...@@ -25,7 +25,7 @@ pub fn isSdkInstalled(gpa: Allocator, io: Io) bool {
25 gpa.free(result.stdout);25 gpa.free(result.stdout);
26 }26 }
27 return switch (result.term) {27 return switch (result.term) {
28 .Exited => |code| if (code == 0) result.stdout.len > 0 else false,28 .exited => |code| if (code == 0) result.stdout.len > 0 else false,
29 else => false,29 else => false,
30 };30 };
31}31}
...@@ -47,13 +47,13 @@ pub fn getSdk(gpa: Allocator, io: Io, target: *const Target) ?[]const u8 {...@@ -47,13 +47,13 @@ pub fn getSdk(gpa: Allocator, io: Io, target: *const Target) ?[]const u8 {
47 else => return null,47 else => return null,
48 };48 };
49 const argv = &[_][]const u8{ "xcrun", "--sdk", sdk, "--show-sdk-path" };49 const argv = &[_][]const u8{ "xcrun", "--sdk", sdk, "--show-sdk-path" };
50 const result = std.process.Child.run(gpa, io, .{ .argv = argv }) catch return null;50 const result = std.process.run(gpa, io, .{ .argv = argv }) catch return null;
51 defer {51 defer {
52 gpa.free(result.stderr);52 gpa.free(result.stderr);
53 gpa.free(result.stdout);53 gpa.free(result.stdout);
54 }54 }
55 switch (result.term) {55 switch (result.term) {
56 .Exited => |code| if (code != 0) return null,56 .exited => |code| if (code != 0) return null,
57 else => return null,57 else => return null,
58 }58 }
59 return gpa.dupe(u8, mem.trimEnd(u8, result.stdout, "\r\n")) catch null;59 return gpa.dupe(u8, mem.trimEnd(u8, result.stdout, "\r\n")) catch null;
src/Compilation.zig+64-35
...@@ -54,6 +54,7 @@ gpa: Allocator,...@@ -54,6 +54,7 @@ gpa: Allocator,
54/// threads at once.54/// threads at once.
55arena: Allocator,55arena: Allocator,
56io: Io,56io: Io,
57environ_map: *const std.process.Environ.Map,
57thread_limit: usize,58thread_limit: usize,
58/// Not every Compilation compiles .zig code! For example you could do `zig build-exe foo.o`.59/// Not every Compilation compiles .zig code! For example you could do `zig build-exe foo.o`.
59zcu: ?*Zcu,60zcu: ?*Zcu,
...@@ -761,11 +762,12 @@ pub const Directories = struct {...@@ -761,11 +762,12 @@ pub const Directories = struct {
761 .wasi => void,762 .wasi => void,
762 else => []const u8,763 else => []const u8,
763 },764 },
765 environ_map: *const std.process.Environ.Map,
764 ) Directories {766 ) Directories {
765 const wasi = builtin.target.os.tag == .wasi;767 const wasi = builtin.target.os.tag == .wasi;
766768
767 const cwd = introspect.getResolvedCwd(arena) catch |err| {769 const cwd = introspect.getResolvedCwd(arena) catch |err| {
768 fatal("unable to get cwd: {s}", .{@errorName(err)});770 fatal("unable to get cwd: {t}", .{err});
769 };771 };
770772
771 const zig_lib: Cache.Directory = d: {773 const zig_lib: Cache.Directory = d: {
...@@ -779,7 +781,7 @@ pub const Directories = struct {...@@ -779,7 +781,7 @@ pub const Directories = struct {
779 const global_cache: Cache.Directory = d: {781 const global_cache: Cache.Directory = d: {
780 if (override_global_cache) |path| break :d openUnresolved(arena, io, cwd, path, .@"global cache");782 if (override_global_cache) |path| break :d openUnresolved(arena, io, cwd, path, .@"global cache");
781 if (wasi) break :d openWasiPreopen(wasi_preopens, "/cache");783 if (wasi) break :d openWasiPreopen(wasi_preopens, "/cache");
782 const path = introspect.resolveGlobalCacheDir(arena) catch |err| {784 const path = introspect.resolveGlobalCacheDir(arena, environ_map) catch |err| {
783 fatal("unable to resolve zig cache directory: {t}", .{err});785 fatal("unable to resolve zig cache directory: {t}", .{err});
784 };786 };
785 break :d openUnresolved(arena, io, cwd, path, .@"global cache");787 break :d openUnresolved(arena, io, cwd, path, .@"global cache");
...@@ -1797,6 +1799,8 @@ pub const CreateOptions = struct {...@@ -1797,6 +1799,8 @@ pub const CreateOptions = struct {
17971799
1798 parent_whole_cache: ?ParentWholeCache = null,1800 parent_whole_cache: ?ParentWholeCache = null,
17991801
1802 environ_map: *const std.process.Environ.Map,
1803
1800 pub const Entry = link.File.OpenOptions.Entry;1804 pub const Entry = link.File.OpenOptions.Entry;
18011805
1802 /// Which fields are valid depends on the `cache_mode` given.1806 /// 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,...@@ -1967,6 +1971,7 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic,
1967 options.root_mod.resolved_target.is_native_abi,1971 options.root_mod.resolved_target.is_native_abi,
1968 link_libc,1972 link_libc,
1969 options.libc_installation,1973 options.libc_installation,
1974 options.environ_map,
1970 ) catch |err| switch (err) {1975 ) catch |err| switch (err) {
1971 error.OutOfMemory => |e| return e,1976 error.OutOfMemory => |e| return e,
1972 // Every other error is specifically related to finding the native installation1977 // 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,...@@ -2123,6 +2128,7 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic,
2123 .manifest_dir = options.dirs.local_cache.handle.createDirPathOpen(io, "h", .{}) catch |err| {2128 .manifest_dir = options.dirs.local_cache.handle.createDirPathOpen(io, "h", .{}) catch |err| {
2124 return diag.fail(.{ .create_cache_path = .{ .which = .local, .sub = "h", .err = err } });2129 return diag.fail(.{ .create_cache_path = .{ .which = .local, .sub = "h", .err = err } });
2125 },2130 },
2131 .cwd = options.dirs.cwd,
2126 };2132 };
2127 // These correspond to std.zig.Server.Message.PathPrefix.2133 // These correspond to std.zig.Server.Message.PathPrefix.
2128 cache.addPrefix(.{ .path = null, .handle = Io.Dir.cwd() });2134 cache.addPrefix(.{ .path = null, .handle = Io.Dir.cwd() });
...@@ -2306,6 +2312,7 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic,...@@ -2306,6 +2312,7 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic,
2306 .emit_llvm_ir = try options.emit_llvm_ir.resolve(arena, &options, .llvm_ir),2312 .emit_llvm_ir = try options.emit_llvm_ir.resolve(arena, &options, .llvm_ir),
2307 .emit_llvm_bc = try options.emit_llvm_bc.resolve(arena, &options, .llvm_bc),2313 .emit_llvm_bc = try options.emit_llvm_bc.resolve(arena, &options, .llvm_bc),
2308 .emit_docs = try options.emit_docs.resolve(arena, &options, .docs),2314 .emit_docs = try options.emit_docs.resolve(arena, &options, .docs),
2315 .environ_map = options.environ_map,
2309 };2316 };
23102317
2311 errdefer {2318 errdefer {
...@@ -5503,6 +5510,7 @@ fn workerDocsWasmFallible(comp: *Compilation, prog_node: std.Progress.Node) SubU...@@ -5503,6 +5510,7 @@ fn workerDocsWasmFallible(comp: *Compilation, prog_node: std.Progress.Node) SubU
5503 .verbose_llvm_bc = comp.verbose_llvm_bc,5510 .verbose_llvm_bc = comp.verbose_llvm_bc,
5504 .verbose_cimport = comp.verbose_cimport,5511 .verbose_cimport = comp.verbose_cimport,
5505 .verbose_llvm_cpu_features = comp.verbose_llvm_cpu_features,5512 .verbose_llvm_cpu_features = comp.verbose_llvm_cpu_features,
5513 .environ_map = comp.environ_map,
5506 }) catch |err| switch (err) {5514 }) catch |err| switch (err) {
5507 error.CreateFail => {5515 error.CreateFail => {
5508 comp.lockAndSetMiscFailure(.docs_wasm, "sub-compilation of docs_wasm failed: {f}", .{sub_create_diag});5516 comp.lockAndSetMiscFailure(.docs_wasm, "sub-compilation of docs_wasm failed: {f}", .{sub_create_diag});
...@@ -5705,6 +5713,7 @@ pub fn translateC(...@@ -5705,6 +5713,7 @@ pub fn translateC(
5705 translated_basename: []const u8,5713 translated_basename: []const u8,
5706 owner_mod: *Package.Module,5714 owner_mod: *Package.Module,
5707 prog_node: std.Progress.Node,5715 prog_node: std.Progress.Node,
5716 environ_map: *const std.process.Environ.Map,
5708) !CImportResult {5717) !CImportResult {
5709 dev.check(.translate_c_command);5718 dev.check(.translate_c_command);
57105719
...@@ -5774,7 +5783,7 @@ pub fn translateC(...@@ -5774,7 +5783,7 @@ pub fn translateC(
5774 }5783 }
57755784
5776 var stdout: []u8 = undefined;5785 var stdout: []u8 = undefined;
5777 try @import("main.zig").translateC(gpa, arena, io, argv.items, prog_node, &stdout);5786 try @import("main.zig").translateC(gpa, arena, io, argv.items, environ_map, prog_node, &stdout);
57785787
5779 if (out_dep_path) |dep_file_path| add_deps: {5788 if (out_dep_path) |dep_file_path| add_deps: {
5780 if (comp.verbose_cimport) log.info("processing dep file at {s}", .{dep_file_path});5789 if (comp.verbose_cimport) log.info("processing dep file at {s}", .{dep_file_path});
...@@ -5861,7 +5870,8 @@ pub fn cImport(...@@ -5861,7 +5870,8 @@ pub fn cImport(
5861 defer arena_allocator.deinit();5870 defer arena_allocator.deinit();
5862 const arena = arena_allocator.allocator();5871 const arena = arena_allocator.allocator();
58635872
5864 break :result try comp.translateC(5873 break :result try translateC(
5874 comp,
5865 arena,5875 arena,
5866 &man,5876 &man,
5867 .c,5877 .c,
...@@ -5869,6 +5879,7 @@ pub fn cImport(...@@ -5869,6 +5879,7 @@ pub fn cImport(
5869 translated_basename,5879 translated_basename,
5870 owner_mod,5880 owner_mod,
5871 prog_node,5881 prog_node,
5882 comp.environ_map,
5872 );5883 );
5873 };5884 };
58745885
...@@ -6249,7 +6260,7 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr...@@ -6249,7 +6260,7 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr
6249 // that we could "tail call" clang by doing an execve, and any use of6260 // that we could "tail call" clang by doing an execve, and any use of
6250 // the caching system would actually be problematic since the user is6261 // the caching system would actually be problematic since the user is
6251 // presumably doing their own caching by using dep file flags.6262 // presumably doing their own caching by using dep file flags.
6252 if (std.process.can_execv and direct_o and6263 if (std.process.can_replace and direct_o and
6253 comp.disable_c_depfile and comp.clang_passthrough_mode)6264 comp.disable_c_depfile and comp.clang_passthrough_mode)
6254 {6265 {
6255 try comp.addCCArgs(arena, &argv, ext, null, c_object.src.owner);6266 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...@@ -6281,8 +6292,8 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr
6281 try dumpArgv(io, argv.items);6292 try dumpArgv(io, argv.items);
6282 }6293 }
62836294
6284 const err = std.process.execv(arena, argv.items);6295 const err = std.process.replace(io, .{ .argv = argv.items });
6285 fatal("unable to execv clang: {s}", .{@errorName(err)});6296 fatal("unable to replace process with clang: {t}", .{err});
6286 }6297 }
62876298
6288 // We can't know the digest until we do the C compiler invocation,6299 // 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...@@ -6337,17 +6348,24 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr
6337 else => log.warn("failed to delete '{s}': {s}", .{ dep_file_path, @errorName(err) }),6348 else => log.warn("failed to delete '{s}': {s}", .{ dep_file_path, @errorName(err) }),
6338 };6349 };
6339 if (std.process.can_spawn) {6350 if (std.process.can_spawn) {
6340 var child = std.process.Child.init(argv.items, arena);
6341 if (comp.clang_passthrough_mode) {6351 if (comp.clang_passthrough_mode) {
6342 child.stdin_behavior = .Inherit;6352 var child = std.process.spawn(io, .{
6343 child.stdout_behavior = .Inherit;6353 .argv = argv.items,
6344 child.stderr_behavior = .Inherit;6354 .stdin = .inherit,
63456355 .stdout = .inherit,
6346 const term = child.spawnAndWait(io) catch |err| {6356 .stderr = .inherit,
6347 return comp.failCObj(c_object, "failed to spawn zig clang (passthrough mode) {s}: {s}", .{ argv.items[0], @errorName(err) });6357 }) catch |err| {
6358 return comp.failCObj(c_object, "failed to spawn zig clang (passthrough mode) {s}: {t}", .{
6359 argv.items[0], err,
6360 });
6361 };
6362 const term = child.wait(io) catch |err| {
6363 return comp.failCObj(c_object, "failed to wait zig clang (passthrough mode) {s}: {t}", .{
6364 argv.items[0], err,
6365 });
6348 };6366 };
6349 switch (term) {6367 switch (term) {
6350 .Exited => |code| {6368 .exited => |code| {
6351 if (code != 0) {6369 if (code != 0) {
6352 std.process.exit(code);6370 std.process.exit(code);
6353 }6371 }
...@@ -6357,21 +6375,21 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr...@@ -6357,21 +6375,21 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr
6357 else => std.process.abort(),6375 else => std.process.abort(),
6358 }6376 }
6359 } else {6377 } else {
6360 child.stdin_behavior = .Ignore;6378 var child = try std.process.spawn(io, .{
6361 child.stdout_behavior = .Ignore;6379 .argv = argv.items,
6362 child.stderr_behavior = .Pipe;6380 .stdin = .ignore,
63636381 .stdout = .ignore,
6364 try child.spawn(io);6382 .stderr = .pipe,
6383 });
63656384
6366 var stderr_reader = child.stderr.?.readerStreaming(io, &.{});6385 var stderr_reader = child.stderr.?.readerStreaming(io, &.{});
6367 const stderr = try stderr_reader.interface.allocRemaining(arena, .limited(std.math.maxInt(u32)));6386 const stderr = try stderr_reader.interface.allocRemaining(arena, .limited(std.math.maxInt(u32)));
63686387
6369 const term = child.wait(io) catch |err| {6388 const term = child.wait(io) catch |err|
6370 return comp.failCObj(c_object, "failed to spawn zig clang {s}: {s}", .{ argv.items[0], @errorName(err) });6389 return comp.failCObj(c_object, "failed to spawn zig clang {s}: {t}", .{ argv.items[0], err });
6371 };
63726390
6373 switch (term) {6391 switch (term) {
6374 .Exited => |code| if (code != 0) if (out_diag_path) |diag_file_path| {6392 .exited => |code| if (code != 0) if (out_diag_path) |diag_file_path| {
6375 const bundle = CObject.Diag.Bundle.parse(gpa, io, diag_file_path) catch |err| {6393 const bundle = CObject.Diag.Bundle.parse(gpa, io, diag_file_path) catch |err| {
6376 log.err("{}: failed to parse clang diagnostics: {s}", .{ err, stderr });6394 log.err("{}: failed to parse clang diagnostics: {s}", .{ err, stderr });
6377 return comp.failCObj(c_object, "clang exited with code {d}", .{code});6395 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...@@ -6381,6 +6399,10 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr
6381 log.err("clang failed with stderr: {s}", .{stderr});6399 log.err("clang failed with stderr: {s}", .{stderr});
6382 return comp.failCObj(c_object, "clang exited with code {d}", .{code});6400 return comp.failCObj(c_object, "clang exited with code {d}", .{code});
6383 },6401 },
6402 .signal => |sig| {
6403 log.err("clang failed with stderr: {s}", .{stderr});
6404 return comp.failCObj(c_object, "clang terminated with signal {t}", .{sig});
6405 },
6384 else => {6406 else => {
6385 log.err("clang terminated with stderr: {s}", .{stderr});6407 log.err("clang terminated with stderr: {s}", .{stderr});
6386 return comp.failCObj(c_object, "clang terminated unexpectedly", .{});6408 return comp.failCObj(c_object, "clang terminated unexpectedly", .{});
...@@ -6741,15 +6763,16 @@ fn spawnZigRc(...@@ -6741,15 +6763,16 @@ fn spawnZigRc(
6741 var node_name: std.ArrayList(u8) = .empty;6763 var node_name: std.ArrayList(u8) = .empty;
6742 defer node_name.deinit(arena);6764 defer node_name.deinit(arena);
67436765
6744 var child = std.process.Child.init(argv, arena);6766 var child = std.process.spawn(io, .{
6745 child.stdin_behavior = .Ignore;6767 .argv = argv,
6746 child.stdout_behavior = .Pipe;6768 .stdin = .ignore,
6747 child.stderr_behavior = .Pipe;6769 .stdout = .pipe,
6748 child.progress_node = child_progress_node;6770 .stderr = .pipe,
67496771 .progress_node = child_progress_node,
6750 child.spawn(io) catch |err| {6772 }) catch |err| return comp.failWin32Resource(win32_resource, "unable to spawn {s} rc: {t}", .{
6751 return comp.failWin32Resource(win32_resource, "unable to spawn {s} rc: {t}", .{ argv[0], err });6773 argv[0], err,
6752 };6774 });
6775 defer child.kill(io);
67536776
6754 var poller = std.Io.poll(comp.gpa, enum { stdout, stderr }, .{6777 var poller = std.Io.poll(comp.gpa, enum { stdout, stderr }, .{
6755 .stdout = child.stdout.?,6778 .stdout = child.stdout.?,
...@@ -6781,16 +6804,20 @@ fn spawnZigRc(...@@ -6781,16 +6804,20 @@ fn spawnZigRc(
6781 const stderr = poller.reader(.stderr);6804 const stderr = poller.reader(.stderr);
67826805
6783 const term = child.wait(io) catch |err| {6806 const term = child.wait(io) catch |err| {
6784 return comp.failWin32Resource(win32_resource, "unable to wait for {s} rc: {s}", .{ argv[0], @errorName(err) });6807 return comp.failWin32Resource(win32_resource, "unable to wait for {s} rc: {t}", .{ argv[0], err });
6785 };6808 };
67866809
6787 switch (term) {6810 switch (term) {
6788 .Exited => |code| {6811 .exited => |code| {
6789 if (code != 0) {6812 if (code != 0) {
6790 log.err("zig rc failed with stderr:\n{s}", .{stderr.buffered()});6813 log.err("zig rc failed with stderr:\n{s}", .{stderr.buffered()});
6791 return comp.failWin32Resource(win32_resource, "zig rc exited with code {d}", .{code});6814 return comp.failWin32Resource(win32_resource, "zig rc exited with code {d}", .{code});
6792 }6815 }
6793 },6816 },
6817 .signal => |sig| {
6818 log.err("zig rc signaled {t} with stderr:\n{s}", .{ sig, stderr.buffered() });
6819 return comp.failWin32Resource(win32_resource, "zig rc terminated unexpectedly", .{});
6820 },
6794 else => {6821 else => {
6795 log.err("zig rc terminated with stderr:\n{s}", .{stderr.buffered()});6822 log.err("zig rc terminated with stderr:\n{s}", .{stderr.buffered()});
6796 return comp.failWin32Resource(win32_resource, "zig rc terminated unexpectedly", .{});6823 return comp.failWin32Resource(win32_resource, "zig rc terminated unexpectedly", .{});
...@@ -7959,6 +7986,7 @@ fn buildOutputFromZig(...@@ -7959,6 +7986,7 @@ fn buildOutputFromZig(
7959 .verbose_llvm_cpu_features = comp.verbose_llvm_cpu_features,7986 .verbose_llvm_cpu_features = comp.verbose_llvm_cpu_features,
7960 .clang_passthrough_mode = comp.clang_passthrough_mode,7987 .clang_passthrough_mode = comp.clang_passthrough_mode,
7961 .skip_linker_dependencies = true,7988 .skip_linker_dependencies = true,
7989 .environ_map = comp.environ_map,
7962 }) catch |err| switch (err) {7990 }) catch |err| switch (err) {
7963 error.CreateFail => {7991 error.CreateFail => {
7964 comp.lockAndSetMiscFailure(misc_task_tag, "sub-compilation of {t} failed: {f}", .{ misc_task_tag, sub_create_diag });7992 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(...@@ -8096,6 +8124,7 @@ pub fn build_crt_file(
8096 .verbose_llvm_cpu_features = comp.verbose_llvm_cpu_features,8124 .verbose_llvm_cpu_features = comp.verbose_llvm_cpu_features,
8097 .clang_passthrough_mode = comp.clang_passthrough_mode,8125 .clang_passthrough_mode = comp.clang_passthrough_mode,
8098 .skip_linker_dependencies = true,8126 .skip_linker_dependencies = true,
8127 .environ_map = comp.environ_map,
8099 }) catch |err| switch (err) {8128 }) catch |err| switch (err) {
8100 error.CreateFail => {8129 error.CreateFail => {
8101 comp.lockAndSetMiscFailure(misc_task_tag, "sub-compilation of {t} failed: {f}", .{ misc_task_tag, sub_create_diag });8130 comp.lockAndSetMiscFailure(misc_task_tag, "sub-compilation of {t} failed: {f}", .{ misc_task_tag, sub_create_diag });
src/DarwinPosixSpawn.zig deleted-225
...@@ -1,225 +0,0 @@
1const errno = std.posix.errno;
2const unexpectedErrno = std.posix.unexpectedErrno;
3
4pub const Error = error{
5 SystemResources,
6 InvalidFileDescriptor,
7 NameTooLong,
8 TooBig,
9 AccessDenied,
10 PermissionDenied,
11 InputOutput,
12 FileSystem,
13 FileNotFound,
14 InvalidExe,
15 NotDir,
16 FileBusy,
17 /// Returned when the child fails to execute either in the pre-exec() initialization step, or
18 /// when exec(3) is invoked.
19 ChildExecFailed,
20} || std.posix.UnexpectedError;
21
22pub const Attr = struct {
23 attr: std.c.posix_spawnattr_t,
24
25 pub fn init() Error!Attr {
26 var attr: std.c.posix_spawnattr_t = undefined;
27 switch (errno(std.c.posix_spawnattr_init(&attr))) {
28 .SUCCESS => return Attr{ .attr = attr },
29 .NOMEM => return error.SystemResources,
30 .INVAL => unreachable,
31 else => |err| return unexpectedErrno(err),
32 }
33 }
34
35 pub fn deinit(self: *Attr) void {
36 defer self.* = undefined;
37 switch (errno(std.c.posix_spawnattr_destroy(&self.attr))) {
38 .SUCCESS => return,
39 .INVAL => unreachable, // Invalid parameters.
40 else => unreachable,
41 }
42 }
43
44 pub fn get(self: Attr) Error!std.c.POSIX_SPAWN {
45 var flags: std.c.POSIX_SPAWN = undefined;
46 switch (errno(std.c.posix_spawnattr_getflags(&self.attr, &flags))) {
47 .SUCCESS => return flags,
48 .INVAL => unreachable,
49 else => |err| return unexpectedErrno(err),
50 }
51 }
52
53 pub fn set(self: *Attr, flags: std.c.POSIX_SPAWN) Error!void {
54 switch (errno(std.c.posix_spawnattr_setflags(&self.attr, flags))) {
55 .SUCCESS => return,
56 .INVAL => unreachable,
57 else => |err| return unexpectedErrno(err),
58 }
59 }
60};
61
62pub const Actions = struct {
63 actions: std.c.posix_spawn_file_actions_t,
64
65 pub fn init() Error!Actions {
66 var actions: std.c.posix_spawn_file_actions_t = undefined;
67 switch (errno(std.c.posix_spawn_file_actions_init(&actions))) {
68 .SUCCESS => return Actions{ .actions = actions },
69 .NOMEM => return error.SystemResources,
70 .INVAL => unreachable,
71 else => |err| return unexpectedErrno(err),
72 }
73 }
74
75 pub fn deinit(self: *Actions) void {
76 defer self.* = undefined;
77 switch (errno(std.c.posix_spawn_file_actions_destroy(&self.actions))) {
78 .SUCCESS => return,
79 .INVAL => unreachable, // Invalid parameters.
80 else => unreachable,
81 }
82 }
83
84 pub fn open(self: *Actions, fd: std.c.fd_t, path: []const u8, flags: u32, mode: std.c.mode_t) Error!void {
85 const posix_path = try std.posix.toPosixPath(path);
86 return self.openZ(fd, &posix_path, flags, mode);
87 }
88
89 pub fn openZ(self: *Actions, fd: std.c.fd_t, path: [*:0]const u8, flags: u32, mode: std.c.mode_t) Error!void {
90 switch (errno(std.c.posix_spawn_file_actions_addopen(&self.actions, fd, path, @as(c_int, @bitCast(flags)), mode))) {
91 .SUCCESS => return,
92 .BADF => return error.InvalidFileDescriptor,
93 .NOMEM => return error.SystemResources,
94 .NAMETOOLONG => return error.NameTooLong,
95 .INVAL => unreachable, // the value of file actions is invalid
96 else => |err| return unexpectedErrno(err),
97 }
98 }
99
100 pub fn close(self: *Actions, fd: std.c.fd_t) Error!void {
101 switch (errno(std.c.posix_spawn_file_actions_addclose(&self.actions, fd))) {
102 .SUCCESS => return,
103 .BADF => return error.InvalidFileDescriptor,
104 .NOMEM => return error.SystemResources,
105 .INVAL => unreachable, // the value of file actions is invalid
106 .NAMETOOLONG => unreachable,
107 else => |err| return unexpectedErrno(err),
108 }
109 }
110
111 pub fn dup2(self: *Actions, fd: std.c.fd_t, newfd: std.c.fd_t) Error!void {
112 switch (errno(std.c.posix_spawn_file_actions_adddup2(&self.actions, fd, newfd))) {
113 .SUCCESS => return,
114 .BADF => return error.InvalidFileDescriptor,
115 .NOMEM => return error.SystemResources,
116 .INVAL => unreachable, // the value of file actions is invalid
117 .NAMETOOLONG => unreachable,
118 else => |err| return unexpectedErrno(err),
119 }
120 }
121
122 pub fn inherit(self: *Actions, fd: std.c.fd_t) Error!void {
123 switch (errno(std.c.posix_spawn_file_actions_addinherit_np(&self.actions, fd))) {
124 .SUCCESS => return,
125 .BADF => return error.InvalidFileDescriptor,
126 .NOMEM => return error.SystemResources,
127 .INVAL => unreachable, // the value of file actions is invalid
128 .NAMETOOLONG => unreachable,
129 else => |err| return unexpectedErrno(err),
130 }
131 }
132
133 pub fn chdir(self: *Actions, path: []const u8) Error!void {
134 const posix_path = try std.posix.toPosixPath(path);
135 return self.chdirZ(&posix_path);
136 }
137
138 pub fn chdirZ(self: *Actions, path: [*:0]const u8) Error!void {
139 switch (errno(std.c.posix_spawn_file_actions_addchdir_np(&self.actions, path))) {
140 .SUCCESS => return,
141 .NOMEM => return error.SystemResources,
142 .NAMETOOLONG => return error.NameTooLong,
143 .BADF => unreachable,
144 .INVAL => unreachable, // the value of file actions is invalid
145 else => |err| return unexpectedErrno(err),
146 }
147 }
148
149 pub fn fchdir(self: *Actions, fd: std.c.fd_t) Error!void {
150 switch (errno(std.c.posix_spawn_file_actions_addfchdir_np(&self.actions, fd))) {
151 .SUCCESS => return,
152 .BADF => return error.InvalidFileDescriptor,
153 .NOMEM => return error.SystemResources,
154 .INVAL => unreachable, // the value of file actions is invalid
155 .NAMETOOLONG => unreachable,
156 else => |err| return unexpectedErrno(err),
157 }
158 }
159};
160
161pub fn spawn(
162 path: []const u8,
163 actions: ?Actions,
164 attr: ?Attr,
165 argv: [*:null]const ?[*:0]const u8,
166 envp: [*:null]const ?[*:0]const u8,
167) Error!std.c.pid_t {
168 const posix_path = try std.posix.toPosixPath(path);
169 return spawnZ(&posix_path, actions, attr, argv, envp);
170}
171
172pub fn spawnZ(
173 path: [*:0]const u8,
174 actions: ?Actions,
175 attr: ?Attr,
176 argv: [*:null]const ?[*:0]const u8,
177 envp: [*:null]const ?[*:0]const u8,
178) Error!std.c.pid_t {
179 var pid: std.c.pid_t = undefined;
180 switch (errno(std.c.posix_spawn(
181 &pid,
182 path,
183 if (actions) |a| &a.actions else null,
184 if (attr) |a| &a.attr else null,
185 argv,
186 envp,
187 ))) {
188 .SUCCESS => return pid,
189 .@"2BIG" => return error.TooBig,
190 .NOMEM => return error.SystemResources,
191 .BADF => return error.InvalidFileDescriptor,
192 .ACCES => return error.AccessDenied,
193 .IO => return error.InputOutput,
194 .LOOP => return error.FileSystem,
195 .NAMETOOLONG => return error.NameTooLong,
196 .NOENT => return error.FileNotFound,
197 .NOEXEC => return error.InvalidExe,
198 .NOTDIR => return error.NotDir,
199 .TXTBSY => return error.FileBusy,
200 .BADARCH => return error.InvalidExe,
201 .BADEXEC => return error.InvalidExe,
202 .FAULT => unreachable,
203 .INVAL => unreachable,
204 else => |err| return unexpectedErrno(err),
205 }
206}
207
208pub fn waitpid(pid: std.c.pid_t, flags: u32) Error!std.posix.WaitPidResult {
209 var status: c_int = undefined;
210 while (true) {
211 const rc = waitpid(pid, &status, @as(c_int, @intCast(flags)));
212 switch (errno(rc)) {
213 .SUCCESS => return std.posix.WaitPidResult{
214 .pid = @as(std.c.pid_t, @intCast(rc)),
215 .status = @as(u32, @bitCast(status)),
216 },
217 .INTR => continue,
218 .CHILD => return error.ChildExecFailed,
219 .INVAL => unreachable, // Invalid flags.
220 else => unreachable,
221 }
222 }
223}
224
225const std = @import("std");
src/Zcu/PerThread.zig+1-4
...@@ -2571,10 +2571,7 @@ fn newEmbedFile(...@@ -2571,10 +2571,7 @@ fn newEmbedFile(
2571 try whole.cache_manifest_mutex.lock(io);2571 try whole.cache_manifest_mutex.lock(io);
2572 defer whole.cache_manifest_mutex.unlock(io);2572 defer whole.cache_manifest_mutex.unlock(io);
25732573
2574 man.addFilePostContents(path_str, contents, new_file.stat) catch |err| switch (err) {2574 try man.addFilePostContents(path_str, contents, new_file.stat);
2575 error.Unexpected => unreachable,
2576 else => |e| return e,
2577 };
2578 }2575 }
25792576
2580 return new_file;2577 return new_file;
src/introspect.zig+17-20
...@@ -6,6 +6,7 @@ const Dir = std.Io.Dir;...@@ -6,6 +6,7 @@ const Dir = std.Io.Dir;
6const mem = std.mem;6const mem = std.mem;
7const Allocator = std.mem.Allocator;7const Allocator = std.mem.Allocator;
8const Cache = std.Build.Cache;8const Cache = std.Build.Cache;
9const assert = std.debug.assert;
910
10const build_options = @import("build_options");11const build_options = @import("build_options");
1112
...@@ -62,14 +63,14 @@ pub fn getResolvedCwd(gpa: Allocator) error{...@@ -62,14 +63,14 @@ pub fn getResolvedCwd(gpa: Allocator) error{
62 if (std.debug.runtime_safety) {63 if (std.debug.runtime_safety) {
63 const cwd = try std.process.getCwdAlloc(gpa);64 const cwd = try std.process.getCwdAlloc(gpa);
64 defer gpa.free(cwd);65 defer gpa.free(cwd);
65 std.debug.assert(mem.eql(u8, cwd, "."));66 assert(mem.eql(u8, cwd, "."));
66 }67 }
67 return "";68 return "";
68 }69 }
69 const cwd = try std.process.getCwdAlloc(gpa);70 const cwd = try std.process.getCwdAlloc(gpa);
70 defer gpa.free(cwd);71 defer gpa.free(cwd);
71 const resolved = try Dir.path.resolve(gpa, &.{cwd});72 const resolved = try Dir.path.resolve(gpa, &.{cwd});
72 std.debug.assert(Dir.path.isAbsolute(resolved));73 assert(Dir.path.isAbsolute(resolved));
73 return resolved;74 return resolved;
74}75}
7576
...@@ -101,31 +102,27 @@ pub fn findZigLibDirFromSelfExe(...@@ -101,31 +102,27 @@ pub fn findZigLibDirFromSelfExe(
101 return error.FileNotFound;102 return error.FileNotFound;
102}103}
103104
104/// Caller owns returned memory.105pub fn resolveGlobalCacheDir(arena: Allocator, environ_map: *const std.process.Environ.Map) ![]const u8 {
105pub fn resolveGlobalCacheDir(gpa: Allocator) ![]u8 {106 if (std.zig.EnvVar.ZIG_GLOBAL_CACHE_DIR.get(environ_map)) |value| return value;
106 if (try std.zig.EnvVar.ZIG_GLOBAL_CACHE_DIR.get(gpa)) |value| return value;
107107
108 const app_name = "zig";108 const app_name = "zig";
109109
110 switch (builtin.os.tag) {110 switch (builtin.os.tag) {
111 .wasi => @compileError("on WASI the global cache dir must be resolved with preopens"),111 .wasi => @compileError("on WASI the global cache dir must be resolved with preopens"),
112 .windows => {112 .windows => {
113 const local_app_data_dir = (std.zig.EnvVar.LOCALAPPDATA.get(gpa) catch |err| switch (err) {113 const local_app_data_dir = std.zig.EnvVar.LOCALAPPDATA.get(environ_map) orelse
114 error.OutOfMemory => |e| return e,114 return error.AppDataDirUnavailable;
115 error.InvalidWtf8 => return error.AppDataDirUnavailable,115 return Dir.path.join(arena, &.{ local_app_data_dir, app_name });
116 }) orelse return error.AppDataDirUnavailable;
117 defer gpa.free(local_app_data_dir);
118 return Dir.path.join(gpa, &.{ local_app_data_dir, app_name });
119 },116 },
120 else => {117 else => {
121 if (std.zig.EnvVar.XDG_CACHE_HOME.getPosix()) |cache_root| {118 if (std.zig.EnvVar.XDG_CACHE_HOME.get(environ_map)) |cache_root| {
122 if (cache_root.len > 0) {119 if (cache_root.len > 0) {
123 return Dir.path.join(gpa, &.{ cache_root, app_name });120 return Dir.path.join(arena, &.{ cache_root, app_name });
124 }121 }
125 }122 }
126 if (std.zig.EnvVar.HOME.getPosix()) |home| {123 if (std.zig.EnvVar.HOME.get(environ_map)) |home| {
127 if (home.len > 0) {124 if (home.len > 0) {
128 return Dir.path.join(gpa, &.{ home, ".cache", app_name });125 return Dir.path.join(arena, &.{ home, ".cache", app_name });
129 }126 }
130 }127 }
131 return error.AppDataDirUnavailable;128 return error.AppDataDirUnavailable;
...@@ -144,7 +141,7 @@ pub fn resolvePath(...@@ -144,7 +141,7 @@ pub fn resolvePath(
144 paths: []const []const u8,141 paths: []const []const u8,
145) Allocator.Error![]u8 {142) Allocator.Error![]u8 {
146 if (builtin.target.os.tag == .wasi) {143 if (builtin.target.os.tag == .wasi) {
147 std.debug.assert(mem.eql(u8, cwd_resolved, ""));144 assert(mem.eql(u8, cwd_resolved, ""));
148 const res = try Dir.path.resolve(gpa, paths);145 const res = try Dir.path.resolve(gpa, paths);
149 if (mem.eql(u8, res, ".")) {146 if (mem.eql(u8, res, ".")) {
150 gpa.free(res);147 gpa.free(res);
...@@ -164,8 +161,8 @@ pub fn resolvePath(...@@ -164,8 +161,8 @@ pub fn resolvePath(
164 gpa.free(res);161 gpa.free(res);
165 return "";162 return "";
166 }163 }
167 std.debug.assert(!Dir.path.isAbsolute(res));164 assert(!Dir.path.isAbsolute(res));
168 std.debug.assert(!isUpDir(res));165 assert(!isUpDir(res));
169 return res;166 return res;
170 }167 }
171168
...@@ -184,8 +181,8 @@ pub fn resolvePath(...@@ -184,8 +181,8 @@ pub fn resolvePath(
184 };181 };
185 errdefer gpa.free(path_resolved);182 errdefer gpa.free(path_resolved);
186183
187 std.debug.assert(Dir.path.isAbsolute(path_resolved));184 assert(Dir.path.isAbsolute(path_resolved));
188 std.debug.assert(Dir.path.isAbsolute(cwd_resolved));185 assert(Dir.path.isAbsolute(cwd_resolved));
189186
190 if (!std.mem.startsWith(u8, path_resolved, cwd_resolved)) return path_resolved; // not in cwd187 if (!std.mem.startsWith(u8, path_resolved, cwd_resolved)) return path_resolved; // not in cwd
191 if (path_resolved.len == cwd_resolved.len) {188 if (path_resolved.len == cwd_resolved.len) {
src/libs/freebsd.zig+2
...@@ -445,6 +445,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye...@@ -445,6 +445,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye
445 .gpa = gpa,445 .gpa = gpa,
446 .io = io,446 .io = io,
447 .manifest_dir = try comp.dirs.global_cache.handle.createDirPathOpen(io, "h", .{}),447 .manifest_dir = try comp.dirs.global_cache.handle.createDirPathOpen(io, "h", .{}),
448 .cwd = comp.dirs.cwd,
448 };449 };
449 cache.addPrefix(.{ .path = null, .handle = Io.Dir.cwd() });450 cache.addPrefix(.{ .path = null, .handle = Io.Dir.cwd() });
450 cache.addPrefix(comp.dirs.zig_lib);451 cache.addPrefix(comp.dirs.zig_lib);
...@@ -1119,6 +1120,7 @@ fn buildSharedLib(...@@ -1119,6 +1120,7 @@ fn buildSharedLib(
1119 .soname = soname,1120 .soname = soname,
1120 .c_source_files = &c_source_files,1121 .c_source_files = &c_source_files,
1121 .skip_linker_dependencies = true,1122 .skip_linker_dependencies = true,
1123 .environ_map = comp.environ_map,
1122 }) catch |err| switch (err) {1124 }) catch |err| switch (err) {
1123 error.CreateFail => {1125 error.CreateFail => {
1124 comp.lockAndSetMiscFailure(misc_task, "sub-compilation of {t} failed: {f}", .{ misc_task, sub_create_diag });1126 comp.lockAndSetMiscFailure(misc_task, "sub-compilation of {t} failed: {f}", .{ misc_task, sub_create_diag });
src/libs/glibc.zig+2
...@@ -680,6 +680,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye...@@ -680,6 +680,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye
680 .gpa = gpa,680 .gpa = gpa,
681 .io = io,681 .io = io,
682 .manifest_dir = try comp.dirs.global_cache.handle.createDirPathOpen(io, "h", .{}),682 .manifest_dir = try comp.dirs.global_cache.handle.createDirPathOpen(io, "h", .{}),
683 .cwd = comp.dirs.cwd,
683 };684 };
684 cache.addPrefix(.{ .path = null, .handle = Io.Dir.cwd() });685 cache.addPrefix(.{ .path = null, .handle = Io.Dir.cwd() });
685 cache.addPrefix(comp.dirs.zig_lib);686 cache.addPrefix(comp.dirs.zig_lib);
...@@ -1258,6 +1259,7 @@ fn buildSharedLib(...@@ -1258,6 +1259,7 @@ fn buildSharedLib(
1258 .soname = soname,1259 .soname = soname,
1259 .c_source_files = &c_source_files,1260 .c_source_files = &c_source_files,
1260 .skip_linker_dependencies = true,1261 .skip_linker_dependencies = true,
1262 .environ_map = comp.environ_map,
1261 }) catch |err| switch (err) {1263 }) catch |err| switch (err) {
1262 error.CreateFail => {1264 error.CreateFail => {
1263 comp.lockAndSetMiscFailure(misc_task, "sub-compilation of {t} failed: {f}", .{ misc_task, sub_create_diag });1265 comp.lockAndSetMiscFailure(misc_task, "sub-compilation of {t} failed: {f}", .{ misc_task, sub_create_diag });
src/libs/libcxx.zig+2
...@@ -275,6 +275,7 @@ pub fn buildLibCxx(comp: *Compilation, prog_node: std.Progress.Node) BuildError!...@@ -275,6 +275,7 @@ pub fn buildLibCxx(comp: *Compilation, prog_node: std.Progress.Node) BuildError!
275 .verbose_llvm_cpu_features = comp.verbose_llvm_cpu_features,275 .verbose_llvm_cpu_features = comp.verbose_llvm_cpu_features,
276 .clang_passthrough_mode = comp.clang_passthrough_mode,276 .clang_passthrough_mode = comp.clang_passthrough_mode,
277 .skip_linker_dependencies = true,277 .skip_linker_dependencies = true,
278 .environ_map = comp.environ_map,
278 }) catch |err| {279 }) catch |err| {
279 switch (err) {280 switch (err) {
280 else => comp.lockAndSetMiscFailure(misc_task, "unable to build libc++: create compilation failed: {t}", .{err}),281 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...@@ -468,6 +469,7 @@ pub fn buildLibCxxAbi(comp: *Compilation, prog_node: std.Progress.Node) BuildErr
468 .verbose_llvm_cpu_features = comp.verbose_llvm_cpu_features,469 .verbose_llvm_cpu_features = comp.verbose_llvm_cpu_features,
469 .clang_passthrough_mode = comp.clang_passthrough_mode,470 .clang_passthrough_mode = comp.clang_passthrough_mode,
470 .skip_linker_dependencies = true,471 .skip_linker_dependencies = true,
472 .environ_map = comp.environ_map,
471 }) catch |err| {473 }) catch |err| {
472 switch (err) {474 switch (err) {
473 else => comp.lockAndSetMiscFailure(misc_task, "unable to build libc++abi: create compilation failed: {t}", .{err}),475 else => comp.lockAndSetMiscFailure(misc_task, "unable to build libc++abi: create compilation failed: {t}", .{err}),
src/libs/libtsan.zig+1
...@@ -301,6 +301,7 @@ pub fn buildTsan(comp: *Compilation, prog_node: std.Progress.Node) BuildError!vo...@@ -301,6 +301,7 @@ pub fn buildTsan(comp: *Compilation, prog_node: std.Progress.Node) BuildError!vo
301 .linker_allow_shlib_undefined = linker_allow_shlib_undefined,301 .linker_allow_shlib_undefined = linker_allow_shlib_undefined,
302 .install_name = install_name,302 .install_name = install_name,
303 .headerpad_size = headerpad_size,303 .headerpad_size = headerpad_size,
304 .environ_map = comp.environ_map,
304 }) catch |err| {305 }) catch |err| {
305 switch (err) {306 switch (err) {
306 else => comp.lockAndSetMiscFailure(misc_task, "unable to build {t}: create compilation failed: {t}", .{ misc_task, err }),307 else => comp.lockAndSetMiscFailure(misc_task, "unable to build {t}: create compilation failed: {t}", .{ misc_task, err }),
src/libs/libunwind.zig+1
...@@ -166,6 +166,7 @@ pub fn buildStaticLib(comp: *Compilation, prog_node: std.Progress.Node) BuildErr...@@ -166,6 +166,7 @@ pub fn buildStaticLib(comp: *Compilation, prog_node: std.Progress.Node) BuildErr
166 .verbose_llvm_cpu_features = comp.verbose_llvm_cpu_features,166 .verbose_llvm_cpu_features = comp.verbose_llvm_cpu_features,
167 .clang_passthrough_mode = comp.clang_passthrough_mode,167 .clang_passthrough_mode = comp.clang_passthrough_mode,
168 .skip_linker_dependencies = true,168 .skip_linker_dependencies = true,
169 .environ_map = comp.environ_map,
169 }) catch |err| {170 }) catch |err| {
170 switch (err) {171 switch (err) {
171 else => comp.lockAndSetMiscFailure(misc_task, "unable to build {t}: create compilation failed: {t}", .{ misc_task, err }),172 else => comp.lockAndSetMiscFailure(misc_task, "unable to build {t}: create compilation failed: {t}", .{ misc_task, err }),
src/libs/mingw.zig+1
...@@ -259,6 +259,7 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void {...@@ -259,6 +259,7 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void {
259 .gpa = gpa,259 .gpa = gpa,
260 .io = io,260 .io = io,
261 .manifest_dir = try comp.dirs.global_cache.handle.createDirPathOpen(io, "h", .{}),261 .manifest_dir = try comp.dirs.global_cache.handle.createDirPathOpen(io, "h", .{}),
262 .cwd = comp.dirs.cwd,
262 };263 };
263 cache.addPrefix(.{ .path = null, .handle = Io.Dir.cwd() });264 cache.addPrefix(.{ .path = null, .handle = Io.Dir.cwd() });
264 cache.addPrefix(comp.dirs.zig_lib);265 cache.addPrefix(comp.dirs.zig_lib);
src/libs/musl.zig+1
...@@ -272,6 +272,7 @@ pub fn buildCrtFile(comp: *Compilation, in_crt_file: CrtFile, prog_node: std.Pro...@@ -272,6 +272,7 @@ pub fn buildCrtFile(comp: *Compilation, in_crt_file: CrtFile, prog_node: std.Pro
272 },272 },
273 .skip_linker_dependencies = true,273 .skip_linker_dependencies = true,
274 .soname = "libc.so",274 .soname = "libc.so",
275 .environ_map = comp.environ_map,
275 }) catch |err| switch (err) {276 }) catch |err| switch (err) {
276 error.CreateFail => {277 error.CreateFail => {
277 comp.lockAndSetMiscFailure(misc_task, "sub-compilation of {t} failed: {f}", .{ misc_task, sub_create_diag });278 comp.lockAndSetMiscFailure(misc_task, "sub-compilation of {t} failed: {f}", .{ misc_task, sub_create_diag });
src/libs/netbsd.zig+2
...@@ -386,6 +386,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye...@@ -386,6 +386,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye
386 .gpa = gpa,386 .gpa = gpa,
387 .io = io,387 .io = io,
388 .manifest_dir = try comp.dirs.global_cache.handle.createDirPathOpen(io, "h", .{}),388 .manifest_dir = try comp.dirs.global_cache.handle.createDirPathOpen(io, "h", .{}),
389 .cwd = comp.dirs.cwd,
389 };390 };
390 cache.addPrefix(.{ .path = null, .handle = Io.Dir.cwd() });391 cache.addPrefix(.{ .path = null, .handle = Io.Dir.cwd() });
391 cache.addPrefix(comp.dirs.zig_lib);392 cache.addPrefix(comp.dirs.zig_lib);
...@@ -761,6 +762,7 @@ fn buildSharedLib(...@@ -761,6 +762,7 @@ fn buildSharedLib(
761 .soname = soname,762 .soname = soname,
762 .c_source_files = &c_source_files,763 .c_source_files = &c_source_files,
763 .skip_linker_dependencies = true,764 .skip_linker_dependencies = true,
765 .environ_map = comp.environ_map,
764 }) catch |err| switch (err) {766 }) catch |err| switch (err) {
765 error.CreateFail => {767 error.CreateFail => {
766 comp.lockAndSetMiscFailure(misc_task, "sub-compilation of {t} failed: {f}", .{ misc_task, sub_create_diag });768 comp.lockAndSetMiscFailure(misc_task, "sub-compilation of {t} failed: {f}", .{ misc_task, sub_create_diag });
src/link/Lld.zig+29-26
...@@ -1604,19 +1604,24 @@ fn spawnLld(comp: *Compilation, arena: Allocator, argv: []const []const u8) !voi...@@ -1604,19 +1604,24 @@ fn spawnLld(comp: *Compilation, arena: Allocator, argv: []const []const u8) !voi
1604 var stderr: []u8 = &.{};1604 var stderr: []u8 = &.{};
1605 defer gpa.free(stderr);1605 defer gpa.free(stderr);
16061606
1607 var child = std.process.Child.init(argv, arena);1607 // TODO rework this awkward logic to call child.kill() in the failure case
1608 const term = (if (comp.clang_passthrough_mode) term: {1608 const term = (if (comp.clang_passthrough_mode) term: {
1609 child.stdin_behavior = .Inherit;1609 var child = std.process.spawn(io, .{
1610 child.stdout_behavior = .Inherit;1610 .argv = argv,
1611 child.stderr_behavior = .Inherit;1611 .stdin = .inherit,
1612 .stdout = .inherit,
1613 .stderr = .inherit,
1614 }) catch |err| break :term err;
16121615
1613 break :term child.spawnAndWait(io);1616 break :term child.wait(io);
1614 } else term: {1617 } else term: {
1615 child.stdin_behavior = .Ignore;1618 var child = std.process.spawn(io, .{
1616 child.stdout_behavior = .Ignore;1619 .argv = argv,
1617 child.stderr_behavior = .Pipe;1620 .stdin = .ignore,
1621 .stdout = .ignore,
1622 .stderr = .pipe,
1623 }) catch |err| break :term err;
16181624
1619 child.spawn(io) catch |err| break :term err;
1620 var stderr_reader = child.stderr.?.readerStreaming(io, &.{});1625 var stderr_reader = child.stderr.?.readerStreaming(io, &.{});
1621 stderr = try stderr_reader.interface.allocRemaining(gpa, .unlimited);1626 stderr = try stderr_reader.interface.allocRemaining(gpa, .unlimited);
1622 break :term child.wait(io);1627 break :term child.wait(io);
...@@ -1650,23 +1655,21 @@ fn spawnLld(comp: *Compilation, arena: Allocator, argv: []const []const u8) !voi...@@ -1650,23 +1655,21 @@ fn spawnLld(comp: *Compilation, arena: Allocator, argv: []const []const u8) !voi
1650 try rsp_writer.flush();1655 try rsp_writer.flush();
1651 }1656 }
16521657
1653 var rsp_child = std.process.Child.init(&.{ argv[0], argv[1], try std.fmt.allocPrint(1658 var rsp_child = std.process.spawn(io, .{
1654 arena,1659 .argv = &.{
1655 "@{s}",1660 argv[0],
1656 .{try comp.dirs.local_cache.join(arena, &.{rsp_path})},1661 argv[1],
1657 ) }, arena);1662 try std.fmt.allocPrint(arena, "@{s}", .{
1663 try comp.dirs.local_cache.join(arena, &.{rsp_path}),
1664 }),
1665 },
1666 .stdin = if (comp.clang_passthrough_mode) .inherit else .ignore,
1667 .stdout = if (comp.clang_passthrough_mode) .inherit else .ignore,
1668 .stderr = if (comp.clang_passthrough_mode) .inherit else .pipe,
1669 }) catch |err| break :err err;
1658 if (comp.clang_passthrough_mode) {1670 if (comp.clang_passthrough_mode) {
1659 rsp_child.stdin_behavior = .Inherit;1671 break :term rsp_child.wait(io) catch |err| break :err err;
1660 rsp_child.stdout_behavior = .Inherit;
1661 rsp_child.stderr_behavior = .Inherit;
1662
1663 break :term rsp_child.spawnAndWait(io) catch |err| break :err err;
1664 } else {1672 } else {
1665 rsp_child.stdin_behavior = .Ignore;
1666 rsp_child.stdout_behavior = .Ignore;
1667 rsp_child.stderr_behavior = .Pipe;
1668
1669 rsp_child.spawn(io) catch |err| break :err err;
1670 var stderr_reader = rsp_child.stderr.?.readerStreaming(io, &.{});1673 var stderr_reader = rsp_child.stderr.?.readerStreaming(io, &.{});
1671 stderr = try stderr_reader.interface.allocRemaining(gpa, .unlimited);1674 stderr = try stderr_reader.interface.allocRemaining(gpa, .unlimited);
1672 break :term rsp_child.wait(io) catch |err| break :err err;1675 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...@@ -1674,13 +1677,13 @@ fn spawnLld(comp: *Compilation, arena: Allocator, argv: []const []const u8) !voi
1674 },1677 },
1675 else => first_err,1678 else => first_err,
1676 };1679 };
1677 log.err("unable to spawn LLD {s}: {s}", .{ argv[0], @errorName(err) });1680 log.err("unable to spawn LLD {s}: {t}", .{ argv[0], err });
1678 return error.UnableToSpawnSelf;1681 return error.UnableToSpawnSelf;
1679 };1682 };
16801683
1681 const diags = &comp.link_diags;1684 const diags = &comp.link_diags;
1682 switch (term) {1685 switch (term) {
1683 .Exited => |code| if (code != 0) {1686 .exited => |code| if (code != 0) {
1684 if (comp.clang_passthrough_mode) std.process.exit(code);1687 if (comp.clang_passthrough_mode) std.process.exit(code);
1685 diags.lockAndParseLldStderr(argv[1], stderr);1688 diags.lockAndParseLldStderr(argv[1], stderr);
1686 return error.LinkFailure;1689 return error.LinkFailure;
src/main.zig+281-268
...@@ -42,7 +42,6 @@ test {...@@ -42,7 +42,6 @@ test {
42const thread_stack_size = 60 << 20;42const thread_stack_size = 60 << 20;
4343
44pub const std_options: std.Options = .{44pub const std_options: std.Options = .{
45 .wasiCwd = wasi_cwd,
46 .logFn = log,45 .logFn = log,
4746
48 .log_level = switch (builtin.mode) {47 .log_level = switch (builtin.mode) {
...@@ -51,16 +50,17 @@ pub const std_options: std.Options = .{...@@ -51,16 +50,17 @@ pub const std_options: std.Options = .{
51 .ReleaseSmall => .err,50 .ReleaseSmall => .err,
52 },51 },
53};52};
53pub const std_options_cwd = if (native_os == .wasi) wasi_cwd else null;
5454
55pub const panic = crash_report.panic;55pub const panic = crash_report.panic;
56pub const debug = crash_report.debug;56pub const debug = crash_report.debug;
5757
58var wasi_preopens: fs.wasi.Preopens = undefined;58var wasi_preopens: fs.wasi.Preopens = undefined;
59pub fn wasi_cwd() std.os.wasi.fd_t {59pub fn wasi_cwd() Io.Dir {
60 // Expect the first preopen to be current working directory.60 // Expect the first preopen to be current working directory.
61 const cwd_fd: std.posix.fd_t = 3;61 const cwd_fd: std.posix.fd_t = 3;
62 assert(mem.eql(u8, wasi_preopens.names[cwd_fd], "."));62 assert(mem.eql(u8, wasi_preopens.names[cwd_fd], "."));
63 return cwd_fd;63 return .{ .handle = cwd_fd };
64}64}
6565
66const fatal = std.process.fatal;66const fatal = std.process.fatal;
...@@ -168,7 +168,7 @@ const use_debug_allocator = build_options.debug_gpa or...@@ -168,7 +168,7 @@ const use_debug_allocator = build_options.debug_gpa or
168 .ReleaseFast, .ReleaseSmall => false,168 .ReleaseFast, .ReleaseSmall => false,
169 });169 });
170170
171pub fn main() anyerror!void {171pub fn main(init: std.process.Init.Minimal) anyerror!void {
172 const gpa = gpa: {172 const gpa = gpa: {
173 if (use_debug_allocator) break :gpa debug_allocator.allocator();173 if (use_debug_allocator) break :gpa debug_allocator.allocator();
174 if (native_os == .wasi) break :gpa std.heap.wasm_allocator;174 if (native_os == .wasi) break :gpa std.heap.wasm_allocator;
...@@ -182,34 +182,48 @@ pub fn main() anyerror!void {...@@ -182,34 +182,48 @@ pub fn main() anyerror!void {
182 defer arena_instance.deinit();182 defer arena_instance.deinit();
183 const arena = arena_instance.allocator();183 const arena = arena_instance.allocator();
184184
185 const args = try process.argsAlloc(arena);185 const args = try init.args.toSlice(arena);
186186
187 if (args.len > 0) crash_report.zig_argv0 = args[0];187 if (args.len > 0) crash_report.zig_argv0 = args[0];
188188
189 if (args.len <= 1) {
190 std.log.info("{s}", .{usage});
191 fatal("expected command argument", .{});
192 }
193
194 var environ_map = init.environ.createMap(arena) catch |err| fatal("failed to parse environment: {t}", .{err});
195
196 Compilation.setMainThread();
197
198 var threaded: Io.Threaded = .init(gpa, .{
199 .argv0 = .init(init.args),
200 .environ = init.environ,
201 });
202 defer threaded.deinit();
203 threaded_impl_ptr = &threaded;
204 threaded.stack_size = thread_stack_size;
205 const io = threaded.io();
206
189 if (tracy.enable_allocation) {207 if (tracy.enable_allocation) {
190 var gpa_tracy = tracy.tracyAllocator(gpa);208 var gpa_tracy = tracy.tracyAllocator(gpa);
191 return mainArgs(gpa_tracy.allocator(), arena, args);209 return mainArgs(gpa_tracy.allocator(), arena, io, args, &environ_map);
192 }210 }
193211
194 if (native_os == .wasi) {212 if (native_os == .wasi) {
195 wasi_preopens = try fs.wasi.preopensAlloc(arena);213 wasi_preopens = try fs.wasi.preopensAlloc(arena);
196 }214 }
197215
198 return mainArgs(gpa, arena, args);216 return mainArgs(gpa, arena, io, args, &environ_map);
199}217}
200218
201fn mainArgs(gpa: Allocator, arena: Allocator, args: []const [:0]const u8) !void {219fn mainArgs(
202 const tr = tracy.trace(@src());220 gpa: Allocator,
203 defer tr.end();221 arena: Allocator,
204222 io: Io,
205 Compilation.setMainThread();223 args: []const [:0]const u8,
206224 environ_map: *process.Environ.Map,
207 if (args.len <= 1) {225) !void {
208 std.log.info("{s}", .{usage});226 if (process.can_replace and EnvVar.ZIG_IS_DETECTING_LIBC_PATHS.isSet(environ_map)) {
209 fatal("expected command argument", .{});
210 }
211
212 if (process.can_execv and std.posix.getenvZ("ZIG_IS_DETECTING_LIBC_PATHS") != null) {
213 dev.check(.cc_command);227 dev.check(.cc_command);
214 // In this case we have accidentally invoked ourselves as "the system C compiler"228 // In this case we have accidentally invoked ourselves as "the system C compiler"
215 // to figure out where libc is installed. This is essentially infinite recursion229 // 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...@@ -217,58 +231,51 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const [:0]const u8) !void
217 // Here we ignore the CC environment variable and exec `cc` as a child process.231 // Here we ignore the CC environment variable and exec `cc` as a child process.
218 // However it's possible Zig is installed as *that* C compiler as well, which is232 // However it's possible Zig is installed as *that* C compiler as well, which is
219 // why we have this additional environment variable here to check.233 // why we have this additional environment variable here to check.
220 var env_map = try process.getEnvMap(arena);234
221235 const inf_loop_env_key: EnvVar = .ZIG_IS_TRYING_TO_NOT_CALL_ITSELF;
222 const inf_loop_env_key = "ZIG_IS_TRYING_TO_NOT_CALL_ITSELF";236 if (inf_loop_env_key.isSet(environ_map)) {
223 if (env_map.get(inf_loop_env_key) != null) {237 fatal("{s}", .{
224 fatal("The compilation links against libc, but Zig is unable to provide a libc " ++238 "The compilation links against libc, but Zig is unable to provide a libc " ++
225 "for this operating system, and no --libc " ++239 "for this operating system, and no --libc " ++
226 "parameter was provided, so Zig attempted to invoke the system C compiler " ++240 "parameter was provided, so Zig attempted to invoke the system C compiler " ++
227 "in order to determine where libc is installed. However the system C " ++241 "in order to determine where libc is installed. However the system C " ++
228 "compiler is `zig cc`, so no libc installation was found.", .{});242 "compiler is `zig cc`, so no libc installation was found.",
243 });
229 }244 }
230 try env_map.put(inf_loop_env_key, "1");245 try environ_map.put(@tagName(inf_loop_env_key), "1");
231246
232 // Some programs such as CMake will strip the `cc` and subsequent args from the247 // Some programs such as CMake will strip the `cc` and subsequent args from the
233 // CC environment variable. We detect and support this scenario here because of248 // CC environment variable. We detect and support this scenario here because of
234 // the ZIG_IS_DETECTING_LIBC_PATHS environment variable.249 // the ZIG_IS_DETECTING_LIBC_PATHS environment variable.
235 if (mem.eql(u8, args[1], "cc")) {250 if (mem.eql(u8, args[1], "cc")) {
236 return process.execve(arena, args[1..], &env_map);251 return process.replace(io, .{ .argv = args[1..], .environ_map = environ_map });
237 } else {252 } else {
238 const modified_args = try arena.dupe([]const u8, args);253 const modified_args = try arena.dupe([]const u8, args);
239 modified_args[0] = "cc";254 modified_args[0] = "cc";
240 return process.execve(arena, modified_args, &env_map);255 return process.replace(io, .{ .argv = modified_args, .environ_map = environ_map });
241 }256 }
242 }257 }
243258
244 var threaded: Io.Threaded = .init(gpa, .{
245 .argv0 = if (@hasField(Io.Threaded.Argv0, "value")) .{ .value = args[0] } else .{},
246 });
247 defer threaded.deinit();
248 threaded_impl_ptr = &threaded;
249 threaded.stack_size = thread_stack_size;
250 const io = threaded.io();
251
252 const cmd = args[1];259 const cmd = args[1];
253 const cmd_args = args[2..];260 const cmd_args = args[2..];
254 if (mem.eql(u8, cmd, "build-exe")) {261 if (mem.eql(u8, cmd, "build-exe")) {
255 dev.check(.build_exe_command);262 dev.check(.build_exe_command);
256 return buildOutputType(gpa, arena, io, args, .{ .build = .Exe });263 return buildOutputType(gpa, arena, io, args, .{ .build = .Exe }, environ_map);
257 } else if (mem.eql(u8, cmd, "build-lib")) {264 } else if (mem.eql(u8, cmd, "build-lib")) {
258 dev.check(.build_lib_command);265 dev.check(.build_lib_command);
259 return buildOutputType(gpa, arena, io, args, .{ .build = .Lib });266 return buildOutputType(gpa, arena, io, args, .{ .build = .Lib }, environ_map);
260 } else if (mem.eql(u8, cmd, "build-obj")) {267 } else if (mem.eql(u8, cmd, "build-obj")) {
261 dev.check(.build_obj_command);268 dev.check(.build_obj_command);
262 return buildOutputType(gpa, arena, io, args, .{ .build = .Obj });269 return buildOutputType(gpa, arena, io, args, .{ .build = .Obj }, environ_map);
263 } else if (mem.eql(u8, cmd, "test")) {270 } else if (mem.eql(u8, cmd, "test")) {
264 dev.check(.test_command);271 dev.check(.test_command);
265 return buildOutputType(gpa, arena, io, args, .zig_test);272 return buildOutputType(gpa, arena, io, args, .zig_test, environ_map);
266 } else if (mem.eql(u8, cmd, "test-obj")) {273 } else if (mem.eql(u8, cmd, "test-obj")) {
267 dev.check(.test_command);274 dev.check(.test_command);
268 return buildOutputType(gpa, arena, io, args, .zig_test_obj);275 return buildOutputType(gpa, arena, io, args, .zig_test_obj, environ_map);
269 } else if (mem.eql(u8, cmd, "run")) {276 } else if (mem.eql(u8, cmd, "run")) {
270 dev.check(.run_command);277 dev.check(.run_command);
271 return buildOutputType(gpa, arena, io, args, .run);278 return buildOutputType(gpa, arena, io, args, .run, environ_map);
272 } else if (mem.eql(u8, cmd, "dlltool") or279 } else if (mem.eql(u8, cmd, "dlltool") or
273 mem.eql(u8, cmd, "ranlib") or280 mem.eql(u8, cmd, "ranlib") or
274 mem.eql(u8, cmd, "lib") or281 mem.eql(u8, cmd, "lib") or
...@@ -278,7 +285,7 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const [:0]const u8) !void...@@ -278,7 +285,7 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const [:0]const u8) !void
278 return process.exit(try llvmArMain(arena, args));285 return process.exit(try llvmArMain(arena, args));
279 } else if (mem.eql(u8, cmd, "build")) {286 } else if (mem.eql(u8, cmd, "build")) {
280 dev.check(.build_command);287 dev.check(.build_command);
281 return cmdBuild(gpa, arena, io, cmd_args);288 return cmdBuild(gpa, arena, io, cmd_args, environ_map);
282 } else if (mem.eql(u8, cmd, "clang") or289 } else if (mem.eql(u8, cmd, "clang") or
283 mem.eql(u8, cmd, "-cc1") or mem.eql(u8, cmd, "-cc1as"))290 mem.eql(u8, cmd, "-cc1") or mem.eql(u8, cmd, "-cc1as"))
284 {291 {
...@@ -292,16 +299,16 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const [:0]const u8) !void...@@ -292,16 +299,16 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const [:0]const u8) !void
292 return process.exit(try lldMain(arena, args, true));299 return process.exit(try lldMain(arena, args, true));
293 } else if (mem.eql(u8, cmd, "cc")) {300 } else if (mem.eql(u8, cmd, "cc")) {
294 dev.check(.cc_command);301 dev.check(.cc_command);
295 return buildOutputType(gpa, arena, io, args, .cc);302 return buildOutputType(gpa, arena, io, args, .cc, environ_map);
296 } else if (mem.eql(u8, cmd, "c++")) {303 } else if (mem.eql(u8, cmd, "c++")) {
297 dev.check(.cc_command);304 dev.check(.cc_command);
298 return buildOutputType(gpa, arena, io, args, .cpp);305 return buildOutputType(gpa, arena, io, args, .cpp, environ_map);
299 } else if (mem.eql(u8, cmd, "translate-c")) {306 } else if (mem.eql(u8, cmd, "translate-c")) {
300 dev.check(.translate_c_command);307 dev.check(.translate_c_command);
301 return buildOutputType(gpa, arena, io, args, .translate_c);308 return buildOutputType(gpa, arena, io, args, .translate_c, environ_map);
302 } else if (mem.eql(u8, cmd, "rc")) {309 } else if (mem.eql(u8, cmd, "rc")) {
303 const use_server = cmd_args.len > 0 and std.mem.eql(u8, cmd_args[0], "--zig-integration");310 const use_server = cmd_args.len > 0 and std.mem.eql(u8, cmd_args[0], "--zig-integration");
304 return jitCmd(gpa, arena, io, cmd_args, .{311 return jitCmd(gpa, arena, io, cmd_args, environ_map, .{
305 .cmd_name = "resinator",312 .cmd_name = "resinator",
306 .root_src_path = "resinator/main.zig",313 .root_src_path = "resinator/main.zig",
307 .depend_on_aro = true,314 .depend_on_aro = true,
...@@ -312,20 +319,20 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const [:0]const u8) !void...@@ -312,20 +319,20 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const [:0]const u8) !void
312 dev.check(.fmt_command);319 dev.check(.fmt_command);
313 return @import("fmt.zig").run(gpa, arena, io, cmd_args);320 return @import("fmt.zig").run(gpa, arena, io, cmd_args);
314 } else if (mem.eql(u8, cmd, "objcopy")) {321 } else if (mem.eql(u8, cmd, "objcopy")) {
315 return jitCmd(gpa, arena, io, cmd_args, .{322 return jitCmd(gpa, arena, io, cmd_args, environ_map, .{
316 .cmd_name = "objcopy",323 .cmd_name = "objcopy",
317 .root_src_path = "objcopy.zig",324 .root_src_path = "objcopy.zig",
318 });325 });
319 } else if (mem.eql(u8, cmd, "fetch")) {326 } else if (mem.eql(u8, cmd, "fetch")) {
320 return cmdFetch(gpa, arena, io, cmd_args);327 return cmdFetch(gpa, arena, io, cmd_args, environ_map);
321 } else if (mem.eql(u8, cmd, "libc")) {328 } else if (mem.eql(u8, cmd, "libc")) {
322 return jitCmd(gpa, arena, io, cmd_args, .{329 return jitCmd(gpa, arena, io, cmd_args, environ_map, .{
323 .cmd_name = "libc",330 .cmd_name = "libc",
324 .root_src_path = "libc.zig",331 .root_src_path = "libc.zig",
325 .prepend_zig_lib_dir_path = true,332 .prepend_zig_lib_dir_path = true,
326 });333 });
327 } else if (mem.eql(u8, cmd, "std")) {334 } else if (mem.eql(u8, cmd, "std")) {
328 return jitCmd(gpa, arena, io, cmd_args, .{335 return jitCmd(gpa, arena, io, cmd_args, environ_map, .{
329 .cmd_name = "std",336 .cmd_name = "std",
330 .root_src_path = "std-docs.zig",337 .root_src_path = "std-docs.zig",
331 .prepend_zig_lib_dir_path = true,338 .prepend_zig_lib_dir_path = true,
...@@ -355,10 +362,11 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const [:0]const u8) !void...@@ -355,10 +362,11 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const [:0]const u8) !void
355 args,362 args,
356 if (native_os == .wasi) wasi_preopens,363 if (native_os == .wasi) wasi_preopens,
357 &host,364 &host,
365 environ_map,
358 );366 );
359 return stdout_writer.interface.flush();367 return stdout_writer.interface.flush();
360 } else if (mem.eql(u8, cmd, "reduce")) {368 } else if (mem.eql(u8, cmd, "reduce")) {
361 return jitCmd(gpa, arena, io, cmd_args, .{369 return jitCmd(gpa, arena, io, cmd_args, environ_map, .{
362 .cmd_name = "reduce",370 .cmd_name = "reduce",
363 .root_src_path = "reduce.zig",371 .root_src_path = "reduce.zig",
364 });372 });
...@@ -803,6 +811,7 @@ fn buildOutputType(...@@ -803,6 +811,7 @@ fn buildOutputType(
803 io: Io,811 io: Io,
804 all_args: []const []const u8,812 all_args: []const []const u8,
805 arg_mode: ArgMode,813 arg_mode: ArgMode,
814 environ_map: *process.Environ.Map,
806) !void {815) !void {
807 var provided_name: ?[]const u8 = null;816 var provided_name: ?[]const u8 = null;
808 var root_src_file: ?[]const u8 = null;817 var root_src_file: ?[]const u8 = null;
...@@ -815,9 +824,9 @@ fn buildOutputType(...@@ -815,9 +824,9 @@ fn buildOutputType(
815 var debug_compile_errors = false;824 var debug_compile_errors = false;
816 var debug_incremental = false;825 var debug_incremental = false;
817 var verbose_link = (native_os != .wasi or builtin.link_libc) and826 var verbose_link = (native_os != .wasi or builtin.link_libc) and
818 EnvVar.ZIG_VERBOSE_LINK.isSet();827 EnvVar.ZIG_VERBOSE_LINK.isSet(environ_map);
819 var verbose_cc = (native_os != .wasi or builtin.link_libc) and828 var verbose_cc = (native_os != .wasi or builtin.link_libc) and
820 EnvVar.ZIG_VERBOSE_CC.isSet();829 EnvVar.ZIG_VERBOSE_CC.isSet(environ_map);
821 var verbose_air = false;830 var verbose_air = false;
822 var verbose_intern_pool = false;831 var verbose_intern_pool = false;
823 var verbose_generic_instances = false;832 var verbose_generic_instances = false;
...@@ -889,9 +898,9 @@ fn buildOutputType(...@@ -889,9 +898,9 @@ fn buildOutputType(
889 var runtime_args_start: ?usize = null;898 var runtime_args_start: ?usize = null;
890 var test_filters: std.ArrayList([]const u8) = .empty;899 var test_filters: std.ArrayList([]const u8) = .empty;
891 var test_runner_path: ?[]const u8 = null;900 var test_runner_path: ?[]const u8 = null;
892 var override_local_cache_dir: ?[]const u8 = try EnvVar.ZIG_LOCAL_CACHE_DIR.get(arena);901 var override_local_cache_dir: ?[]const u8 = EnvVar.ZIG_LOCAL_CACHE_DIR.get(environ_map);
893 var override_global_cache_dir: ?[]const u8 = try EnvVar.ZIG_GLOBAL_CACHE_DIR.get(arena);902 var override_global_cache_dir: ?[]const u8 = EnvVar.ZIG_GLOBAL_CACHE_DIR.get(environ_map);
894 var override_lib_dir: ?[]const u8 = try EnvVar.ZIG_LIB_DIR.get(arena);903 var override_lib_dir: ?[]const u8 = EnvVar.ZIG_LIB_DIR.get(environ_map);
895 var clang_preprocessor_mode: Compilation.ClangPreprocessorMode = .no;904 var clang_preprocessor_mode: Compilation.ClangPreprocessorMode = .no;
896 var subsystem: ?std.zig.Subsystem = null;905 var subsystem: ?std.zig.Subsystem = null;
897 var major_subsystem_version: ?u16 = null;906 var major_subsystem_version: ?u16 = null;
...@@ -988,7 +997,7 @@ fn buildOutputType(...@@ -988,7 +997,7 @@ fn buildOutputType(
988 .framework_dirs = .{},997 .framework_dirs = .{},
989 .rpath_list = .{},998 .rpath_list = .{},
990 .each_lib_rpath = null,999 .each_lib_rpath = null,
991 .libc_paths_file = try EnvVar.ZIG_LIBC.get(arena),1000 .libc_paths_file = EnvVar.ZIG_LIBC.get(environ_map),
992 .native_system_include_paths = &.{},1001 .native_system_include_paths = &.{},
993 };1002 };
994 defer create_module.link_inputs.deinit(gpa);1003 defer create_module.link_inputs.deinit(gpa);
...@@ -997,9 +1006,9 @@ fn buildOutputType(...@@ -997,9 +1006,9 @@ fn buildOutputType(
997 // if set, default the color setting to .off or .on, respectively1006 // if set, default the color setting to .off or .on, respectively
998 // explicit --color arguments will still override this setting.1007 // explicit --color arguments will still override this setting.
999 // Disable color on WASI per https://github.com/WebAssembly/WASI/issues/1621008 // Disable color on WASI per https://github.com/WebAssembly/WASI/issues/162
1000 var color: Color = if (native_os == .wasi or EnvVar.NO_COLOR.isSet())1009 var color: Color = if (native_os == .wasi or EnvVar.NO_COLOR.isSet(environ_map))
1001 .off1010 .off
1002 else if (EnvVar.CLICOLOR_FORCE.isSet())1011 else if (EnvVar.CLICOLOR_FORCE.isSet(environ_map))
1003 .on1012 .on
1004 else1013 else
1005 .auto;1014 .auto;
...@@ -3097,6 +3106,7 @@ fn buildOutputType(...@@ -3097,6 +3106,7 @@ fn buildOutputType(
3097 },3106 },
3098 if (native_os == .wasi) wasi_preopens,3107 if (native_os == .wasi) wasi_preopens,
3099 self_exe_path,3108 self_exe_path,
3109 environ_map,
3100 );3110 );
3101 defer dirs.deinit(io);3111 defer dirs.deinit(io);
31023112
...@@ -3108,7 +3118,7 @@ fn buildOutputType(...@@ -3108,7 +3118,7 @@ fn buildOutputType(
3108 create_module.opts.emit_bin = emit_bin != .no;3118 create_module.opts.emit_bin = emit_bin != .no;
3109 create_module.opts.any_c_source_files = create_module.c_source_files.items.len != 0;3119 create_module.opts.any_c_source_files = create_module.c_source_files.items.len != 0;
31103120
3111 const main_mod = try createModule(gpa, arena, io, &create_module, 0, null, color);3121 const main_mod = try createModule(gpa, arena, io, &create_module, 0, null, color, environ_map);
3112 for (create_module.modules.keys(), create_module.modules.values()) |key, cli_mod| {3122 for (create_module.modules.keys(), create_module.modules.values()) |key, cli_mod| {
3113 if (cli_mod.resolved == null)3123 if (cli_mod.resolved == null)
3114 fatal("module '{s}' declared but not used", .{key});3124 fatal("module '{s}' declared but not used", .{key});
...@@ -3585,6 +3595,7 @@ fn buildOutputType(...@@ -3585,6 +3595,7 @@ fn buildOutputType(
3585 .global_cc_argv = try cc_argv.toOwnedSlice(arena),3595 .global_cc_argv = try cc_argv.toOwnedSlice(arena),
3586 .file_system_inputs = &file_system_inputs,3596 .file_system_inputs = &file_system_inputs,
3587 .debug_compiler_runtime_libs = debug_compiler_runtime_libs,3597 .debug_compiler_runtime_libs = debug_compiler_runtime_libs,
3598 .environ_map = environ_map,
3588 }) catch |err| switch (err) {3599 }) catch |err| switch (err) {
3589 error.CreateFail => switch (create_diag) {3600 error.CreateFail => switch (create_diag) {
3590 .cross_libc_unavailable => {3601 .cross_libc_unavailable => {
...@@ -3648,6 +3659,7 @@ fn buildOutputType(...@@ -3648,6 +3659,7 @@ fn buildOutputType(
3648 arg_mode,3659 arg_mode,
3649 all_args,3660 all_args,
3650 runtime_args_start,3661 runtime_args_start,
3662 environ_map,
3651 );3663 );
3652 return cleanExit(io);3664 return cleanExit(io);
3653 },3665 },
...@@ -3674,6 +3686,7 @@ fn buildOutputType(...@@ -3674,6 +3686,7 @@ fn buildOutputType(
3674 arg_mode,3686 arg_mode,
3675 all_args,3687 all_args,
3676 runtime_args_start,3688 runtime_args_start,
3689 environ_map,
3677 );3690 );
3678 return cleanExit(io);3691 return cleanExit(io);
3679 },3692 },
...@@ -3686,7 +3699,7 @@ fn buildOutputType(...@@ -3686,7 +3699,7 @@ fn buildOutputType(
3686 defer root_prog_node.end();3699 defer root_prog_node.end();
36873700
3688 if (arg_mode == .translate_c) {3701 if (arg_mode == .translate_c) {
3689 return cmdTranslateC(comp, arena, null, null, root_prog_node);3702 return cmdTranslateC(comp, arena, null, null, root_prog_node, environ_map);
3690 }3703 }
36913704
3692 updateModule(comp, color, root_prog_node) catch |err| switch (err) {3705 updateModule(comp, color, root_prog_node) catch |err| switch (err) {
...@@ -3754,6 +3767,7 @@ fn buildOutputType(...@@ -3754,6 +3767,7 @@ fn buildOutputType(
3754 all_args,3767 all_args,
3755 runtime_args_start,3768 runtime_args_start,
3756 create_module.resolved_options.link_libc,3769 create_module.resolved_options.link_libc,
3770 environ_map,
3757 );3771 );
3758 }3772 }
37593773
...@@ -3809,6 +3823,7 @@ fn createModule(...@@ -3809,6 +3823,7 @@ fn createModule(
3809 index: usize,3823 index: usize,
3810 parent: ?*Package.Module,3824 parent: ?*Package.Module,
3811 color: std.zig.Color,3825 color: std.zig.Color,
3826 environ_map: *process.Environ.Map,
3812) Allocator.Error!*Package.Module {3827) Allocator.Error!*Package.Module {
3813 const cli_mod = &create_module.modules.values()[index];3828 const cli_mod = &create_module.modules.values()[index];
3814 if (cli_mod.resolved) |m| return m;3829 if (cli_mod.resolved) |m| return m;
...@@ -3988,7 +4003,7 @@ fn createModule(...@@ -3988,7 +4003,7 @@ fn createModule(
3988 resolved_target.is_native_os and resolved_target.is_native_abi and4003 resolved_target.is_native_os and resolved_target.is_native_abi and
3989 create_module.want_native_include_dirs)4004 create_module.want_native_include_dirs)
3990 {4005 {
3991 var paths = std.zig.system.NativePaths.detect(arena, io, target) catch |err|4006 var paths = std.zig.system.NativePaths.detect(arena, io, target, environ_map) catch |err|
3992 fatal("unable to detect native system paths: {t}", .{err});4007 fatal("unable to detect native system paths: {t}", .{err});
3993 for (paths.warnings.items) |warning| {4008 for (paths.warnings.items) |warning| {
3994 warn("{s}", .{warning});4009 warn("{s}", .{warning});
...@@ -4015,6 +4030,7 @@ fn createModule(...@@ -4015,6 +4030,7 @@ fn createModule(
4015 create_module.libc_installation = LibCInstallation.findNative(arena, io, .{4030 create_module.libc_installation = LibCInstallation.findNative(arena, io, .{
4016 .verbose = true,4031 .verbose = true,
4017 .target = target,4032 .target = target,
4033 .environ_map = environ_map,
4018 }) catch |err| {4034 }) catch |err| {
4019 fatal("unable to find native libc installation: {t}", .{err});4035 fatal("unable to find native libc installation: {t}", .{err});
4020 };4036 };
...@@ -4119,7 +4135,7 @@ fn createModule(...@@ -4119,7 +4135,7 @@ fn createModule(
4119 for (cli_mod.deps) |dep| {4135 for (cli_mod.deps) |dep| {
4120 const dep_index = create_module.modules.getIndex(dep.value) orelse4136 const dep_index = create_module.modules.getIndex(dep.value) orelse
4121 fatal("module '{s}' depends on non-existent module '{s}'", .{ name, dep.key });4137 fatal("module '{s}' depends on non-existent module '{s}'", .{ name, dep.key });
4122 const dep_mod = try createModule(gpa, arena, io, create_module, dep_index, mod, color);4138 const dep_mod = try createModule(gpa, arena, io, create_module, dep_index, mod, color, environ_map);
4123 try mod.deps.put(arena, dep.key, dep_mod);4139 try mod.deps.put(arena, dep.key, dep_mod);
4124 }4140 }
41254141
...@@ -4128,9 +4144,7 @@ fn createModule(...@@ -4128,9 +4144,7 @@ fn createModule(
41284144
4129fn saveState(comp: *Compilation, incremental: bool) void {4145fn saveState(comp: *Compilation, incremental: bool) void {
4130 if (incremental) {4146 if (incremental) {
4131 comp.saveState() catch |err| {4147 comp.saveState() catch |err| warn("unable to save incremental compilation state: {t}", .{err});
4132 warn("unable to save incremental compilation state: {s}", .{@errorName(err)});
4133 };
4134 }4148 }
4135}4149}
41364150
...@@ -4143,6 +4157,7 @@ fn serve(...@@ -4143,6 +4157,7 @@ fn serve(
4143 arg_mode: ArgMode,4157 arg_mode: ArgMode,
4144 all_args: []const []const u8,4158 all_args: []const []const u8,
4145 runtime_args_start: ?usize,4159 runtime_args_start: ?usize,
4160 environ_map: *process.Environ.Map,
4146) !void {4161) !void {
4147 const gpa = comp.gpa;4162 const gpa = comp.gpa;
4148 const io = comp.io;4163 const io = comp.io;
...@@ -4190,7 +4205,7 @@ fn serve(...@@ -4190,7 +4205,7 @@ fn serve(
4190 defer arena_instance.deinit();4205 defer arena_instance.deinit();
4191 const arena = arena_instance.allocator();4206 const arena = arena_instance.allocator();
4192 var output: Compilation.CImportResult = undefined;4207 var output: Compilation.CImportResult = undefined;
4193 try cmdTranslateC(comp, arena, &output, file_system_inputs, main_progress_node);4208 try cmdTranslateC(comp, arena, &output, file_system_inputs, main_progress_node, environ_map);
4194 defer output.deinit(gpa);4209 defer output.deinit(gpa);
41954210
4196 if (file_system_inputs.items.len != 0) {4211 if (file_system_inputs.items.len != 0) {
...@@ -4390,6 +4405,7 @@ fn runOrTest(...@@ -4390,6 +4405,7 @@ fn runOrTest(
4390 all_args: []const []const u8,4405 all_args: []const []const u8,
4391 runtime_args_start: ?usize,4406 runtime_args_start: ?usize,
4392 link_libc: bool,4407 link_libc: bool,
4408 environ_map: *process.Environ.Map,
4393) !void {4409) !void {
4394 const raw_emit_bin = comp.emit_bin orelse return;4410 const raw_emit_bin = comp.emit_bin orelse return;
4395 const exe_path = switch (comp.cache_use) {4411 const exe_path = switch (comp.cache_use) {
...@@ -4426,77 +4442,90 @@ fn runOrTest(...@@ -4426,77 +4442,90 @@ fn runOrTest(
4426 if (runtime_args_start) |i| {4442 if (runtime_args_start) |i| {
4427 try argv.appendSlice(all_args[i..]);4443 try argv.appendSlice(all_args[i..]);
4428 }4444 }
4429 var env_map = try process.getEnvMap(arena);4445 try environ_map.put("ZIG_EXE", self_exe_path);
4430 try env_map.put("ZIG_EXE", self_exe_path);
44314446
4432 // We do not execve for tests because if the test fails we want to print4447 // We do not execve for tests because if the test fails we want to print
4433 // the error message and invocation below.4448 // the error message and invocation below.
4434 if (process.can_execv and arg_mode == .run) {4449 if (process.can_replace and arg_mode == .run) {
4435 // execv releases the locks; no need to destroy the Compilation here.4450 // process replacement releases the locks; no need to destroy the Compilation here.
4436 _ = try io.lockStderr(&.{}, .no_color);4451 _ = try io.lockStderr(&.{}, .no_color);
4437 const err = process.execve(gpa, argv.items, &env_map);4452 const err = process.replace(io, .{ .argv = argv.items, .environ_map = environ_map });
4438 io.unlockStderr();4453 io.unlockStderr();
4439 try warnAboutForeignBinaries(io, arena, arg_mode, target, link_libc);4454 try warnAboutForeignBinaries(io, arena, arg_mode, target, link_libc);
4440 const cmd = try std.mem.join(arena, " ", argv.items);4455 const cmd = try std.mem.join(arena, " ", argv.items);
4441 fatal("the following command failed to execve with '{t}':\n{s}", .{ err, cmd });4456 fatal("the following command failed to execve with '{t}':\n{s}", .{ err, cmd });
4442 } else if (process.can_spawn) {4457 } else if (!process.can_spawn) {
4443 var child = std.process.Child.init(argv.items, gpa);4458 const cmd = try std.mem.join(arena, " ", argv.items);
4444 child.env_map = &env_map;4459 fatal("the following command cannot be executed ({t} does not support spawning a child process):\n{s}", .{
4445 child.stdin_behavior = .Inherit;4460 native_os, cmd,
4446 child.stdout_behavior = .Inherit;4461 });
4447 child.stderr_behavior = .Inherit;4462 }
44484463 const term_result = (term: {
4449 // Here we release all the locks associated with the Compilation so4464 // Here we release all the locks associated with the Compilation so
4450 // that whatever this child process wants to do won't deadlock.4465 // that whatever this child process wants to do won't deadlock.
4451 comp.destroy();4466 comp.destroy();
4452 comp_destroyed.* = true;4467 comp_destroyed.* = true;
44534468
4454 const term_result = t: {4469 _ = try io.lockStderr(&.{}, .no_color);
4455 _ = try io.lockStderr(&.{}, .no_color);4470 defer io.unlockStderr();
4456 defer io.unlockStderr();4471
4457 break :t child.spawnAndWait(io);4472 var child = std.process.spawn(io, .{
4458 };4473 .argv = argv.items,
4459 const term = term_result catch |err| {4474 .environ_map = environ_map,
4460 try warnAboutForeignBinaries(io, arena, arg_mode, target, link_libc);4475 .stdin = .inherit,
4461 const cmd = try std.mem.join(arena, " ", argv.items);4476 .stdout = .inherit,
4462 fatal("the following command failed with '{s}':\n{s}", .{ @errorName(err), cmd });4477 .stderr = .inherit,
4463 };4478 }) catch |err| break :term err;
4464 switch (arg_mode) {4479 defer child.kill(io);
4465 .run, .build => {4480
4466 switch (term) {4481 break :term child.wait(io);
4467 .Exited => |code| {4482 });
4468 if (code == 0) {4483
4469 return cleanExit(io);4484 const term = term_result catch |err| {
4470 } else {4485 try warnAboutForeignBinaries(io, arena, arg_mode, target, link_libc);
4471 process.exit(code);
4472 }
4473 },
4474 else => {
4475 process.exit(1);
4476 },
4477 }
4478 },
4479 .zig_test => {
4480 switch (term) {
4481 .Exited => |code| {
4482 if (code == 0) {
4483 return cleanExit(io);
4484 } else {
4485 const cmd = try std.mem.join(arena, " ", argv.items);
4486 fatal("the following test command failed with exit code {d}:\n{s}", .{ code, cmd });
4487 }
4488 },
4489 else => {
4490 const cmd = try std.mem.join(arena, " ", argv.items);
4491 fatal("the following test command crashed:\n{s}", .{cmd});
4492 },
4493 }
4494 },
4495 else => unreachable,
4496 }
4497 } else {
4498 const cmd = try std.mem.join(arena, " ", argv.items);4486 const cmd = try std.mem.join(arena, " ", argv.items);
4499 fatal("the following command cannot be executed ({s} does not support spawning a child process):\n{s}", .{ @tagName(native_os), cmd });4487 fatal("the following command failed with {t}:\n{s}", .{ err, cmd });
4488 };
4489 switch (arg_mode) {
4490 .run, .build => {
4491 switch (term) {
4492 .exited => |code| {
4493 if (code == 0) {
4494 return cleanExit(io);
4495 } else {
4496 process.exit(code);
4497 }
4498 },
4499 .signal => |sig| {
4500 const cmd = try std.mem.join(arena, " ", argv.items);
4501 fatal("the following command terminated with signal {t}:\n{s}", .{ sig, cmd });
4502 },
4503 else => {
4504 process.exit(1);
4505 },
4506 }
4507 },
4508 .zig_test => {
4509 switch (term) {
4510 .exited => |code| {
4511 if (code == 0) {
4512 return cleanExit(io);
4513 } else {
4514 const cmd = try std.mem.join(arena, " ", argv.items);
4515 fatal("the following test command failed with exit code {d}:\n{s}", .{ code, cmd });
4516 }
4517 },
4518 .signal => |sig| {
4519 const cmd = try std.mem.join(arena, " ", argv.items);
4520 fatal("the following test command terminated with signal {t}:\n{s}", .{ sig, cmd });
4521 },
4522 else => {
4523 const cmd = try std.mem.join(arena, " ", argv.items);
4524 fatal("the following test command crashed:\n{s}", .{cmd});
4525 },
4526 }
4527 },
4528 else => unreachable,
4500 }4529 }
4501}4530}
45024531
...@@ -4559,43 +4588,13 @@ fn runOrTestHotSwap(...@@ -4559,43 +4588,13 @@ fn runOrTestHotSwap(
4559 try argv.appendSlice(all_args[i..]);4588 try argv.appendSlice(all_args[i..]);
4560 }4589 }
45614590
4562 switch (builtin.target.os.tag) {4591 var child = try std.process.spawn(io, .{
4563 .macos => {4592 .argv = argv.items,
4564 const PosixSpawn = @import("DarwinPosixSpawn.zig");4593 .stdin = .inherit,
45654594 .stdout = .inherit,
4566 var attr = try PosixSpawn.Attr.init();4595 .stderr = .inherit,
4567 defer attr.deinit();4596 });
45684597 return child.id.?;
4569 // ASLR is probably a good default for better debugging experience/programming
4570 // with hot-code updates in mind. However, we can also make it work with ASLR on.
4571 try attr.set(.{
4572 .SETSIGDEF = true,
4573 .SETSIGMASK = true,
4574 .DISABLE_ASLR = true,
4575 });
4576
4577 var arena_allocator = std.heap.ArenaAllocator.init(gpa);
4578 defer arena_allocator.deinit();
4579 const arena = arena_allocator.allocator();
4580
4581 const argv_buf = try arena.allocSentinel(?[*:0]u8, argv.items.len, null);
4582 for (argv.items, 0..) |arg, i| argv_buf[i] = (try arena.dupeZ(u8, arg)).ptr;
4583
4584 const pid = try PosixSpawn.spawn(argv.items[0], null, attr, argv_buf, std.c.environ);
4585 return pid;
4586 },
4587 else => {
4588 var child = std.process.Child.init(argv.items, gpa);
4589
4590 child.stdin_behavior = .Inherit;
4591 child.stdout_behavior = .Inherit;
4592 child.stderr_behavior = .Inherit;
4593
4594 try child.spawn(io);
4595
4596 return child.id;
4597 },
4598 }
4599}4598}
46004599
4601const UpdateModuleError = Compilation.UpdateError || error{4600const UpdateModuleError = Compilation.UpdateError || error{
...@@ -4627,6 +4626,7 @@ fn cmdTranslateC(...@@ -4627,6 +4626,7 @@ fn cmdTranslateC(
4627 fancy_output: ?*Compilation.CImportResult,4626 fancy_output: ?*Compilation.CImportResult,
4628 file_system_inputs: ?*std.ArrayList(u8),4627 file_system_inputs: ?*std.ArrayList(u8),
4629 prog_node: std.Progress.Node,4628 prog_node: std.Progress.Node,
4629 environ_map: *process.Environ.Map,
4630) !void {4630) !void {
4631 dev.check(.translate_c_command);4631 dev.check(.translate_c_command);
46324632
...@@ -4660,6 +4660,7 @@ fn cmdTranslateC(...@@ -4660,6 +4660,7 @@ fn cmdTranslateC(
4660 translated_basename,4660 translated_basename,
4661 comp.root_mod,4661 comp.root_mod,
4662 prog_node,4662 prog_node,
4663 environ_map,
4663 );4664 );
46644665
4665 if (result.errors.errorMessageCount() != 0) {4666 if (result.errors.errorMessageCount() != 0) {
...@@ -4707,10 +4708,11 @@ pub fn translateC(...@@ -4707,10 +4708,11 @@ pub fn translateC(
4707 arena: Allocator,4708 arena: Allocator,
4708 io: Io,4709 io: Io,
4709 argv: []const []const u8,4710 argv: []const []const u8,
4711 environ_map: *const process.Environ.Map,
4710 prog_node: std.Progress.Node,4712 prog_node: std.Progress.Node,
4711 capture: ?*[]u8,4713 capture: ?*[]u8,
4712) !void {4714) !void {
4713 try jitCmd(gpa, arena, io, argv, .{4715 try jitCmd(gpa, arena, io, argv, environ_map, .{
4714 .cmd_name = "translate-c",4716 .cmd_name = "translate-c",
4715 .root_src_path = "translate-c/main.zig",4717 .root_src_path = "translate-c/main.zig",
4716 .depend_on_aro = true,4718 .depend_on_aro = true,
...@@ -4867,21 +4869,21 @@ test sanitizeExampleName {...@@ -4867,21 +4869,21 @@ test sanitizeExampleName {
4867 try std.testing.expectEqualStrings("test_project", try sanitizeExampleName(arena, "test project"));4869 try std.testing.expectEqualStrings("test_project", try sanitizeExampleName(arena, "test project"));
4868}4870}
48694871
4870fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) !void {4872fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8, environ_map: *process.Environ.Map) !void {
4871 dev.check(.build_command);4873 dev.check(.build_command);
48724874
4873 var build_file: ?[]const u8 = null;4875 var build_file: ?[]const u8 = null;
4874 var override_lib_dir: ?[]const u8 = try EnvVar.ZIG_LIB_DIR.get(arena);4876 var override_lib_dir: ?[]const u8 = EnvVar.ZIG_LIB_DIR.get(environ_map);
4875 var override_global_cache_dir: ?[]const u8 = try EnvVar.ZIG_GLOBAL_CACHE_DIR.get(arena);4877 var override_global_cache_dir: ?[]const u8 = EnvVar.ZIG_GLOBAL_CACHE_DIR.get(environ_map);
4876 var override_local_cache_dir: ?[]const u8 = try EnvVar.ZIG_LOCAL_CACHE_DIR.get(arena);4878 var override_local_cache_dir: ?[]const u8 = EnvVar.ZIG_LOCAL_CACHE_DIR.get(environ_map);
4877 var override_build_runner: ?[]const u8 = try EnvVar.ZIG_BUILD_RUNNER.get(arena);4879 var override_build_runner: ?[]const u8 = EnvVar.ZIG_BUILD_RUNNER.get(environ_map);
4878 var child_argv = std.array_list.Managed([]const u8).init(arena);4880 var child_argv = std.array_list.Managed([]const u8).init(arena);
4879 var reference_trace: ?u32 = null;4881 var reference_trace: ?u32 = null;
4880 var debug_compile_errors = false;4882 var debug_compile_errors = false;
4881 var verbose_link = (native_os != .wasi or builtin.link_libc) and4883 var verbose_link = (native_os != .wasi or builtin.link_libc) and
4882 EnvVar.ZIG_VERBOSE_LINK.isSet();4884 EnvVar.ZIG_VERBOSE_LINK.isSet(environ_map);
4883 var verbose_cc = (native_os != .wasi or builtin.link_libc) and4885 var verbose_cc = (native_os != .wasi or builtin.link_libc) and
4884 EnvVar.ZIG_VERBOSE_CC.isSet();4886 EnvVar.ZIG_VERBOSE_CC.isSet(environ_map);
4885 var verbose_air = false;4887 var verbose_air = false;
4886 var verbose_intern_pool = false;4888 var verbose_intern_pool = false;
4887 var verbose_generic_instances = false;4889 var verbose_generic_instances = false;
...@@ -5078,7 +5080,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8)...@@ -5078,7 +5080,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8)
5078 }5080 }
50795081
5080 const work_around_btrfs_bug = native_os == .linux and5082 const work_around_btrfs_bug = native_os == .linux and
5081 EnvVar.ZIG_BTRFS_WORKAROUND.isSet();5083 EnvVar.ZIG_BTRFS_WORKAROUND.isSet(environ_map);
5082 const root_prog_node = std.Progress.start(io, .{5084 const root_prog_node = std.Progress.start(io, .{
5083 .disable_printing = (color == .off),5085 .disable_printing = (color == .off),
5084 .root_name = "Compile Build Script",5086 .root_name = "Compile Build Script",
...@@ -5138,6 +5140,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8)...@@ -5138,6 +5140,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8)
5138 } },5140 } },
5139 {},5141 {},
5140 self_exe_path,5142 self_exe_path,
5143 environ_map,
5141 );5144 );
5142 defer dirs.deinit(io);5145 defer dirs.deinit(io);
51435146
...@@ -5240,7 +5243,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8)...@@ -5240,7 +5243,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8)
5240 job_queue.read_only = true;5243 job_queue.read_only = true;
5241 cleanup_build_dir = job_queue.global_cache.handle;5244 cleanup_build_dir = job_queue.global_cache.handle;
5242 } else {5245 } else {
5243 try http_client.initDefaultProxies(arena);5246 try http_client.initDefaultProxies(arena, environ_map);
5244 }5247 }
52455248
5246 try job_queue.all_fetches.ensureUnusedCapacity(gpa, 1);5249 try job_queue.all_fetches.ensureUnusedCapacity(gpa, 1);
...@@ -5394,6 +5397,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8)...@@ -5394,6 +5397,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8)
5394 .cache_mode = .whole,5397 .cache_mode = .whole,
5395 .reference_trace = reference_trace,5398 .reference_trace = reference_trace,
5396 .debug_compile_errors = debug_compile_errors,5399 .debug_compile_errors = debug_compile_errors,
5400 .environ_map = environ_map,
5397 }) catch |err| switch (err) {5401 }) catch |err| switch (err) {
5398 error.CreateFail => fatal("failed to create compilation: {f}", .{create_diag}),5402 error.CreateFail => fatal("failed to create compilation: {f}", .{create_diag}),
5399 else => fatal("failed to create compilation: {s}", .{@errorName(err)}),5403 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)...@@ -5415,81 +5419,81 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8)
5415 });5419 });
5416 }5420 }
54175421
5418 if (process.can_spawn) {5422 if (!process.can_spawn) {
5419 var child = std.process.Child.init(child_argv.items, gpa);5423 const cmd = try std.mem.join(arena, " ", child_argv.items);
5420 child.stdin_behavior = .Inherit;5424 fatal("the following command cannot be executed ({t} does not support spawning a child process):\n{s}", .{ native_os, cmd });
5421 child.stdout_behavior = .Inherit;5425 }
5422 child.stderr_behavior = .Inherit;5426 switch (term: {
54235427 _ = try io.lockStderr(&.{}, .no_color);
5424 const term = t: {5428 defer io.unlockStderr();
5425 _ = try io.lockStderr(&.{}, .no_color);5429 var child = std.process.spawn(io, .{
5426 defer io.unlockStderr();5430 .argv = child_argv.items,
5427 break :t child.spawnAndWait(io) catch |err|5431 }) catch |err| fatal("failed to spawn build runner {s}: {t}", .{ child_argv.items[0], err });
5428 fatal("failed to spawn build runner {s}: {t}", .{ child_argv.items[0], err });5432 defer child.kill(io);
5429 };5433 break :term child.wait(io) catch |err|
54305434 fatal("failed to wait build runner {s}: {t}", .{ child_argv.items[0], err });
5431 switch (term) {5435 }) {
5432 .Exited => |code| {5436 .exited => |code| {
5433 if (code == 0) return cleanExit(io);5437 if (code == 0) return cleanExit(io);
5434 // Indicates that the build runner has reported compile errors5438 // Indicates that the build runner has reported compile errors
5435 // and this parent process does not need to report any further5439 // and this parent process does not need to report any further
5436 // diagnostics.5440 // diagnostics.
5437 if (code == 2) process.exit(2);5441 if (code == 2) process.exit(2);
54385442
5439 if (code == 3) {5443 if (code == 3) {
5440 if (!dev.env.supports(.fetch_command)) process.exit(3);5444 if (!dev.env.supports(.fetch_command)) process.exit(3);
5441 // Indicates the configure phase failed due to missing lazy5445 // Indicates the configure phase failed due to missing lazy
5442 // dependencies and stdout contains the hashes of the ones5446 // dependencies and stdout contains the hashes of the ones
5443 // that are missing.5447 // that are missing.
5444 const s = fs.path.sep_str;5448 const s = fs.path.sep_str;
5445 const tmp_sub_path = "tmp" ++ s ++ results_tmp_file_nonce;5449 const tmp_sub_path = "tmp" ++ s ++ results_tmp_file_nonce;
5446 const stdout = dirs.local_cache.handle.readFileAlloc(io, tmp_sub_path, arena, .limited(50 * 1024 * 1024)) catch |err| {5450 const stdout = dirs.local_cache.handle.readFileAlloc(io, tmp_sub_path, arena, .limited(50 * 1024 * 1024)) catch |err| {
5447 fatal("unable to read results of configure phase from '{f}{s}': {s}", .{5451 fatal("unable to read results of configure phase from '{f}{s}': {t}", .{
5448 dirs.local_cache, tmp_sub_path, @errorName(err),5452 dirs.local_cache, tmp_sub_path, err,
5453 });
5454 };
5455 dirs.local_cache.handle.deleteFile(io, tmp_sub_path) catch {};
5456
5457 var it = mem.splitScalar(u8, stdout, '\n');
5458 var any_errors = false;
5459 while (it.next()) |hash| {
5460 if (hash.len == 0) continue;
5461 if (hash.len > Package.Hash.max_len) {
5462 std.log.err("invalid digest (length {d} exceeds maximum): '{s}'", .{
5463 hash.len, hash,
5449 });5464 });
5450 };5465 any_errors = true;
5451 dirs.local_cache.handle.deleteFile(io, tmp_sub_path) catch {};5466 continue;
5452
5453 var it = mem.splitScalar(u8, stdout, '\n');
5454 var any_errors = false;
5455 while (it.next()) |hash| {
5456 if (hash.len == 0) continue;
5457 if (hash.len > Package.Hash.max_len) {
5458 std.log.err("invalid digest (length {d} exceeds maximum): '{s}'", .{
5459 hash.len, hash,
5460 });
5461 any_errors = true;
5462 continue;
5463 }
5464 try unlazy_set.put(arena, .fromSlice(hash), {});
5465 }5467 }
5466 if (any_errors) process.exit(3);5468 try unlazy_set.put(arena, .fromSlice(hash), {});
5467 if (system_pkg_dir_path) |p| {5469 }
5468 // In this mode, the system needs to provide these packages; they5470 if (any_errors) process.exit(3);
5469 // cannot be fetched by Zig.5471 if (system_pkg_dir_path) |p| {
5470 for (unlazy_set.keys()) |*hash| {5472 // In this mode, the system needs to provide these packages; they
5471 std.log.err("lazy dependency package not found: {s}" ++ s ++ "{s}", .{5473 // cannot be fetched by Zig.
5472 p, hash.toSlice(),5474 for (unlazy_set.keys()) |*hash| {
5473 });5475 std.log.err("lazy dependency package not found: {s}" ++ s ++ "{s}", .{
5474 }5476 p, hash.toSlice(),
5475 std.log.info("remote package fetching disabled due to --system mode", .{});5477 });
5476 std.log.info("dependencies might be avoidable depending on build configuration", .{});
5477 process.exit(3);
5478 }5478 }
5479 continue;5479 std.log.info("remote package fetching disabled due to --system mode", .{});
5480 std.log.info("dependencies might be avoidable depending on build configuration", .{});
5481 process.exit(3);
5480 }5482 }
5483 continue;
5484 }
54815485
5482 const cmd = try std.mem.join(arena, " ", child_argv.items);5486 const cmd = try std.mem.join(arena, " ", child_argv.items);
5483 fatal("the following build command failed with exit code {d}:\n{s}", .{ code, cmd });5487 fatal("the following build command failed with exit code {d}:\n{s}", .{ code, cmd });
5484 },5488 },
5485 else => {5489 .signal => |sig| {
5486 const cmd = try std.mem.join(arena, " ", child_argv.items);5490 const cmd = try std.mem.join(arena, " ", child_argv.items);
5487 fatal("the following build command crashed:\n{s}", .{cmd});5491 fatal("the following build command terminated with signal {t}:\n{s}", .{ sig, cmd });
5488 },5492 },
5489 }5493 else => {
5490 } else {5494 const cmd = try std.mem.join(arena, " ", child_argv.items);
5491 const cmd = try std.mem.join(arena, " ", child_argv.items);5495 fatal("the following build command crashed:\n{s}", .{cmd});
5492 fatal("the following command cannot be executed ({s} does not support spawning a child process):\n{s}", .{ @tagName(native_os), cmd });5496 },
5493 }5497 }
5494 }5498 }
5495}5499}
...@@ -5512,6 +5516,7 @@ fn jitCmd(...@@ -5512,6 +5516,7 @@ fn jitCmd(
5512 arena: Allocator,5516 arena: Allocator,
5513 io: Io,5517 io: Io,
5514 args: []const []const u8,5518 args: []const []const u8,
5519 environ_map: *const process.Environ.Map,
5515 options: JitCmdOptions,5520 options: JitCmdOptions,
5516) !void {5521) !void {
5517 dev.check(.jit_command);5522 dev.check(.jit_command);
...@@ -5533,13 +5538,13 @@ fn jitCmd(...@@ -5533,13 +5538,13 @@ fn jitCmd(
5533 const self_exe_path = process.executablePathAlloc(io, arena) catch |err|5538 const self_exe_path = process.executablePathAlloc(io, arena) catch |err|
5534 fatal("unable to find self exe path: {t}", .{err});5539 fatal("unable to find self exe path: {t}", .{err});
55355540
5536 const optimize_mode: std.builtin.OptimizeMode = if (EnvVar.ZIG_DEBUG_CMD.isSet())5541 const optimize_mode: std.builtin.OptimizeMode = if (EnvVar.ZIG_DEBUG_CMD.isSet(environ_map))
5537 .Debug5542 .Debug
5538 else5543 else
5539 .ReleaseFast;5544 .ReleaseFast;
5540 const strip = optimize_mode != .Debug;5545 const strip = optimize_mode != .Debug;
5541 const override_lib_dir: ?[]const u8 = try EnvVar.ZIG_LIB_DIR.get(arena);5546 const override_lib_dir: ?[]const u8 = EnvVar.ZIG_LIB_DIR.get(environ_map);
5542 const override_global_cache_dir: ?[]const u8 = try EnvVar.ZIG_GLOBAL_CACHE_DIR.get(arena);5547 const override_global_cache_dir: ?[]const u8 = EnvVar.ZIG_GLOBAL_CACHE_DIR.get(environ_map);
55435548
5544 // This `init` calls `fatal` on error.5549 // This `init` calls `fatal` on error.
5545 var dirs: Compilation.Directories = .init(5550 var dirs: Compilation.Directories = .init(
...@@ -5550,6 +5555,7 @@ fn jitCmd(...@@ -5550,6 +5555,7 @@ fn jitCmd(
5550 .global,5555 .global,
5551 if (native_os == .wasi) wasi_preopens,5556 if (native_os == .wasi) wasi_preopens,
5552 self_exe_path,5557 self_exe_path,
5558 environ_map,
5553 );5559 );
5554 defer dirs.deinit(io);5560 defer dirs.deinit(io);
55555561
...@@ -5623,6 +5629,7 @@ fn jitCmd(...@@ -5623,6 +5629,7 @@ fn jitCmd(
5623 .self_exe_path = self_exe_path,5629 .self_exe_path = self_exe_path,
5624 .thread_limit = thread_limit,5630 .thread_limit = thread_limit,
5625 .cache_mode = .whole,5631 .cache_mode = .whole,
5632 .environ_map = environ_map,
5626 }) catch |err| switch (err) {5633 }) catch |err| switch (err) {
5627 error.CreateFail => fatal("failed to create compilation: {f}", .{create_diag}),5634 error.CreateFail => fatal("failed to create compilation: {f}", .{create_diag}),
5628 else => fatal("failed to create compilation: {s}", .{@errorName(err)}),5635 else => fatal("failed to create compilation: {s}", .{@errorName(err)}),
...@@ -5668,32 +5675,34 @@ fn jitCmd(...@@ -5668,32 +5675,34 @@ fn jitCmd(
56685675
5669 child_argv.appendSliceAssumeCapacity(args);5676 child_argv.appendSliceAssumeCapacity(args);
56705677
5671 if (process.can_execv and options.capture == null) {5678 if (process.can_replace and options.capture == null) {
5672 if (EnvVar.ZIG_DEBUG_CMD.isSet()) {5679 if (EnvVar.ZIG_DEBUG_CMD.isSet(environ_map)) {
5673 const cmd = try std.mem.join(arena, " ", child_argv.items);5680 const cmd = try std.mem.join(arena, " ", child_argv.items);
5674 std.debug.print("{s}\n", .{cmd});5681 std.debug.print("{s}\n", .{cmd});
5675 }5682 }
5676 const err = process.execv(gpa, child_argv.items);5683 const err = process.replace(io, .{ .argv = child_argv.items, .environ_map = environ_map });
5677 const cmd = try std.mem.join(arena, " ", child_argv.items);5684 const cmd = try std.mem.join(arena, " ", child_argv.items);
5678 fatal("the following command failed to execve with '{t}':\n{s}", .{ err, cmd });5685 fatal("the following command failed to execve with '{t}':\n{s}", .{ err, cmd });
5679 }5686 }
56805687
5681 if (!process.can_spawn) {5688 if (!process.can_spawn) {
5682 const cmd = try std.mem.join(arena, " ", child_argv.items);5689 const cmd = try std.mem.join(arena, " ", child_argv.items);
5683 fatal("the following command cannot be executed ({s} does not support spawning a child process):\n{s}", .{5690 fatal("the following command cannot be executed ({t} does not support spawning a child process):\n{s}", .{
5684 @tagName(native_os), cmd,5691 native_os, cmd,
5685 });5692 });
5686 }5693 }
56875694
5688 var child = std.process.Child.init(child_argv.items, gpa);5695 switch (t: {
5689 child.stdin_behavior = .Inherit;
5690 child.stdout_behavior = if (options.capture == null) .Inherit else .Pipe;
5691 child.stderr_behavior = .Inherit;
5692
5693 const term = t: {
5694 _ = try io.lockStderr(&.{}, .no_color);5696 _ = try io.lockStderr(&.{}, .no_color);
5695 defer io.unlockStderr();5697 defer io.unlockStderr();
5696 try child.spawn(io);5698
5699 var child = std.process.spawn(io, .{
5700 .argv = child_argv.items,
5701 .stdin = .inherit,
5702 .stdout = if (options.capture == null) .inherit else .pipe,
5703 .stderr = .inherit,
5704 }) catch |err| fatal("failed to spawn {s}: {t}", .{ child_argv.items[0], err });
5705 defer child.kill(io);
56975706
5698 if (options.capture) |ptr| {5707 if (options.capture) |ptr| {
5699 var stdout_reader = child.stdout.?.readerStreaming(io, &.{});5708 var stdout_reader = child.stdout.?.readerStreaming(io, &.{});
...@@ -5701,9 +5710,8 @@ fn jitCmd(...@@ -5701,9 +5710,8 @@ fn jitCmd(
5701 }5710 }
57025711
5703 break :t try child.wait(io);5712 break :t try child.wait(io);
5704 };5713 }) {
5705 switch (term) {5714 .exited => |code| {
5706 .Exited => |code| {
5707 if (code == 0) {5715 if (code == 0) {
5708 if (options.capture != null) return;5716 if (options.capture != null) return;
5709 return cleanExit(io);5717 return cleanExit(io);
...@@ -5711,6 +5719,10 @@ fn jitCmd(...@@ -5711,6 +5719,10 @@ fn jitCmd(
5711 const cmd = try std.mem.join(arena, " ", child_argv.items);5719 const cmd = try std.mem.join(arena, " ", child_argv.items);
5712 fatal("the following build command failed with exit code {d}:\n{s}", .{ code, cmd });5720 fatal("the following build command failed with exit code {d}:\n{s}", .{ code, cmd });
5713 },5721 },
5722 .signal => |sig| {
5723 const cmd = try std.mem.join(arena, " ", child_argv.items);
5724 fatal("the following build command terminated with signal {t}:\n{s}", .{ sig, cmd });
5725 },
5714 else => {5726 else => {
5715 const cmd = try std.mem.join(arena, " ", child_argv.items);5727 const cmd = try std.mem.join(arena, " ", child_argv.items);
5716 fatal("the following build command crashed:\n{s}", .{cmd});5728 fatal("the following build command crashed:\n{s}", .{cmd});
...@@ -5826,7 +5838,7 @@ pub fn lldMain(...@@ -5826,7 +5838,7 @@ pub fn lldMain(
5826 return @intFromBool(!ok);5838 return @intFromBool(!ok);
5827}5839}
58285840
5829const ArgIteratorResponseFile = process.ArgIteratorGeneral(.{ .comments = true, .single_quotes = true });5841const ArgIteratorResponseFile = process.Args.IteratorGeneral(.{ .comments = true, .single_quotes = true });
58305842
5831/// Initialize the arguments from a Response File. "*.rsp"5843/// Initialize the arguments from a Response File. "*.rsp"
5832fn initArgIteratorResponseFile(allocator: Allocator, io: Io, resp_file_path: []const u8) !ArgIteratorResponseFile {5844fn initArgIteratorResponseFile(allocator: Allocator, io: Io, resp_file_path: []const u8) !ArgIteratorResponseFile {
...@@ -6902,14 +6914,15 @@ fn cmdFetch(...@@ -6902,14 +6914,15 @@ fn cmdFetch(
6902 arena: Allocator,6914 arena: Allocator,
6903 io: Io,6915 io: Io,
6904 args: []const []const u8,6916 args: []const []const u8,
6917 environ_map: *process.Environ.Map,
6905) !void {6918) !void {
6906 dev.check(.fetch_command);6919 dev.check(.fetch_command);
69076920
6908 const color: Color = .auto;6921 const color: Color = .auto;
6909 const work_around_btrfs_bug = native_os == .linux and6922 const work_around_btrfs_bug = native_os == .linux and
6910 EnvVar.ZIG_BTRFS_WORKAROUND.isSet();6923 EnvVar.ZIG_BTRFS_WORKAROUND.isSet(environ_map);
6911 var opt_path_or_url: ?[]const u8 = null;6924 var opt_path_or_url: ?[]const u8 = null;
6912 var override_global_cache_dir: ?[]const u8 = try EnvVar.ZIG_GLOBAL_CACHE_DIR.get(arena);6925 var override_global_cache_dir: ?[]const u8 = EnvVar.ZIG_GLOBAL_CACHE_DIR.get(environ_map);
6913 var debug_hash: bool = false;6926 var debug_hash: bool = false;
6914 var save: union(enum) {6927 var save: union(enum) {
6915 no,6928 no,
...@@ -6955,7 +6968,7 @@ fn cmdFetch(...@@ -6955,7 +6968,7 @@ fn cmdFetch(
6955 var http_client: std.http.Client = .{ .allocator = gpa, .io = io };6968 var http_client: std.http.Client = .{ .allocator = gpa, .io = io };
6956 defer http_client.deinit();6969 defer http_client.deinit();
69576970
6958 try http_client.initDefaultProxies(arena);6971 try http_client.initDefaultProxies(arena, environ_map);
69596972
6960 var root_prog_node = std.Progress.start(io, .{6973 var root_prog_node = std.Progress.start(io, .{
6961 .root_name = "Fetch",6974 .root_name = "Fetch",
...@@ -6963,7 +6976,7 @@ fn cmdFetch(...@@ -6963,7 +6976,7 @@ fn cmdFetch(
6963 defer root_prog_node.end();6976 defer root_prog_node.end();
69646977
6965 var global_cache_directory: Directory = l: {6978 var global_cache_directory: Directory = l: {
6966 const p = override_global_cache_dir orelse try introspect.resolveGlobalCacheDir(arena);6979 const p = override_global_cache_dir orelse try introspect.resolveGlobalCacheDir(arena, environ_map);
6967 break :l .{6980 break :l .{
6968 .handle = try Io.Dir.cwd().createDirPathOpen(io, p, .{}),6981 .handle = try Io.Dir.cwd().createDirPathOpen(io, p, .{}),
6969 .path = p,6982 .path = p,
src/print_env.zig+6-4
...@@ -19,9 +19,10 @@ pub fn cmdEnv(...@@ -19,9 +19,10 @@ pub fn cmdEnv(
19 else => void,19 else => void,
20 },20 },
21 host: *const std.Target,21 host: *const std.Target,
22 environ_map: *std.process.Environ.Map,
22) !void {23) !void {
23 const override_lib_dir: ?[]const u8 = try EnvVar.ZIG_LIB_DIR.get(arena);24 const override_lib_dir: ?[]const u8 = EnvVar.ZIG_LIB_DIR.get(environ_map);
24 const override_global_cache_dir: ?[]const u8 = try EnvVar.ZIG_GLOBAL_CACHE_DIR.get(arena);25 const override_global_cache_dir: ?[]const u8 = EnvVar.ZIG_GLOBAL_CACHE_DIR.get(environ_map);
2526
26 const self_exe_path = switch (builtin.target.os.tag) {27 const self_exe_path = switch (builtin.target.os.tag) {
27 .wasi => args[0],28 .wasi => args[0],
...@@ -38,6 +39,7 @@ pub fn cmdEnv(...@@ -38,6 +39,7 @@ pub fn cmdEnv(
38 .global,39 .global,
39 if (builtin.target.os.tag == .wasi) wasi_preopens,40 if (builtin.target.os.tag == .wasi) wasi_preopens,
40 if (builtin.target.os.tag != .wasi) self_exe_path,41 if (builtin.target.os.tag != .wasi) self_exe_path,
42 environ_map,
41 );43 );
42 defer dirs.deinit(io);44 defer dirs.deinit(io);
4345
...@@ -56,8 +58,8 @@ pub fn cmdEnv(...@@ -56,8 +58,8 @@ pub fn cmdEnv(
56 try root.field("version", build_options.version, .{});58 try root.field("version", build_options.version, .{});
57 try root.field("target", triple, .{});59 try root.field("target", triple, .{});
58 var env = try root.beginStructField("env", .{});60 var env = try root.beginStructField("env", .{});
59 inline for (@typeInfo(std.zig.EnvVar).@"enum".fields) |field| {61 inline for (@typeInfo(EnvVar).@"enum".fields) |field| {
60 try env.field(field.name, try @field(std.zig.EnvVar, field.name).get(arena), .{});62 try env.field(field.name, @field(EnvVar, field.name).get(environ_map), .{});
61 }63 }
62 try env.end();64 try env.end();
63 try root.end();65 try root.end();
test/src/Debugger.zig+1-1
...@@ -2306,7 +2306,7 @@ fn addTest(...@@ -2306,7 +2306,7 @@ fn addTest(
2306 run.addArgs(db_argv2);2306 run.addArgs(db_argv2);
2307 run.addArtifactArg(exe);2307 run.addArtifactArg(exe);
2308 for (expected_output) |expected| run.addCheck(.{ .expect_stdout_match = db.b.fmt("{s}\n", .{expected}) });2308 for (expected_output) |expected| run.addCheck(.{ .expect_stdout_match = db.b.fmt("{s}\n", .{expected}) });
2309 run.addCheck(.{ .expect_term = .{ .Exited = success } });2309 run.addCheck(.{ .expect_term = .{ .exited = success } });
2310 run.setStdIn(.{ .bytes = "" });2310 run.setStdIn(.{ .bytes = "" });
2311 db.root_step.dependOn(&run.step);2311 db.root_step.dependOn(&run.step);
2312}2312}
test/src/StackTrace.zig+3-3
...@@ -193,9 +193,9 @@ fn addCaseInstance(...@@ -193,9 +193,9 @@ fn addCaseInstance(
193 run.removeEnvironmentVariable("CLICOLOR_FORCE");193 run.removeEnvironmentVariable("CLICOLOR_FORCE");
194 run.setEnvironmentVariable("NO_COLOR", "1");194 run.setEnvironmentVariable("NO_COLOR", "1");
195 run.addCheck(.{ .expect_term = term: {195 run.addCheck(.{ .expect_term = term: {
196 if (!expect_panic) break :term .{ .Exited = 0 };196 if (!expect_panic) break :term .{ .exited = 0 };
197 if (target.result.os.tag == .windows) break :term .{ .Exited = 3 };197 if (target.result.os.tag == .windows) break :term .{ .exited = 3 };
198 break :term .{ .Signal = 6 };198 break :term .{ .signal = @enumFromInt(6) };
199 } });199 } });
200 run.expectStdOutEqual("");200 run.expectStdOutEqual("");
201201
test/src/convert-stack-trace.zig+4-11
...@@ -24,20 +24,13 @@...@@ -24,20 +24,13 @@
24//!24//!
25//! With these transformations, the test harness can safely do string comparisons.25//! With these transformations, the test harness can safely do string comparisons.
2626
27pub fn main() !void {27pub fn main(init: std.process.Init) !void {
28 var arena_instance: std.heap.ArenaAllocator = .init(std.heap.page_allocator);28 const arena = init.arena.allocator();
29 defer arena_instance.deinit();29 const io = init.io;
30 const arena = arena_instance.allocator();30 const args = try init.minimal.args.toSlice(arena);
3131
32 const args = try std.process.argsAlloc(arena);
33 if (args.len != 2) std.process.fatal("usage: convert-stack-trace path/to/test/output", .{});32 if (args.len != 2) std.process.fatal("usage: convert-stack-trace path/to/test/output", .{});
3433
35 const gpa = arena;
36
37 var threaded: std.Io.Threaded = .init(gpa, .{});
38 defer threaded.deinit();
39 const io = threaded.io();
40
41 var read_buf: [1024]u8 = undefined;34 var read_buf: [1024]u8 = undefined;
42 var write_buf: [1024]u8 = undefined;35 var write_buf: [1024]u8 = undefined;
4336
test/standalone/child_process/child.zig+8-16
...@@ -4,31 +4,23 @@ const Io = std.Io;...@@ -4,31 +4,23 @@ const Io = std.Io;
4// 42 is expected by parent; other values result in test failure4// 42 is expected by parent; other values result in test failure
5var exit_code: u8 = 42;5var exit_code: u8 = 42;
66
7pub fn main() !void {7pub fn main(init: std.process.Init) !void {
8 var arena_state = std.heap.ArenaAllocator.init(std.heap.page_allocator);8 try run(init.arena.allocator(), init.io, init.minimal.args);
9 const arena = arena_state.allocator();
10
11 var threaded: std.Io.Threaded = .init(arena, .{});
12 defer threaded.deinit();
13 const io = threaded.io();
14
15 try run(arena, io);
16 arena_state.deinit();
17 std.process.exit(exit_code);9 std.process.exit(exit_code);
18}10}
1911
20fn run(allocator: std.mem.Allocator, io: Io) !void {12fn run(arena: std.mem.Allocator, io: Io, args: std.process.Args) !void {
21 var args = try std.process.argsWithAllocator(allocator);13 var it = try args.iterateAllocator(arena);
22 defer args.deinit();14 defer it.deinit();
23 _ = args.next() orelse unreachable; // skip binary name15 _ = it.next() orelse unreachable; // skip binary name
2416
25 // test cmd args17 // test cmd args
26 const hello_arg = "hello arg";18 const hello_arg = "hello arg";
27 const a1 = args.next() orelse unreachable;19 const a1 = it.next() orelse unreachable;
28 if (!std.mem.eql(u8, a1, hello_arg)) {20 if (!std.mem.eql(u8, a1, hello_arg)) {
29 testError(io, "first arg: '{s}'; want '{s}'", .{ a1, hello_arg });21 testError(io, "first arg: '{s}'; want '{s}'", .{ a1, hello_arg });
30 }22 }
31 if (args.next()) |a2| {23 if (it.next()) |a2| {
32 testError(io, "expected only one arg; got more: {s}", .{a2});24 testError(io, "expected only one arg; got more: {s}", .{a2});
33 }25 }
3426
test/standalone/child_process/main.zig+23-12
...@@ -1,15 +1,21 @@...@@ -1,15 +1,21 @@
1const std = @import("std");1const std = @import("std");
2const Io = std.Io;2const Io = std.Io;
33
4pub fn main() !void {4pub fn main(init: std.process.Init.Minimal) !void {
5 // make sure safety checks are enabled even in release modes5 // make sure safety checks are enabled even in release modes
6 var gpa_state = std.heap.GeneralPurposeAllocator(.{ .safety = true }){};6 var gpa_state: std.heap.GeneralPurposeAllocator(.{ .safety = true }) = .{};
7 defer if (gpa_state.deinit() != .ok) {7 defer if (gpa_state.deinit() != .ok) {
8 @panic("found memory leaks");8 @panic("found memory leaks");
9 };9 };
10 const gpa = gpa_state.allocator();10 const gpa = gpa_state.allocator();
1111
12 var it = try std.process.argsWithAllocator(gpa);12 const process_cwd_path = try std.process.getCwdAlloc(gpa);
13 defer gpa.free(process_cwd_path);
14
15 var environ_map = try init.environ.createMap(gpa);
16 defer environ_map.deinit();
17
18 var it = try init.args.iterateAllocator(gpa);
13 defer it.deinit();19 defer it.deinit();
14 _ = it.next() orelse unreachable; // skip binary name20 _ = it.next() orelse unreachable; // skip binary name
15 const child_path, const needs_free = child_path: {21 const child_path, const needs_free = child_path: {
...@@ -17,19 +23,24 @@ pub fn main() !void {...@@ -17,19 +23,24 @@ pub fn main() !void {
17 const cwd_path = it.next() orelse break :child_path .{ child_path, false };23 const cwd_path = it.next() orelse break :child_path .{ child_path, false };
18 // If there is a third argument, it is the current CWD somewhere within the cache directory.24 // If there is a third argument, it is the current CWD somewhere within the cache directory.
19 // In that case, modify the child path in order to test spawning a path with a leading `..` component.25 // In that case, modify the child path in order to test spawning a path with a leading `..` component.
20 break :child_path .{ try std.fs.path.relative(gpa, cwd_path, child_path), true };26 break :child_path .{ try std.fs.path.relative(gpa, process_cwd_path, &environ_map, cwd_path, child_path), true };
21 };27 };
22 defer if (needs_free) gpa.free(child_path);28 defer if (needs_free) gpa.free(child_path);
2329
24 var threaded: Io.Threaded = .init(gpa, .{});30 var threaded: Io.Threaded = .init(gpa, .{
31 .argv0 = .init(init.args),
32 .environ = init.environ,
33 });
25 defer threaded.deinit();34 defer threaded.deinit();
26 const io = threaded.io();35 const io = threaded.io();
2736
28 var child = std.process.Child.init(&.{ child_path, "hello arg" }, gpa);37 var child = try std.process.spawn(io, .{
29 child.stdin_behavior = .Pipe;38 .argv = &.{ child_path, "hello arg" },
30 child.stdout_behavior = .Pipe;39 .stdin = .pipe,
31 child.stderr_behavior = .Inherit;40 .stdout = .pipe,
32 try child.spawn(io);41 .stderr = .inherit,
42 });
43
33 const child_stdin = child.stdin.?;44 const child_stdin = child.stdin.?;
34 try child_stdin.writeStreamingAll(io, "hello from stdin"); // verified in child45 try child_stdin.writeStreamingAll(io, "hello from stdin"); // verified in child
35 child_stdin.close(io);46 child_stdin.close(io);
...@@ -44,7 +55,7 @@ pub fn main() !void {...@@ -44,7 +55,7 @@ pub fn main() !void {
44 }55 }
4556
46 switch (try child.wait(io)) {57 switch (try child.wait(io)) {
47 .Exited => |code| {58 .exited => |code| {
48 const child_ok_code = 42; // set by child if no test errors59 const child_ok_code = 42; // set by child if no test errors
49 if (code != child_ok_code) {60 if (code != child_ok_code) {
50 testError(io, "child exit code: {d}; want {d}", .{ code, child_ok_code });61 testError(io, "child exit code: {d}; want {d}", .{ code, child_ok_code });
...@@ -57,7 +68,7 @@ pub fn main() !void {...@@ -57,7 +68,7 @@ pub fn main() !void {
57 // Check that FileNotFound is consistent across platforms when trying to spawn an executable that doesn't exist68 // Check that FileNotFound is consistent across platforms when trying to spawn an executable that doesn't exist
58 const missing_child_path = try std.mem.concat(gpa, u8, &.{ child_path, "_intentionally_missing" });69 const missing_child_path = try std.mem.concat(gpa, u8, &.{ child_path, "_intentionally_missing" });
59 defer gpa.free(missing_child_path);70 defer gpa.free(missing_child_path);
60 try std.testing.expectError(error.FileNotFound, std.process.Child.run(gpa, io, .{ .argv = &.{missing_child_path} }));71 try std.testing.expectError(error.FileNotFound, std.process.run(gpa, io, .{ .argv = &.{missing_child_path} }));
61}72}
6273
63var parent_test_error = false;74var parent_test_error = false;
test/standalone/cmakedefine/check.zig+4-8
...@@ -1,16 +1,12 @@...@@ -1,16 +1,12 @@
1pub fn main() !void {1pub fn main(init: std.process.Init) !void {
2 var arena_state: std.heap.ArenaAllocator = .init(std.heap.page_allocator);2 const arena = init.arena.allocator();
3 defer arena_state.deinit();3 const io = init.io;
4 const arena = arena_state.allocator();4 const args = try init.minimal.args.toSlice(arena);
5
6 const args = try std.process.argsAlloc(arena);
75
8 if (args.len != 3) return error.BadUsage;6 if (args.len != 3) return error.BadUsage;
9 const actual_path = args[1];7 const actual_path = args[1];
10 const expected_path = args[2];8 const expected_path = args[2];
119
12 const io = std.Io.Threaded.global_single_threaded.ioBasic();
13
14 const actual = try std.Io.Dir.cwd().readFileAlloc(io, actual_path, arena, .limited(1024 * 1024));10 const actual = try std.Io.Dir.cwd().readFileAlloc(io, actual_path, arena, .limited(1024 * 1024));
15 const expected = try std.Io.Dir.cwd().readFileAlloc(io, expected_path, arena, .limited(1024 * 1024));11 const expected = try std.Io.Dir.cwd().readFileAlloc(io, expected_path, arena, .limited(1024 * 1024));
1612
test/standalone/coff_dwarf/main.zig+3-8
...@@ -3,18 +3,13 @@ const fatal = std.process.fatal;...@@ -3,18 +3,13 @@ const fatal = std.process.fatal;
33
4extern fn add(a: u32, b: u32, addr: *usize) u32;4extern fn add(a: u32, b: u32, addr: *usize) u32;
55
6pub fn main() void {6pub fn main(init: std.process.Init) void {
7 var debug_alloc_inst: std.heap.DebugAllocator(.{}) = .init;7 const gpa = init.gpa;
8 defer std.debug.assert(debug_alloc_inst.deinit() == .ok);8 const io = init.io;
9 const gpa = debug_alloc_inst.allocator();
109
11 var di: std.debug.SelfInfo = .init;10 var di: std.debug.SelfInfo = .init;
12 defer di.deinit(gpa);11 defer di.deinit(gpa);
1312
14 var threaded: std.Io.Threaded = .init(gpa, .{});
15 defer threaded.deinit();
16 const io = threaded.io();
17
18 var add_addr: usize = undefined;13 var add_addr: usize = undefined;
19 _ = add(1, 2, &add_addr);14 _ = add(1, 2, &add_addr);
2015
test/standalone/dirname/exists_in.zig+2-10
...@@ -11,16 +11,8 @@...@@ -11,16 +11,8 @@
1111
12const std = @import("std");12const std = @import("std");
1313
14pub fn main() !void {14pub fn main(init: std.process.Init) !void {
15 var arena_state = std.heap.ArenaAllocator.init(std.heap.page_allocator);15 var args = try init.minimal.args.iterateAllocator(init.gpa);
16 const arena = arena_state.allocator();
17 defer arena_state.deinit();
18
19 try run(arena);
20}
21
22fn run(allocator: std.mem.Allocator) !void {
23 var args = try std.process.argsWithAllocator(allocator);
24 defer args.deinit();16 defer args.deinit();
25 _ = args.next() orelse unreachable; // skip binary name17 _ = args.next() orelse unreachable; // skip binary name
2618
test/standalone/dirname/has_basename.zig+2-10
...@@ -13,16 +13,8 @@...@@ -13,16 +13,8 @@
1313
14const std = @import("std");14const std = @import("std");
1515
16pub fn main() !void {16pub fn main(init: std.process.Init) !void {
17 var arena_state = std.heap.ArenaAllocator.init(std.heap.page_allocator);17 var args = try init.minimal.args.iterateAllocator(init.gpa);
18 const arena = arena_state.allocator();
19 defer arena_state.deinit();
20
21 try run(arena);
22}
23
24fn run(allocator: std.mem.Allocator) !void {
25 var args = try std.process.argsWithAllocator(allocator);
26 defer args.deinit();18 defer args.deinit();
27 _ = args.next() orelse unreachable; // skip binary name19 _ = args.next() orelse unreachable; // skip binary name
2820
test/standalone/dirname/touch.zig+2-10
...@@ -8,16 +8,8 @@...@@ -8,16 +8,8 @@
88
9const std = @import("std");9const std = @import("std");
1010
11pub fn main() !void {11pub fn main(init: std.process.Init) !void {
12 var arena_state = std.heap.ArenaAllocator.init(std.heap.page_allocator);12 var args = try init.minimal.args.iterateAllocator(init.gpa);
13 const arena = arena_state.allocator();
14 defer arena_state.deinit();
15
16 try run(arena);
17}
18
19fn run(allocator: std.mem.Allocator) !void {
20 var args = try std.process.argsWithAllocator(allocator);
21 defer args.deinit();13 defer args.deinit();
22 _ = args.next() orelse unreachable; // skip binary name14 _ = args.next() orelse unreachable; // skip binary name
2315
test/standalone/empty_env/build.zig+1-1
...@@ -7,7 +7,7 @@ pub fn build(b: *std.Build) void {...@@ -7,7 +7,7 @@ pub fn build(b: *std.Build) void {
77
8 const optimize: std.builtin.OptimizeMode = .Debug;8 const optimize: std.builtin.OptimizeMode = .Debug;
99
10 if (builtin.os.tag == .windows and std.process.hasEnvVarConstant("ConEmuHWND")) {10 if (builtin.os.tag == .windows and b.graph.environ_map.contains("ConEmuHWND")) {
11 // ConEmu injects environment variables into processes before they are executed11 // ConEmu injects environment variables into processes before they are executed
12 // depending on user settings. This obviously invalidates the test, so skipping12 // depending on user settings. This obviously invalidates the test, so skipping
13 // it is the best option.13 // it is the best option.
test/standalone/empty_env/main.zig+2-5
...@@ -1,8 +1,5 @@...@@ -1,8 +1,5 @@
1const std = @import("std");1const std = @import("std");
22
3pub fn main() !void {3pub fn main(init: std.process.Init) !void {
4 var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init;4 try std.testing.expectEqual(0, init.environ_map.count());
5 defer _ = gpa.deinit();
6 const env_map = std.process.getEnvMap(gpa.allocator()) catch @panic("unable to get env map");
7 try std.testing.expect(env_map.count() == 0);
8}5}
test/standalone/entry_point/check_differ.zig+6-8
...@@ -1,12 +1,11 @@...@@ -1,12 +1,11 @@
1pub fn main() !void {1const std = @import("std");
2 var arena_state: std.heap.ArenaAllocator = .init(std.heap.page_allocator);
3 defer arena_state.deinit();
4 const arena = arena_state.allocator();
52
6 const args = try std.process.argsAlloc(arena);3pub fn main(init: std.process.Init) !void {
7 if (args.len != 3) return error.BadUsage; // usage: 'check_differ <path a> <path b>'4 const arena = init.arena.allocator();
5 const io = init.io;
6 const args = try init.minimal.args.toSlice(arena);
87
9 const io = std.Io.Threaded.global_single_threaded.ioBasic();8 if (args.len != 3) return error.BadUsage; // usage: 'check_differ <path a> <path b>'
109
11 const contents_1 = try std.Io.Dir.cwd().readFileAlloc(io, args[1], arena, .limited(1024 * 1024 * 64)); // 64 MiB ought to be plenty10 const contents_1 = try std.Io.Dir.cwd().readFileAlloc(io, args[1], arena, .limited(1024 * 1024 * 64)); // 64 MiB ought to be plenty
12 const contents_2 = try std.Io.Dir.cwd().readFileAlloc(io, args[2], arena, .limited(1024 * 1024 * 64)); // 64 MiB ought to be plenty11 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 {...@@ -16,4 +15,3 @@ pub fn main() !void {
16 }15 }
17 // success, files differ16 // success, files differ
18}17}
19const std = @import("std");
test/standalone/env_vars/main.zig+89-113
...@@ -2,174 +2,150 @@ const std = @import("std");...@@ -2,174 +2,150 @@ const std = @import("std");
2const builtin = @import("builtin");2const builtin = @import("builtin");
33
4// Note: the environment variables under test are set by the build.zig4// Note: the environment variables under test are set by the build.zig
5pub fn main() !void {5pub fn main(init: std.process.Init) !void {
6 @setEvalBranchQuota(10000);6 @setEvalBranchQuota(10000);
77
8 var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init;8 const allocator = init.gpa;
9 defer _ = gpa.deinit();9 const arena = init.arena.allocator();
10 const allocator = gpa.allocator();10 const environ = init.minimal.environ;
1111
12 var arena_state = std.heap.ArenaAllocator.init(allocator);12 // containsUnempty
13 defer arena_state.deinit();
14 const arena = arena_state.allocator();
15
16 // hasNonEmptyEnvVar
17 {13 {
18 try std.testing.expect(try std.process.hasNonEmptyEnvVar(allocator, "FOO"));14 try std.testing.expect(try environ.containsUnempty(allocator, "FOO"));
19 try std.testing.expect(!(try std.process.hasNonEmptyEnvVar(allocator, "FOO=")));15 try std.testing.expect(!(try environ.containsUnempty(allocator, "FOO=")));
20 try std.testing.expect(!(try std.process.hasNonEmptyEnvVar(allocator, "FO")));16 try std.testing.expect(!(try environ.containsUnempty(allocator, "FO")));
21 try std.testing.expect(!(try std.process.hasNonEmptyEnvVar(allocator, "FOOO")));17 try std.testing.expect(!(try environ.containsUnempty(allocator, "FOOO")));
22 if (builtin.os.tag == .windows) {18 if (builtin.os.tag == .windows) {
23 try std.testing.expect(try std.process.hasNonEmptyEnvVar(allocator, "foo"));19 try std.testing.expect(try environ.containsUnempty(allocator, "foo"));
24 }20 }
25 try std.testing.expect(try std.process.hasNonEmptyEnvVar(allocator, "EQUALS"));21 try std.testing.expect(try environ.containsUnempty(allocator, "EQUALS"));
26 try std.testing.expect(!(try std.process.hasNonEmptyEnvVar(allocator, "EQUALS=ABC")));22 try std.testing.expect(!(try environ.containsUnempty(allocator, "EQUALS=ABC")));
27 try std.testing.expect(try std.process.hasNonEmptyEnvVar(allocator, "КИРиллИЦА"));23 try std.testing.expect(try environ.containsUnempty(allocator, "КИРиллИЦА"));
28 if (builtin.os.tag == .windows) {24 if (builtin.os.tag == .windows) {
29 try std.testing.expect(try std.process.hasNonEmptyEnvVar(allocator, "кирИЛЛица"));25 try std.testing.expect(try environ.containsUnempty(allocator, "кирИЛЛица"));
30 }26 }
31 try std.testing.expect(!(try std.process.hasNonEmptyEnvVar(allocator, "NO_VALUE")));27 try std.testing.expect(!(try environ.containsUnempty(allocator, "NO_VALUE")));
32 try std.testing.expect(!(try std.process.hasNonEmptyEnvVar(allocator, "NOT_SET")));28 try std.testing.expect(!(try environ.containsUnempty(allocator, "NOT_SET")));
33 if (builtin.os.tag == .windows) {29 if (builtin.os.tag == .windows) {
34 try std.testing.expect(try std.process.hasNonEmptyEnvVar(allocator, "=HIDDEN"));30 try std.testing.expect(try environ.containsUnempty(allocator, "=HIDDEN"));
35 try std.testing.expect(try std.process.hasNonEmptyEnvVar(allocator, "INVALID_UTF16_\xed\xa0\x80"));31 try std.testing.expect(try environ.containsUnempty(allocator, "INVALID_UTF16_\xed\xa0\x80"));
36 }32 }
37 }33 }
3834
39 // hasNonEmptyEnvVarContstant35 // containsUnemptyConstant
40 {36 {
41 try std.testing.expect(std.process.hasNonEmptyEnvVarConstant("FOO"));37 try std.testing.expect(environ.containsUnemptyConstant("FOO"));
42 try std.testing.expect(!std.process.hasNonEmptyEnvVarConstant("FOO="));38 try std.testing.expect(!environ.containsUnemptyConstant("FOO="));
43 try std.testing.expect(!std.process.hasNonEmptyEnvVarConstant("FO"));39 try std.testing.expect(!environ.containsUnemptyConstant("FO"));
44 try std.testing.expect(!std.process.hasNonEmptyEnvVarConstant("FOOO"));40 try std.testing.expect(!environ.containsUnemptyConstant("FOOO"));
45 if (builtin.os.tag == .windows) {41 if (builtin.os.tag == .windows) {
46 try std.testing.expect(std.process.hasNonEmptyEnvVarConstant("foo"));42 try std.testing.expect(environ.containsUnemptyConstant("foo"));
47 }43 }
48 try std.testing.expect(std.process.hasNonEmptyEnvVarConstant("EQUALS"));44 try std.testing.expect(environ.containsUnemptyConstant("EQUALS"));
49 try std.testing.expect(!std.process.hasNonEmptyEnvVarConstant("EQUALS=ABC"));45 try std.testing.expect(!environ.containsUnemptyConstant("EQUALS=ABC"));
50 try std.testing.expect(std.process.hasNonEmptyEnvVarConstant("КИРиллИЦА"));46 try std.testing.expect(environ.containsUnemptyConstant("КИРиллИЦА"));
51 if (builtin.os.tag == .windows) {47 if (builtin.os.tag == .windows) {
52 try std.testing.expect(std.process.hasNonEmptyEnvVarConstant("кирИЛЛица"));48 try std.testing.expect(environ.containsUnemptyConstant("кирИЛЛица"));
53 }49 }
54 try std.testing.expect(!(std.process.hasNonEmptyEnvVarConstant("NO_VALUE")));50 try std.testing.expect(!(environ.containsUnemptyConstant("NO_VALUE")));
55 try std.testing.expect(!(std.process.hasNonEmptyEnvVarConstant("NOT_SET")));51 try std.testing.expect(!(environ.containsUnemptyConstant("NOT_SET")));
56 if (builtin.os.tag == .windows) {52 if (builtin.os.tag == .windows) {
57 try std.testing.expect(std.process.hasNonEmptyEnvVarConstant("=HIDDEN"));53 try std.testing.expect(environ.containsUnemptyConstant("=HIDDEN"));
58 try std.testing.expect(std.process.hasNonEmptyEnvVarConstant("INVALID_UTF16_\xed\xa0\x80"));54 try std.testing.expect(environ.containsUnemptyConstant("INVALID_UTF16_\xed\xa0\x80"));
59 }55 }
60 }56 }
6157
62 // hasEnvVar58 // contains
63 {59 {
64 try std.testing.expect(try std.process.hasEnvVar(allocator, "FOO"));60 try std.testing.expect(try environ.contains(allocator, "FOO"));
65 try std.testing.expect(!(try std.process.hasEnvVar(allocator, "FOO=")));61 try std.testing.expect(!(try environ.contains(allocator, "FOO=")));
66 try std.testing.expect(!(try std.process.hasEnvVar(allocator, "FO")));62 try std.testing.expect(!(try environ.contains(allocator, "FO")));
67 try std.testing.expect(!(try std.process.hasEnvVar(allocator, "FOOO")));63 try std.testing.expect(!(try environ.contains(allocator, "FOOO")));
68 if (builtin.os.tag == .windows) {64 if (builtin.os.tag == .windows) {
69 try std.testing.expect(try std.process.hasEnvVar(allocator, "foo"));65 try std.testing.expect(try environ.contains(allocator, "foo"));
70 }66 }
71 try std.testing.expect(try std.process.hasEnvVar(allocator, "EQUALS"));67 try std.testing.expect(try environ.contains(allocator, "EQUALS"));
72 try std.testing.expect(!(try std.process.hasEnvVar(allocator, "EQUALS=ABC")));68 try std.testing.expect(!(try environ.contains(allocator, "EQUALS=ABC")));
73 try std.testing.expect(try std.process.hasEnvVar(allocator, "КИРиллИЦА"));69 try std.testing.expect(try environ.contains(allocator, "КИРиллИЦА"));
74 if (builtin.os.tag == .windows) {70 if (builtin.os.tag == .windows) {
75 try std.testing.expect(try std.process.hasEnvVar(allocator, "кирИЛЛица"));71 try std.testing.expect(try environ.contains(allocator, "кирИЛЛица"));
76 }72 }
77 try std.testing.expect(try std.process.hasEnvVar(allocator, "NO_VALUE"));73 try std.testing.expect(try environ.contains(allocator, "NO_VALUE"));
78 try std.testing.expect(!(try std.process.hasEnvVar(allocator, "NOT_SET")));74 try std.testing.expect(!(try environ.contains(allocator, "NOT_SET")));
79 if (builtin.os.tag == .windows) {75 if (builtin.os.tag == .windows) {
80 try std.testing.expect(try std.process.hasEnvVar(allocator, "=HIDDEN"));76 try std.testing.expect(try environ.contains(allocator, "=HIDDEN"));
81 try std.testing.expect(try std.process.hasEnvVar(allocator, "INVALID_UTF16_\xed\xa0\x80"));77 try std.testing.expect(try environ.contains(allocator, "INVALID_UTF16_\xed\xa0\x80"));
82 }78 }
83 }79 }
8480
85 // hasEnvVarConstant81 // containsConstant
86 {82 {
87 try std.testing.expect(std.process.hasEnvVarConstant("FOO"));83 try std.testing.expect(environ.containsConstant("FOO"));
88 try std.testing.expect(!std.process.hasEnvVarConstant("FOO="));84 try std.testing.expect(!environ.containsConstant("FOO="));
89 try std.testing.expect(!std.process.hasEnvVarConstant("FO"));85 try std.testing.expect(!environ.containsConstant("FO"));
90 try std.testing.expect(!std.process.hasEnvVarConstant("FOOO"));86 try std.testing.expect(!environ.containsConstant("FOOO"));
91 if (builtin.os.tag == .windows) {87 if (builtin.os.tag == .windows) {
92 try std.testing.expect(std.process.hasEnvVarConstant("foo"));88 try std.testing.expect(environ.containsConstant("foo"));
93 }89 }
94 try std.testing.expect(std.process.hasEnvVarConstant("EQUALS"));90 try std.testing.expect(environ.containsConstant("EQUALS"));
95 try std.testing.expect(!std.process.hasEnvVarConstant("EQUALS=ABC"));91 try std.testing.expect(!environ.containsConstant("EQUALS=ABC"));
96 try std.testing.expect(std.process.hasEnvVarConstant("КИРиллИЦА"));92 try std.testing.expect(environ.containsConstant("КИРиллИЦА"));
97 if (builtin.os.tag == .windows) {93 if (builtin.os.tag == .windows) {
98 try std.testing.expect(std.process.hasEnvVarConstant("кирИЛЛица"));94 try std.testing.expect(environ.containsConstant("кирИЛЛица"));
99 }95 }
100 try std.testing.expect(std.process.hasEnvVarConstant("NO_VALUE"));96 try std.testing.expect(environ.containsConstant("NO_VALUE"));
101 try std.testing.expect(!(std.process.hasEnvVarConstant("NOT_SET")));97 try std.testing.expect(!(environ.containsConstant("NOT_SET")));
102 if (builtin.os.tag == .windows) {98 if (builtin.os.tag == .windows) {
103 try std.testing.expect(std.process.hasEnvVarConstant("=HIDDEN"));99 try std.testing.expect(environ.containsConstant("=HIDDEN"));
104 try std.testing.expect(std.process.hasEnvVarConstant("INVALID_UTF16_\xed\xa0\x80"));100 try std.testing.expect(environ.containsConstant("INVALID_UTF16_\xed\xa0\x80"));
105 }101 }
106 }102 }
107103
108 // getEnvVarOwned104 // getAlloc
109 {105 {
110 try std.testing.expectEqualSlices(u8, "123", try std.process.getEnvVarOwned(arena, "FOO"));106 try std.testing.expectEqualSlices(u8, "123", try environ.getAlloc(arena, "FOO"));
111 try std.testing.expectError(error.EnvironmentVariableNotFound, std.process.getEnvVarOwned(arena, "FOO="));107 try std.testing.expectError(error.EnvironmentVariableMissing, environ.getAlloc(arena, "FOO="));
112 try std.testing.expectError(error.EnvironmentVariableNotFound, std.process.getEnvVarOwned(arena, "FO"));108 try std.testing.expectError(error.EnvironmentVariableMissing, environ.getAlloc(arena, "FO"));
113 try std.testing.expectError(error.EnvironmentVariableNotFound, std.process.getEnvVarOwned(arena, "FOOO"));109 try std.testing.expectError(error.EnvironmentVariableMissing, environ.getAlloc(arena, "FOOO"));
114 if (builtin.os.tag == .windows) {
115 try std.testing.expectEqualSlices(u8, "123", try std.process.getEnvVarOwned(arena, "foo"));
116 }
117 try std.testing.expectEqualSlices(u8, "ABC=123", try std.process.getEnvVarOwned(arena, "EQUALS"));
118 try std.testing.expectError(error.EnvironmentVariableNotFound, std.process.getEnvVarOwned(arena, "EQUALS=ABC"));
119 try std.testing.expectEqualSlices(u8, "non-ascii አማርኛ \u{10FFFF}", try std.process.getEnvVarOwned(arena, "КИРиллИЦА"));
120 if (builtin.os.tag == .windows) {
121 try std.testing.expectEqualSlices(u8, "non-ascii አማርኛ \u{10FFFF}", try std.process.getEnvVarOwned(arena, "кирИЛЛица"));
122 }
123 try std.testing.expectEqualSlices(u8, "", try std.process.getEnvVarOwned(arena, "NO_VALUE"));
124 try std.testing.expectError(error.EnvironmentVariableNotFound, std.process.getEnvVarOwned(arena, "NOT_SET"));
125 if (builtin.os.tag == .windows) {110 if (builtin.os.tag == .windows) {
126 try std.testing.expectEqualSlices(u8, "hi", try std.process.getEnvVarOwned(arena, "=HIDDEN"));111 try std.testing.expectEqualSlices(u8, "123", try environ.getAlloc(arena, "foo"));
127 try std.testing.expectEqualSlices(u8, "\xed\xa0\x80", try std.process.getEnvVarOwned(arena, "INVALID_UTF16_\xed\xa0\x80"));
128 }112 }
129 }113 try std.testing.expectEqualSlices(u8, "ABC=123", try environ.getAlloc(arena, "EQUALS"));
130114 try std.testing.expectError(error.EnvironmentVariableMissing, environ.getAlloc(arena, "EQUALS=ABC"));
131 // parseEnvVarInt115 try std.testing.expectEqualSlices(u8, "non-ascii አማርኛ \u{10FFFF}", try environ.getAlloc(arena, "КИРиллИЦА"));
132 {
133 try std.testing.expectEqual(123, try std.process.parseEnvVarInt("FOO", u32, 10));
134 try std.testing.expectError(error.EnvironmentVariableNotFound, std.process.parseEnvVarInt("FO", u32, 10));
135 try std.testing.expectError(error.EnvironmentVariableNotFound, std.process.parseEnvVarInt("FOOO", u32, 10));
136 try std.testing.expectEqual(0x123, try std.process.parseEnvVarInt("FOO", u32, 16));
137 if (builtin.os.tag == .windows) {116 if (builtin.os.tag == .windows) {
138 try std.testing.expectEqual(123, try std.process.parseEnvVarInt("foo", u32, 10));117 try std.testing.expectEqualSlices(u8, "non-ascii አማርኛ \u{10FFFF}", try environ.getAlloc(arena, "кирИЛЛица"));
139 }118 }
140 try std.testing.expectError(error.InvalidCharacter, std.process.parseEnvVarInt("EQUALS", u32, 10));119 try std.testing.expectEqualSlices(u8, "", try environ.getAlloc(arena, "NO_VALUE"));
141 try std.testing.expectError(error.EnvironmentVariableNotFound, std.process.parseEnvVarInt("EQUALS=ABC", u32, 10));120 try std.testing.expectError(error.EnvironmentVariableMissing, environ.getAlloc(arena, "NOT_SET"));
142 try std.testing.expectError(error.InvalidCharacter, std.process.parseEnvVarInt("КИРиллИЦА", u32, 10));
143 try std.testing.expectError(error.InvalidCharacter, std.process.parseEnvVarInt("NO_VALUE", u32, 10));
144 try std.testing.expectError(error.EnvironmentVariableNotFound, std.process.parseEnvVarInt("NOT_SET", u32, 10));
145 if (builtin.os.tag == .windows) {121 if (builtin.os.tag == .windows) {
146 try std.testing.expectError(error.InvalidCharacter, std.process.parseEnvVarInt("=HIDDEN", u32, 10));122 try std.testing.expectEqualSlices(u8, "hi", try environ.getAlloc(arena, "=HIDDEN"));
147 try std.testing.expectError(error.InvalidCharacter, std.process.parseEnvVarInt("INVALID_UTF16_\xed\xa0\x80", u32, 10));123 try std.testing.expectEqualSlices(u8, "\xed\xa0\x80", try environ.getAlloc(arena, "INVALID_UTF16_\xed\xa0\x80"));
148 }124 }
149 }125 }
150126
151 // EnvMap127 // Environ.Map
152 {128 {
153 var env_map = try std.process.getEnvMap(allocator);129 var environ_map = try environ.createMap(allocator);
154 defer env_map.deinit();130 defer environ_map.deinit();
155131
156 try std.testing.expectEqualSlices(u8, "123", env_map.get("FOO").?);132 try std.testing.expectEqualSlices(u8, "123", environ_map.get("FOO").?);
157 try std.testing.expectEqual(null, env_map.get("FO"));133 try std.testing.expectEqual(null, environ_map.get("FO"));
158 try std.testing.expectEqual(null, env_map.get("FOOO"));134 try std.testing.expectEqual(null, environ_map.get("FOOO"));
159 if (builtin.os.tag == .windows) {135 if (builtin.os.tag == .windows) {
160 try std.testing.expectEqualSlices(u8, "123", env_map.get("foo").?);136 try std.testing.expectEqualSlices(u8, "123", environ_map.get("foo").?);
161 }137 }
162 try std.testing.expectEqualSlices(u8, "ABC=123", env_map.get("EQUALS").?);138 try std.testing.expectEqualSlices(u8, "ABC=123", environ_map.get("EQUALS").?);
163 try std.testing.expectEqual(null, env_map.get("EQUALS=ABC"));139 try std.testing.expectEqual(null, environ_map.get("EQUALS=ABC"));
164 try std.testing.expectEqualSlices(u8, "non-ascii አማርኛ \u{10FFFF}", env_map.get("КИРиллИЦА").?);140 try std.testing.expectEqualSlices(u8, "non-ascii አማርኛ \u{10FFFF}", environ_map.get("КИРиллИЦА").?);
165 if (builtin.os.tag == .windows) {141 if (builtin.os.tag == .windows) {
166 try std.testing.expectEqualSlices(u8, "non-ascii አማርኛ \u{10FFFF}", env_map.get("кирИЛЛица").?);142 try std.testing.expectEqualSlices(u8, "non-ascii አማርኛ \u{10FFFF}", environ_map.get("кирИЛЛица").?);
167 }143 }
168 try std.testing.expectEqualSlices(u8, "", env_map.get("NO_VALUE").?);144 try std.testing.expectEqualSlices(u8, "", environ_map.get("NO_VALUE").?);
169 try std.testing.expectEqual(null, env_map.get("NOT_SET"));145 try std.testing.expectEqual(null, environ_map.get("NOT_SET"));
170 if (builtin.os.tag == .windows) {146 if (builtin.os.tag == .windows) {
171 try std.testing.expectEqualSlices(u8, "hi", env_map.get("=HIDDEN").?);147 try std.testing.expectEqualSlices(u8, "hi", environ_map.get("=HIDDEN").?);
172 try std.testing.expectEqualSlices(u8, "\xed\xa0\x80", env_map.get("INVALID_UTF16_\xed\xa0\x80").?);148 try std.testing.expectEqualSlices(u8, "\xed\xa0\x80", environ_map.get("INVALID_UTF16_\xed\xa0\x80").?);
173 }149 }
174 }150 }
175}151}
test/standalone/install_headers/check_exists.zig+4-8
...@@ -2,17 +2,13 @@ const std = @import("std");...@@ -2,17 +2,13 @@ const std = @import("std");
22
3/// Checks the existence of files relative to cwd.3/// Checks the existence of files relative to cwd.
4/// A path starting with ! should not exist.4/// A path starting with ! should not exist.
5pub fn main() !void {5pub fn main(init: std.process.Init) !void {
6 var arena_state = std.heap.ArenaAllocator.init(std.heap.page_allocator);6 const arena = init.arena.allocator();
7 defer arena_state.deinit();7 const io = init.io;
88
9 const arena = arena_state.allocator();9 var arg_it = try init.minimal.args.iterateAllocator(arena);
10
11 var arg_it = try std.process.argsWithAllocator(arena);
12 _ = arg_it.next();10 _ = arg_it.next();
1311
14 const io = std.Io.Threaded.global_single_threaded.ioBasic();
15
16 const cwd = std.Io.Dir.cwd();12 const cwd = std.Io.Dir.cwd();
17 const cwd_realpath = try cwd.realPathFileAlloc(io, ".", arena);13 const cwd_realpath = try cwd.realPathFileAlloc(io, ".", arena);
1814
test/standalone/libfuzzer/main.zig+4-9
...@@ -6,19 +6,14 @@ fn testOne(in: abi.Slice) callconv(.c) void {...@@ -6,19 +6,14 @@ fn testOne(in: abi.Slice) callconv(.c) void {
6 std.debug.assertReadable(in.toSlice());6 std.debug.assertReadable(in.toSlice());
7}7}
88
9pub fn main() !void {9pub fn main(init: std.process.Init) !void {
10 var debug_gpa_ctx: std.heap.DebugAllocator(.{}) = .init;10 const gpa = init.gpa;
11 defer _ = debug_gpa_ctx.deinit();11 const io = init.io;
12 const gpa = debug_gpa_ctx.allocator();
1312
14 var args = try std.process.argsWithAllocator(gpa);13 var args = try init.minimal.args.iterateAllocator(gpa);
15 defer args.deinit();14 defer args.deinit();
16 _ = args.skip(); // executable name15 _ = args.skip(); // executable name
1716
18 var threaded: std.Io.Threaded = .init(gpa, .{});
19 defer threaded.deinit();
20 const io = threaded.io();
21
22 const cache_dir_path = args.next() orelse @panic("expected cache directory path argument");17 const cache_dir_path = args.next() orelse @panic("expected cache directory path argument");
23 var cache_dir = try std.Io.Dir.cwd().openDir(io, cache_dir_path, .{});18 var cache_dir = try std.Io.Dir.cwd().openDir(io, cache_dir_path, .{});
24 defer cache_dir.close(io);19 defer cache_dir.close(io);
test/standalone/load_dynamic_library/main.zig+2-5
...@@ -1,10 +1,7 @@...@@ -1,10 +1,7 @@
1const std = @import("std");1const std = @import("std");
22
3pub fn main() !void {3pub fn main(init: std.process.Init) !void {
4 var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init;4 const args = try init.minimal.args.toSlice(init.arena.allocator());
5 defer _ = gpa.deinit();
6 const args = try std.process.argsAlloc(gpa.allocator());
7 defer std.process.argsFree(gpa.allocator(), args);
85
9 const dynlib_name = args[1];6 const dynlib_name = args[1];
107
test/standalone/posix/cwd.zig+5-13
...@@ -7,24 +7,16 @@ const assert = std.debug.assert;...@@ -7,24 +7,16 @@ const assert = std.debug.assert;
77
8const path_max = std.fs.max_path_bytes;8const path_max = std.fs.max_path_bytes;
99
10pub fn main() !void {10pub fn main(init: std.process.Init) !void {
11 switch (builtin.target.os.tag) {11 switch (builtin.target.os.tag) {
12 .wasi => return, // WASI doesn't support changing the working directory at all.12 .wasi => return, // WASI doesn't support changing the working directory at all.
13 .windows => return, // POSIX is not implemented by Windows13 .windows => return, // POSIX is not implemented by Windows
14 else => {},14 else => {},
15 }15 }
1616
17 var debug_allocator: std.heap.DebugAllocator(.{}) = .{};
18 defer assert(debug_allocator.deinit() == .ok);
19 const gpa = debug_allocator.allocator();
20
21 var threaded: std.Io.Threaded = .init(gpa, .{});
22 defer threaded.deinit();
23 const io = threaded.io();
24
25 try test_chdir_self();17 try test_chdir_self();
26 try test_chdir_absolute();18 try test_chdir_absolute();
27 try test_chdir_relative(gpa, io);19 try test_chdir_relative(init.gpa, init.io);
28}20}
2921
30// get current working directory and expect it to match given path22// get current working directory and expect it to match given path
...@@ -39,7 +31,7 @@ fn test_chdir_self() !void {...@@ -39,7 +31,7 @@ fn test_chdir_self() !void {
39 const old_cwd = try std.posix.getcwd(old_cwd_buf[0..]);31 const old_cwd = try std.posix.getcwd(old_cwd_buf[0..]);
4032
41 // Try changing to the current directory33 // Try changing to the current directory
42 try std.posix.chdir(old_cwd);34 try std.Io.Threaded.chdir(old_cwd);
43 try expect_cwd(old_cwd);35 try expect_cwd(old_cwd);
44}36}
4537
...@@ -50,7 +42,7 @@ fn test_chdir_absolute() !void {...@@ -50,7 +42,7 @@ fn test_chdir_absolute() !void {
50 const parent = std.fs.path.dirname(old_cwd) orelse unreachable; // old_cwd should be absolute42 const parent = std.fs.path.dirname(old_cwd) orelse unreachable; // old_cwd should be absolute
5143
52 // Try changing to the parent via a full path44 // Try changing to the parent via a full path
53 try std.posix.chdir(parent);45 try std.Io.Threaded.chdir(parent);
5446
55 try expect_cwd(parent);47 try expect_cwd(parent);
56}48}
...@@ -71,7 +63,7 @@ fn test_chdir_relative(gpa: Allocator, io: Io) !void {...@@ -71,7 +63,7 @@ fn test_chdir_relative(gpa: Allocator, io: Io) !void {
71 defer gpa.free(expected_path);63 defer gpa.free(expected_path);
7264
73 // change current working directory to new test directory65 // change current working directory to new test directory
74 try std.posix.chdir(relative_dir_name);66 try std.Io.Threaded.chdir(relative_dir_name);
7567
76 var new_cwd_buf: [path_max]u8 = undefined;68 var new_cwd_buf: [path_max]u8 = undefined;
77 const new_cwd = try std.posix.getcwd(new_cwd_buf[0..]);69 const new_cwd = try std.posix.getcwd(new_cwd_buf[0..]);
test/standalone/posix/getenv.zig+12-16
...@@ -1,32 +1,28 @@...@@ -1,32 +1,28 @@
1// test getting environment variables1//! test getting environment variables
22
3const std = @import("std");3const std = @import("std");
4const builtin = @import("builtin");4const builtin = @import("builtin");
55
6pub fn main() !void {6pub fn main(init: std.process.Init.Minimal) !void {
7 if (builtin.target.os.tag == .windows) {7 if (builtin.target.os.tag == .windows) return;
8 return; // Windows env strings are WTF-16, so not supported by Zig's std.posix.getenv()8 if (builtin.target.os.tag == .wasi and !builtin.link_libc) return;
9 }
109
11 if (builtin.target.os.tag == .wasi and !builtin.link_libc) {10 const environ = init.environ;
12 return; // std.posix.getenv is not supported on WASI due to the need of allocation
13 }
1411
15 // Test some unset env vars:12 // Test some unset env vars:
1613 try std.testing.expectEqual(environ.getPosix(""), null);
17 try std.testing.expectEqual(std.posix.getenv(""), null);14 try std.testing.expectEqual(environ.getPosix("BOGUSDOESNOTEXISTENVVAR"), null);
18 try std.testing.expectEqual(std.posix.getenv("BOGUSDOESNOTEXISTENVVAR"), null);15 try std.testing.expectEqual(environ.getPosix("BOGUSDOESNOTEXISTENVVAR"), null);
19 try std.testing.expectEqual(std.posix.getenvZ("BOGUSDOESNOTEXISTENVVAR"), null);
2016
21 if (builtin.link_libc) {17 if (builtin.link_libc) {
22 // Test if USER matches what C library sees18 // Test if USER matches what C library sees
23 const expected = std.mem.span(std.c.getenv("USER") orelse "");19 const expected = std.mem.span(std.c.getenv("USER") orelse "");
24 const actual = std.posix.getenv("USER") orelse "";20 const actual = environ.getPosix("USER") orelse "";
25 try std.testing.expectEqualStrings(expected, actual);21 try std.testing.expectEqualStrings(expected, actual);
26 }22 }
2723
28 // env vars set by our build.zig run step:24 // env vars set by our build.zig run step:
29 try std.testing.expectEqualStrings("", std.posix.getenv("ZIG_TEST_POSIX_EMPTY") orelse "invalid");25 try std.testing.expectEqualStrings("", environ.getPosix("ZIG_TEST_POSIX_EMPTY") orelse "invalid");
30 try std.testing.expectEqualStrings("test=variable", std.posix.getenv("ZIG_TEST_POSIX_1EQ") orelse "invalid");26 try std.testing.expectEqualStrings("test=variable", environ.getPosix("ZIG_TEST_POSIX_1EQ") orelse "invalid");
31 try std.testing.expectEqualStrings("=test=variable=", std.posix.getenv("ZIG_TEST_POSIX_3EQ") orelse "invalid");27 try std.testing.expectEqualStrings("=test=variable=", environ.getPosix("ZIG_TEST_POSIX_3EQ") orelse "invalid");
32}28}
test/standalone/posix/relpaths.zig+2-8
...@@ -6,16 +6,10 @@ const builtin = @import("builtin");...@@ -6,16 +6,10 @@ const builtin = @import("builtin");
6const std = @import("std");6const std = @import("std");
7const Io = std.Io;7const Io = std.Io;
88
9pub fn main() !void {9pub fn main(init: std.process.Init) !void {
10 if (builtin.target.os.tag == .wasi) return; // Can link, but can't change into tmpDir10 if (builtin.target.os.tag == .wasi) return; // Can link, but can't change into tmpDir
1111
12 var debug_allocator: std.heap.DebugAllocator(.{}) = .init;12 const io = init.io;
13 const gpa = debug_allocator.allocator();
14 defer std.debug.assert(debug_allocator.deinit() == .ok);
15
16 var threaded: std.Io.Threaded = .init(gpa, .{});
17 defer threaded.deinit();
18 const io = threaded.io();
1913
20 var tmp = tmpDir(io, .{});14 var tmp = tmpDir(io, .{});
21 defer tmp.cleanup(io);15 defer tmp.cleanup(io);
test/standalone/run_cwd/check_file_exists.zig+3-6
...@@ -1,9 +1,6 @@...@@ -1,9 +1,6 @@
1pub fn main() !void {1pub fn main(init: std.process.Init) !void {
2 var arena_state: std.heap.ArenaAllocator = .init(std.heap.page_allocator);2 const arena = init.arena.allocator();
3 defer arena_state.deinit();3 const args = try init.minimal.args.toSlice(arena);
4 const arena = arena_state.allocator();
5
6 const args = try std.process.argsAlloc(arena);
74
8 if (args.len != 2) return error.BadUsage;5 if (args.len != 2) return error.BadUsage;
9 const path = args[1];6 const path = args[1];
test/standalone/run_output_caching/main.zig+3-3
...@@ -1,8 +1,8 @@...@@ -1,8 +1,8 @@
1const std = @import("std");1const std = @import("std");
22
3pub fn main() !void {3pub fn main(init: std.process.Init) !void {
4 const io = std.Io.Threaded.global_single_threaded.ioBasic();4 const io = init.io;
5 var args = try std.process.argsWithAllocator(std.heap.page_allocator);5 var args = try init.minimal.args.iterateAllocator(init.arena.allocator());
6 _ = args.skip();6 _ = args.skip();
7 const filename = args.next().?;7 const filename = args.next().?;
8 const file = try std.Io.Dir.cwd().createFile(io, filename, .{});8 const file = try std.Io.Dir.cwd().createFile(io, filename, .{});
test/standalone/run_output_paths/create_file.zig+3-3
...@@ -1,8 +1,8 @@...@@ -1,8 +1,8 @@
1const std = @import("std");1const std = @import("std");
22
3pub fn main() !void {3pub fn main(init: std.process.Init) !void {
4 const io = std.Io.Threaded.global_single_threaded.ioBasic();4 const io = init.io;
5 var args = try std.process.argsWithAllocator(std.heap.page_allocator);5 var args = try init.minimal.args.iterateAllocator(init.arena.allocator());
6 _ = args.skip();6 _ = args.skip();
7 const dir_name = args.next().?;7 const dir_name = args.next().?;
8 const dir = try std.Io.Dir.cwd().openDir(io, if (std.mem.startsWith(u8, dir_name, "--dir="))8 const dir = try std.Io.Dir.cwd().openDir(io, if (std.mem.startsWith(u8, dir_name, "--dir="))
test/standalone/self_exe_symlink/create-symlink.zig+8-10
...@@ -1,21 +1,19 @@...@@ -1,21 +1,19 @@
1const std = @import("std");1const std = @import("std");
22
3pub fn main() anyerror!void {3pub fn main(init: std.process.Init) !void {
4 var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init;4 const io = init.io;
5 defer if (gpa.deinit() == .leak) @panic("found memory leaks");5 const gpa = init.gpa;
6 const allocator = gpa.allocator();6 var it = try init.minimal.args.iterateAllocator(gpa);
7
8 var it = try std.process.argsWithAllocator(allocator);
9 defer it.deinit();7 defer it.deinit();
10 _ = it.next() orelse unreachable; // skip binary name8 _ = it.next() orelse unreachable; // skip binary name
11 const exe_path = it.next() orelse unreachable;9 const exe_path = it.next() orelse unreachable;
12 const symlink_path = it.next() orelse unreachable;10 const symlink_path = it.next() orelse unreachable;
1311
14 // If `exe_path` is relative to our cwd, we need to convert it to be relative to the dirname of `symlink_path`.12 const cwd = try std.process.getCwdAlloc(init.arena.allocator());
15 const exe_rel_path = try std.fs.path.relative(allocator, std.fs.path.dirname(symlink_path) orelse ".", exe_path);
16 defer allocator.free(exe_rel_path);
1713
18 const io = std.Io.Threaded.global_single_threaded.ioBasic();14 // If `exe_path` is relative to our cwd, we need to convert it to be relative to the dirname of `symlink_path`.
15 const exe_rel_path = try std.fs.path.relative(gpa, cwd, init.environ_map, std.fs.path.dirname(symlink_path) orelse ".", exe_path);
16 defer gpa.free(exe_rel_path);
1917
20 try std.Io.Dir.cwd().symLink(io, exe_rel_path, symlink_path, .{});18 try std.Io.Dir.cwd().symLink(io, exe_rel_path, symlink_path, .{});
21}19}
test/standalone/self_exe_symlink/main.zig+3-8
...@@ -1,13 +1,8 @@...@@ -1,13 +1,8 @@
1const std = @import("std");1const std = @import("std");
22
3pub fn main() !void {3pub fn main(init: std.process.Init) !void {
4 var debug_allocator: std.heap.DebugAllocator(.{}) = .init;4 const gpa = init.gpa;
5 defer if (debug_allocator.deinit() == .leak) @panic("found memory leaks");5 const io = init.io;
6 const gpa = debug_allocator.allocator();
7
8 var threaded: std.Io.Threaded = .init(gpa, .{});
9 defer threaded.deinit();
10 const io = threaded.io();
116
12 const self_path = try std.process.executablePathAlloc(io, gpa);7 const self_path = try std.process.executablePathAlloc(io, gpa);
13 defer gpa.free(self_path);8 defer gpa.free(self_path);
test/standalone/simple/cat/main.zig+4-10
...@@ -4,16 +4,10 @@ const mem = std.mem;...@@ -4,16 +4,10 @@ const mem = std.mem;
4const warn = std.log.warn;4const warn = std.log.warn;
5const fatal = std.process.fatal;5const fatal = std.process.fatal;
66
7pub fn main() !void {7pub fn main(init: std.process.Init) !void {
8 var arena_instance = std.heap.ArenaAllocator.init(std.heap.page_allocator);8 const arena = init.arena.allocator();
9 defer arena_instance.deinit();9 const io = init.io;
10 const arena = arena_instance.allocator();10 const args = try init.minimal.args.toSlice(arena);
11
12 var threaded: std.Io.Threaded = .init(arena, .{});
13 defer threaded.deinit();
14 const io = threaded.io();
15
16 const args = try std.process.argsAlloc(arena);
1711
18 const exe = args[0];12 const exe = args[0];
19 var catted_anything = false;13 var catted_anything = false;
test/standalone/simple/guess_number/main.zig+3-14
...@@ -1,22 +1,11 @@...@@ -1,22 +1,11 @@
1const builtin = @import("builtin");
2const std = @import("std");1const std = @import("std");
32
4// See https://github.com/ziglang/zig/issues/245103pub fn main(init: std.process.Init) !void {
5// for the plan to simplify this code.4 var stdout_writer = std.Io.File.stdout().writerStreaming(init.io, &.{});
6pub fn main() !void {
7 var debug_allocator: std.heap.DebugAllocator(.{}) = .init;
8 defer _ = debug_allocator.deinit();
9 const gpa = debug_allocator.allocator();
10
11 var threaded: std.Io.Threaded = .init(gpa, .{});
12 defer threaded.deinit();
13 const io = threaded.io();
14
15 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});
16 const out = &stdout_writer.interface;5 const out = &stdout_writer.interface;
176
18 var line_buffer: [20]u8 = undefined;7 var line_buffer: [20]u8 = undefined;
19 var stdin_reader: std.Io.File.Reader = .init(.stdin(), io, &line_buffer);8 var stdin_reader: std.Io.File.Reader = .init(.stdin(), init.io, &line_buffer);
20 const in = &stdin_reader.interface;9 const in = &stdin_reader.interface;
2110
22 try out.writeAll("Welcome to the Guess Number Game in Zig.\n");11 try out.writeAll("Welcome to the Guess Number Game in Zig.\n");
test/standalone/simple/hello_world/hello.zig+2-12
...@@ -1,15 +1,5 @@...@@ -1,15 +1,5 @@
1const std = @import("std");1const std = @import("std");
22
3// See https://github.com/ziglang/zig/issues/245103pub fn main(init: std.process.Init) !void {
4// for the plan to simplify this code.4 try std.Io.File.stdout().writeStreamingAll(init.io, "Hello, World!\n");
5pub fn main() !void {
6 var debug_allocator: std.heap.DebugAllocator(.{}) = .init;
7 defer _ = debug_allocator.deinit();
8 const gpa = debug_allocator.allocator();
9
10 var threaded: std.Io.Threaded = .init(gpa, .{});
11 defer threaded.deinit();
12 const io = threaded.io();
13
14 try std.Io.File.stdout().writeStreamingAll(io, "Hello, World!\n");
15}5}
test/standalone/windows_argv/build.zig+1-1
...@@ -67,7 +67,7 @@ pub fn build(b: *std.Build) !void {...@@ -67,7 +67,7 @@ pub fn build(b: *std.Build) !void {
6767
68 // Only target the MSVC ABI if MSVC/Windows SDK is available68 // Only target the MSVC ABI if MSVC/Windows SDK is available
69 const has_msvc = has_msvc: {69 const has_msvc = has_msvc: {
70 const sdk = std.zig.WindowsSdk.find(b.allocator, b.graph.io, builtin.cpu.arch) catch |err| switch (err) {70 const sdk = std.zig.WindowsSdk.find(b.allocator, b.graph.io, builtin.cpu.arch, &b.graph.environ_map) catch |err| switch (err) {
71 error.OutOfMemory => @panic("oom"),71 error.OutOfMemory => @panic("oom"),
72 else => break :has_msvc false,72 else => break :has_msvc false,
73 };73 };
test/standalone/windows_argv/fuzz.zig+10-14
...@@ -3,19 +3,15 @@ const builtin = @import("builtin");...@@ -3,19 +3,15 @@ const builtin = @import("builtin");
3const windows = std.os.windows;3const windows = std.os.windows;
4const Allocator = std.mem.Allocator;4const Allocator = std.mem.Allocator;
55
6pub fn main() !void {6pub fn main(init: std.process.Init) !void {
7 var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init;7 const gpa = init.gpa;
8 defer std.debug.assert(gpa.deinit() == .ok);8 const args = try init.minimal.args.toSlice(init.arena.allocator());
9 const allocator = gpa.allocator();
10
11 const args = try std.process.argsAlloc(allocator);
12 defer std.process.argsFree(allocator, args);
139
14 if (args.len < 2) return error.MissingArgs;10 if (args.len < 2) return error.MissingArgs;
1511
16 const verify_path_wtf8 = args[1];12 const verify_path_wtf8 = args[1];
17 const verify_path_w = try std.unicode.wtf8ToWtf16LeAllocZ(allocator, verify_path_wtf8);13 const verify_path_w = try std.unicode.wtf8ToWtf16LeAllocZ(gpa, verify_path_wtf8);
18 defer allocator.free(verify_path_w);14 defer gpa.free(verify_path_w);
1915
20 const iterations: u64 = iterations: {16 const iterations: u64 = iterations: {
21 if (args.len < 3) break :iterations 0;17 if (args.len < 3) break :iterations 0;
...@@ -41,14 +37,14 @@ pub fn main() !void {...@@ -41,14 +37,14 @@ pub fn main() !void {
41 std.debug.print("rand seed: {}\n", .{seed});37 std.debug.print("rand seed: {}\n", .{seed});
42 }38 }
4339
44 var cmd_line_w_buf = std.array_list.Managed(u16).init(allocator);40 var cmd_line_w_buf = std.array_list.Managed(u16).init(gpa);
45 defer cmd_line_w_buf.deinit();41 defer cmd_line_w_buf.deinit();
4642
47 var i: u64 = 0;43 var i: u64 = 0;
48 var errors: u64 = 0;44 var errors: u64 = 0;
49 while (iterations == 0 or i < iterations) {45 while (iterations == 0 or i < iterations) {
50 const cmd_line_w = try randomCommandLineW(allocator, rand);46 const cmd_line_w = try randomCommandLineW(gpa, rand);
51 defer allocator.free(cmd_line_w);47 defer gpa.free(cmd_line_w);
5248
53 // avoid known difference for 0-length command lines49 // avoid known difference for 0-length command lines
54 if (cmd_line_w.len == 0 or cmd_line_w[0] == '\x00') continue;50 if (cmd_line_w.len == 0 or cmd_line_w[0] == '\x00') continue;
...@@ -56,8 +52,8 @@ pub fn main() !void {...@@ -56,8 +52,8 @@ pub fn main() !void {
56 const exit_code = try spawnVerify(verify_path_w, cmd_line_w);52 const exit_code = try spawnVerify(verify_path_w, cmd_line_w);
57 if (exit_code != 0) {53 if (exit_code != 0) {
58 std.debug.print(">>> found discrepancy <<<\n", .{});54 std.debug.print(">>> found discrepancy <<<\n", .{});
59 const cmd_line_wtf8 = try std.unicode.wtf16LeToWtf8Alloc(allocator, cmd_line_w);55 const cmd_line_wtf8 = try std.unicode.wtf16LeToWtf8Alloc(gpa, cmd_line_w);
60 defer allocator.free(cmd_line_wtf8);56 defer gpa.free(cmd_line_wtf8);
61 std.debug.print("\"{f}\"\n\n", .{std.zig.fmtString(cmd_line_wtf8)});57 std.debug.print("\"{f}\"\n\n", .{std.zig.fmtString(cmd_line_wtf8)});
6258
63 errors += 1;59 errors += 1;
test/standalone/windows_argv/lib.zig+5-2
...@@ -5,7 +5,7 @@ export fn verify(argc: c_int, argv: [*]const [*:0]const u16) c_int {...@@ -5,7 +5,7 @@ export fn verify(argc: c_int, argv: [*]const [*:0]const u16) c_int {
5 const argv_slice = argv[0..@intCast(argc)];5 const argv_slice = argv[0..@intCast(argc)];
6 testArgv(argv_slice) catch |err| switch (err) {6 testArgv(argv_slice) catch |err| switch (err) {
7 error.OutOfMemory => @panic("oom"),7 error.OutOfMemory => @panic("oom"),
8 error.Overflow => @panic("bytes needed to contain args would overflow usize"),8 error.Unexpected => @panic("unexpected error"),
9 error.ArgvMismatch => return 0,9 error.ArgvMismatch => return 0,
10 };10 };
11 return 1;11 return 1;
...@@ -16,7 +16,10 @@ fn testArgv(expected_args: []const [*:0]const u16) !void {...@@ -16,7 +16,10 @@ fn testArgv(expected_args: []const [*:0]const u16) !void {
16 defer arena_state.deinit();16 defer arena_state.deinit();
17 const allocator = arena_state.allocator();17 const allocator = arena_state.allocator();
1818
19 const args = try std.process.argsAlloc(allocator);19 const cmd_line = std.os.windows.peb().ProcessParameters.CommandLine;
20 const cmd_line_w = cmd_line.Buffer.?[0..@divExact(cmd_line.Length, 2)];
21 const raw_args: std.process.Args = .{ .vector = cmd_line_w };
22 const args = try raw_args.toSlice(allocator);
20 var wtf8_buf = std.array_list.Managed(u8).init(allocator);23 var wtf8_buf = std.array_list.Managed(u8).init(allocator);
2124
22 var eql = true;25 var eql = true;
test/standalone/windows_bat_args/echo-args.zig+4-7
...@@ -1,15 +1,12 @@...@@ -1,15 +1,12 @@
1const std = @import("std");1const std = @import("std");
22
3pub fn main() !void {3pub fn main(init: std.process.Init) !void {
4 var arena_state = std.heap.ArenaAllocator.init(std.heap.page_allocator);4 const arena = init.arena.allocator();
5 defer arena_state.deinit();5 const io = init.io;
6 const arena = arena_state.allocator();6 const args = try init.minimal.args.toSlice(arena);
7
8 const io = std.Options.debug_io;
97
10 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});8 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});
11 const stdout = &stdout_writer.interface;9 const stdout = &stdout_writer.interface;
12 var args = try std.process.argsAlloc(arena);
13 for (args[1..], 1..) |arg, i| {10 for (args[1..], 1..) |arg, i| {
14 try stdout.writeAll(arg);11 try stdout.writeAll(arg);
15 if (i != args.len - 1) try stdout.writeByte('\x00');12 if (i != args.len - 1) try stdout.writeByte('\x00');
test/standalone/windows_bat_args/fuzz.zig+8-13
...@@ -4,16 +4,11 @@ const std = @import("std");...@@ -4,16 +4,11 @@ const std = @import("std");
4const Io = std.Io;4const Io = std.Io;
5const Allocator = std.mem.Allocator;5const Allocator = std.mem.Allocator;
66
7pub fn main() anyerror!void {7pub fn main(init: std.process.Init) !void {
8 var debug_alloc_inst: std.heap.DebugAllocator(.{}) = .init;8 const gpa = init.gpa;
9 defer std.debug.assert(debug_alloc_inst.deinit() == .ok);9 const io = init.io;
10 const gpa = debug_alloc_inst.allocator();
1110
12 var threaded: Io.Threaded = .init(gpa, .{});11 var it = try init.minimal.args.iterateAllocator(gpa);
13 defer threaded.deinit();
14 const io = threaded.io();
15
16 var it = try std.process.argsWithAllocator(gpa);
17 defer it.deinit();12 defer it.deinit();
18 _ = it.next() orelse unreachable; // skip binary name13 _ = it.next() orelse unreachable; // skip binary name
19 const child_exe_path_orig = it.next() orelse unreachable;14 const child_exe_path_orig = it.next() orelse unreachable;
...@@ -84,13 +79,13 @@ pub fn main() anyerror!void {...@@ -84,13 +79,13 @@ pub fn main() anyerror!void {
84 }79 }
85}80}
8681
87fn testExec(gpa: Allocator, io: Io, args: []const []const u8, env: ?*std.process.EnvMap) !void {82fn testExec(gpa: Allocator, io: Io, args: []const []const u8, env: ?*std.process.Environ.Map) !void {
88 try testExecBat(gpa, io, "args1.bat", args, env);83 try testExecBat(gpa, io, "args1.bat", args, env);
89 try testExecBat(gpa, io, "args2.bat", args, env);84 try testExecBat(gpa, io, "args2.bat", args, env);
90 try testExecBat(gpa, io, "args3.bat", args, env);85 try testExecBat(gpa, io, "args3.bat", args, env);
91}86}
9287
93fn testExecBat(gpa: Allocator, io: Io, bat: []const u8, args: []const []const u8, env: ?*std.process.EnvMap) !void {88fn testExecBat(gpa: Allocator, io: Io, bat: []const u8, args: []const []const u8, env: ?*std.process.Environ.Map) !void {
94 const argv = try gpa.alloc([]const u8, 1 + args.len);89 const argv = try gpa.alloc([]const u8, 1 + args.len);
95 defer gpa.free(argv);90 defer gpa.free(argv);
96 argv[0] = bat;91 argv[0] = bat;
...@@ -98,8 +93,8 @@ fn testExecBat(gpa: Allocator, io: Io, bat: []const u8, args: []const []const u8...@@ -98,8 +93,8 @@ fn testExecBat(gpa: Allocator, io: Io, bat: []const u8, args: []const []const u8
9893
99 const can_have_trailing_empty_args = std.mem.eql(u8, bat, "args3.bat");94 const can_have_trailing_empty_args = std.mem.eql(u8, bat, "args3.bat");
10095
101 const result = try std.process.Child.run(gpa, io, .{96 const result = try std.process.run(gpa, io, .{
102 .env_map = env,97 .environ_map = env,
103 .argv = argv,98 .argv = argv,
104 });99 });
105 defer gpa.free(result.stdout);100 defer gpa.free(result.stdout);
test/standalone/windows_bat_args/test.zig+9-13
...@@ -2,15 +2,11 @@ const std = @import("std");...@@ -2,15 +2,11 @@ const std = @import("std");
2const Io = std.Io;2const Io = std.Io;
3const Allocator = std.mem.Allocator;3const Allocator = std.mem.Allocator;
44
5pub fn main() anyerror!void {5pub fn main(init: std.process.Init) !void {
6 var debug_alloc_inst: std.heap.DebugAllocator(.{}) = .init;6 const gpa = init.gpa;
7 defer std.debug.assert(debug_alloc_inst.deinit() == .ok);7 const io = init.io;
8 const gpa = debug_alloc_inst.allocator();
98
10 var threaded: Io.Threaded = .init(gpa, .{});9 var it = try init.minimal.args.iterateAllocator(gpa);
11 const io = threaded.io();
12
13 var it = try std.process.argsWithAllocator(gpa);
14 defer it.deinit();10 defer it.deinit();
15 _ = it.next() orelse unreachable; // skip binary name11 _ = it.next() orelse unreachable; // skip binary name
16 const child_exe_path_orig = it.next() orelse unreachable;12 const child_exe_path_orig = it.next() orelse unreachable;
...@@ -109,7 +105,7 @@ pub fn main() anyerror!void {...@@ -109,7 +105,7 @@ pub fn main() anyerror!void {
109 try std.testing.expectError(error.FileNotFound, testExecBat(gpa, io, absolute_with_trailing, &.{"abc"}, null));105 try std.testing.expectError(error.FileNotFound, testExecBat(gpa, io, absolute_with_trailing, &.{"abc"}, null));
110106
111 var env = env: {107 var env = env: {
112 var env = try std.process.getEnvMap(gpa);108 var env = try init.environ_map.clone(gpa);
113 errdefer env.deinit();109 errdefer env.deinit();
114 // No escaping110 // No escaping
115 try env.put("FOO", "123");111 try env.put("FOO", "123");
...@@ -130,13 +126,13 @@ fn testExecError(err: anyerror, gpa: Allocator, io: Io, args: []const []const u8...@@ -130,13 +126,13 @@ fn testExecError(err: anyerror, gpa: Allocator, io: Io, args: []const []const u8
130 return std.testing.expectError(err, testExec(gpa, io, args, null));126 return std.testing.expectError(err, testExec(gpa, io, args, null));
131}127}
132128
133fn testExec(gpa: Allocator, io: Io, args: []const []const u8, env: ?*std.process.EnvMap) !void {129fn testExec(gpa: Allocator, io: Io, args: []const []const u8, env: ?*std.process.Environ.Map) !void {
134 try testExecBat(gpa, io, "args1.bat", args, env);130 try testExecBat(gpa, io, "args1.bat", args, env);
135 try testExecBat(gpa, io, "args2.bat", args, env);131 try testExecBat(gpa, io, "args2.bat", args, env);
136 try testExecBat(gpa, io, "args3.bat", args, env);132 try testExecBat(gpa, io, "args3.bat", args, env);
137}133}
138134
139fn testExecBat(gpa: Allocator, io: Io, bat: []const u8, args: []const []const u8, env: ?*std.process.EnvMap) !void {135fn testExecBat(gpa: Allocator, io: Io, bat: []const u8, args: []const []const u8, env: ?*std.process.Environ.Map) !void {
140 const argv = try gpa.alloc([]const u8, 1 + args.len);136 const argv = try gpa.alloc([]const u8, 1 + args.len);
141 defer gpa.free(argv);137 defer gpa.free(argv);
142 argv[0] = bat;138 argv[0] = bat;
...@@ -144,8 +140,8 @@ fn testExecBat(gpa: Allocator, io: Io, bat: []const u8, args: []const []const u8...@@ -144,8 +140,8 @@ fn testExecBat(gpa: Allocator, io: Io, bat: []const u8, args: []const []const u8
144140
145 const can_have_trailing_empty_args = std.mem.eql(u8, bat, "args3.bat");141 const can_have_trailing_empty_args = std.mem.eql(u8, bat, "args3.bat");
146142
147 const result = try std.process.Child.run(gpa, io, .{143 const result = try std.process.run(gpa, io, .{
148 .env_map = env,144 .environ_map = env,
149 .argv = argv,145 .argv = argv,
150 });146 });
151 defer gpa.free(result.stdout);147 defer gpa.free(result.stdout);
test/standalone/windows_paths/relative.zig+6-13
...@@ -1,21 +1,14 @@...@@ -1,21 +1,14 @@
1const std = @import("std");1const std = @import("std");
22
3pub fn main() !void {3pub fn main(init: std.process.Init) !void {
4 var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init;4 const arena = init.arena.allocator();
5 defer std.debug.assert(gpa.deinit() == .ok);5 const args = try init.minimal.args.toSlice(arena);
6 const allocator = gpa.allocator();6 const io = init.io;
77 const cwd_path = try std.process.getCwdAlloc(arena);
8 const args = try std.process.argsAlloc(allocator);
9 defer std.process.argsFree(allocator, args);
108
11 if (args.len < 3) return error.MissingArgs;9 if (args.len < 3) return error.MissingArgs;
1210
13 var threaded: std.Io.Threaded = .init(allocator, .{});11 const relative = try std.fs.path.relative(arena, cwd_path, init.environ_map, args[1], args[2]);
14 defer threaded.deinit();
15 const io = threaded.io();
16
17 const relative = try std.fs.path.relative(allocator, args[1], args[2]);
18 defer allocator.free(relative);
1912
20 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});13 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{});
21 const stdout = &stdout_writer.interface;14 const stdout = &stdout_writer.interface;
test/standalone/windows_paths/test.zig+9-13
...@@ -1,17 +1,13 @@...@@ -1,17 +1,13 @@
1const std = @import("std");1const std = @import("std");
2const Io = std.Io;2const Io = std.Io;
33
4pub fn main() anyerror!void {4pub fn main(init: std.process.Init) !void {
5 var arena_state = std.heap.ArenaAllocator.init(std.heap.page_allocator);5 const arena = init.arena.allocator();
6 defer arena_state.deinit();6 const args = try init.minimal.args.toSlice(arena);
7 const arena = arena_state.allocator();7 const io = init.io;
8
9 const args = try std.process.argsAlloc(arena);
108
11 if (args.len < 2) return error.MissingArgs;9 if (args.len < 2) return error.MissingArgs;
1210
13 const io = std.Io.Threaded.global_single_threaded.ioBasic();
14
15 const exe_path = args[1];11 const exe_path = args[1];
1612
17 const cwd_path = try std.process.getCwdAlloc(arena);13 const cwd_path = try std.process.getCwdAlloc(arena);
...@@ -25,10 +21,10 @@ pub fn main() anyerror!void {...@@ -25,10 +21,10 @@ pub fn main() anyerror!void {
25 const alt_drive_letter = try getAltDriveLetter(cwd_path);21 const alt_drive_letter = try getAltDriveLetter(cwd_path);
26 const alt_drive_cwd_key = try std.fmt.allocPrint(arena, "={c}:", .{alt_drive_letter});22 const alt_drive_cwd_key = try std.fmt.allocPrint(arena, "={c}:", .{alt_drive_letter});
27 const alt_drive_cwd = try std.fmt.allocPrint(arena, "{c}:\\baz", .{alt_drive_letter});23 const alt_drive_cwd = try std.fmt.allocPrint(arena, "{c}:\\baz", .{alt_drive_letter});
28 var alt_drive_env_map = std.process.EnvMap.init(arena);24 var alt_drive_env_map = std.process.Environ.Map.init(arena);
29 try alt_drive_env_map.put(alt_drive_cwd_key, alt_drive_cwd);25 try alt_drive_env_map.put(alt_drive_cwd_key, alt_drive_cwd);
3026
31 const empty_env = std.process.EnvMap.init(arena);27 const empty_env = std.process.Environ.Map.init(arena);
3228
33 {29 {
34 const drive_rel = try std.fmt.allocPrint(arena, "{c}:foo", .{alt_drive_letter});30 const drive_rel = try std.fmt.allocPrint(arena, "{c}:foo", .{alt_drive_letter});
...@@ -96,12 +92,12 @@ fn checkRelative(...@@ -96,12 +92,12 @@ fn checkRelative(
96 expected_stdout: []const u8,92 expected_stdout: []const u8,
97 argv: []const []const u8,93 argv: []const []const u8,
98 cwd: ?[]const u8,94 cwd: ?[]const u8,
99 env_map: ?*const std.process.EnvMap,95 environ_map: ?*const std.process.Environ.Map,
100) !void {96) !void {
101 const result = try std.process.Child.run(allocator, io, .{97 const result = try std.process.run(allocator, io, .{
102 .argv = argv,98 .argv = argv,
103 .cwd = cwd,99 .cwd = cwd,
104 .env_map = env_map,100 .environ_map = environ_map,
105 });101 });
106 defer allocator.free(result.stdout);102 defer allocator.free(result.stdout);
107 defer allocator.free(result.stderr);103 defer allocator.free(result.stderr);
test/standalone/windows_spawn/main.zig+7-11
...@@ -5,16 +5,12 @@ const Allocator = std.mem.Allocator;...@@ -5,16 +5,12 @@ const Allocator = std.mem.Allocator;
5const windows = std.os.windows;5const windows = std.os.windows;
6const utf16Literal = std.unicode.utf8ToUtf16LeStringLiteral;6const utf16Literal = std.unicode.utf8ToUtf16LeStringLiteral;
77
8pub fn main() anyerror!void {8pub fn main(init: std.process.Init) !void {
9 var debug_allocator: std.heap.DebugAllocator(.{}) = .init;9 const gpa = init.gpa;
10 defer if (debug_allocator.deinit() == .leak) @panic("found memory leaks");10 const io = init.io;
11 const gpa = debug_allocator.allocator();11 const process_cwd_path = try std.process.getCwdAlloc(init.arena.allocator());
1212
13 var threaded: std.Io.Threaded = .init(gpa, .{});13 var it = try init.minimal.args.iterateAllocator(gpa);
14 defer threaded.deinit();
15 const io = threaded.io();
16
17 var it = try std.process.argsWithAllocator(gpa);
18 defer it.deinit();14 defer it.deinit();
19 _ = it.next() orelse unreachable; // skip binary name15 _ = it.next() orelse unreachable; // skip binary name
20 const hello_exe_cache_path = it.next() orelse unreachable;16 const hello_exe_cache_path = it.next() orelse unreachable;
...@@ -28,7 +24,7 @@ pub fn main() anyerror!void {...@@ -28,7 +24,7 @@ pub fn main() anyerror!void {
28 defer gpa.free(tmp_absolute_path_w);24 defer gpa.free(tmp_absolute_path_w);
29 const cwd_absolute_path = try Io.Dir.cwd().realPathFileAlloc(io, ".", gpa);25 const cwd_absolute_path = try Io.Dir.cwd().realPathFileAlloc(io, ".", gpa);
30 defer gpa.free(cwd_absolute_path);26 defer gpa.free(cwd_absolute_path);
31 const tmp_relative_path = try std.fs.path.relative(gpa, cwd_absolute_path, tmp_absolute_path);27 const tmp_relative_path = try std.fs.path.relative(gpa, process_cwd_path, init.environ_map, cwd_absolute_path, tmp_absolute_path);
32 defer gpa.free(tmp_relative_path);28 defer gpa.free(tmp_relative_path);
3329
34 // Clear PATH30 // Clear PATH
...@@ -212,7 +208,7 @@ fn testExec(gpa: Allocator, io: Io, command: []const u8, expected_stdout: []cons...@@ -212,7 +208,7 @@ fn testExec(gpa: Allocator, io: Io, command: []const u8, expected_stdout: []cons
212}208}
213209
214fn testExecWithCwd(gpa: Allocator, io: Io, command: []const u8, cwd: ?[]const u8, expected_stdout: []const u8) !void {210fn testExecWithCwd(gpa: Allocator, io: Io, command: []const u8, cwd: ?[]const u8, expected_stdout: []const u8) !void {
215 const result = try std.process.Child.run(gpa, io, .{211 const result = try std.process.run(gpa, io, .{
216 .argv = &[_][]const u8{command},212 .argv = &[_][]const u8{command},
217 .cwd = cwd,213 .cwd = cwd,
218 });214 });
test/tests.zig+2-2
...@@ -2062,7 +2062,7 @@ pub fn addCliTests(b: *std.Build) *Step {...@@ -2062,7 +2062,7 @@ pub fn addCliTests(b: *std.Build) *Step {
2062 run_run.setCwd(.{ .cwd_relative = tmp_path });2062 run_run.setCwd(.{ .cwd_relative = tmp_path });
2063 run_run.setName("zig build run");2063 run_run.setName("zig build run");
2064 run_run.expectStdOutEqual("Run `zig build test` to run the tests.\n");2064 run_run.expectStdOutEqual("Run `zig build test` to run the tests.\n");
2065 run_run.expectStdErrEqual("All your codebase are belong to us.\n");2065 run_run.expectStdErrMatch("All your codebase are belong to us.\n");
2066 run_run.step.dependOn(&init_exe.step);2066 run_run.step.dependOn(&init_exe.step);
20672067
2068 const cleanup = b.addRemoveDirTree(.{ .cwd_relative = tmp_path });2068 const cleanup = b.addRemoveDirTree(.{ .cwd_relative = tmp_path });
...@@ -2718,7 +2718,7 @@ pub fn addIncrementalTests(b: *std.Build, test_step: *Step, test_filters: []cons...@@ -2718,7 +2718,7 @@ pub fn addIncrementalTests(b: *std.Build, test_step: *Step, test_filters: []cons
2718 if (b.enable_wasmtime) run.addArg("-fwasmtime");2718 if (b.enable_wasmtime) run.addArg("-fwasmtime");
2719 if (b.enable_darling) run.addArg("-fdarling");2719 if (b.enable_darling) run.addArg("-fdarling");
27202720
2721 run.addCheck(.{ .expect_term = .{ .Exited = 0 } });2721 run.addCheck(.{ .expect_term = .{ .exited = 0 } });
27222722
2723 test_step.dependOn(&run.step);2723 test_step.dependOn(&run.step);
2724 }2724 }
tools/docgen.zig+4-12
...@@ -28,21 +28,13 @@ const usage =...@@ -28,21 +28,13 @@ const usage =
28 \\28 \\
29;29;
3030
31pub fn main() !void {31pub fn main(init: std.process.Init) !void {
32 var arena_instance = std.heap.ArenaAllocator.init(std.heap.page_allocator);32 const arena = init.arena.allocator();
33 defer arena_instance.deinit();33 const io = init.io;
3434
35 const arena = arena_instance.allocator();35 var args_it = try init.minimal.args.iterateAllocator(arena);
36
37 var args_it = try process.argsWithAllocator(arena);
38 if (!args_it.skip()) @panic("expected self arg");36 if (!args_it.skip()) @panic("expected self arg");
3937
40 const gpa = arena;
41
42 var threaded: std.Io.Threaded = .init(gpa, .{});
43 defer threaded.deinit();
44 const io = threaded.io();
45
46 var opt_code_dir: ?[]const u8 = null;38 var opt_code_dir: ?[]const u8 = null;
47 var opt_input: ?[]const u8 = null;39 var opt_input: ?[]const u8 = null;
48 var opt_output: ?[]const u8 = null;40 var opt_output: ?[]const u8 = null;
tools/doctest.zig+43-43
...@@ -29,21 +29,17 @@ const usage =...@@ -29,21 +29,17 @@ const usage =
29 \\29 \\
30;30;
3131
32pub fn main() !void {32pub fn main(init: std.process.Init) !void {
33 var arena_instance = std.heap.ArenaAllocator.init(std.heap.page_allocator);33 const arena = init.arena.allocator();
34 defer arena_instance.deinit();34 const io = init.io;
35 const environ_map = init.environ_map;
36 const cwd_path = try std.process.getCwdAlloc(arena);
3537
36 const arena = arena_instance.allocator();38 try environ_map.put("CLICOLOR_FORCE", "1");
3739
38 var args_it = try process.argsWithAllocator(arena);40 var args_it = try init.minimal.args.iterateAllocator(arena);
39 if (!args_it.skip()) fatal("missing argv[0]", .{});41 if (!args_it.skip()) fatal("missing argv[0]", .{});
4042
41 const gpa = arena;
42
43 var threaded: std.Io.Threaded = .init(gpa, .{});
44 defer threaded.deinit();
45 const io = threaded.io();
46
47 var opt_input: ?[]const u8 = null;43 var opt_input: ?[]const u8 = null;
48 var opt_output: ?[]const u8 = null;44 var opt_output: ?[]const u8 = null;
49 var opt_zig: ?[]const u8 = null;45 var opt_zig: ?[]const u8 = null;
...@@ -105,12 +101,13 @@ pub fn main() !void {...@@ -105,12 +101,13 @@ pub fn main() !void {
105 out,101 out,
106 code,102 code,
107 tmp_dir_path,103 tmp_dir_path,
108 try Dir.path.relative(arena, tmp_dir_path, zig_path),104 try Dir.path.relative(arena, cwd_path, environ_map, tmp_dir_path, zig_path),
109 try Dir.path.relative(arena, tmp_dir_path, input_path),105 try Dir.path.relative(arena, cwd_path, environ_map, tmp_dir_path, input_path),
110 if (opt_zig_lib_dir) |zig_lib_dir|106 if (opt_zig_lib_dir) |zig_lib_dir|
111 try Dir.path.relative(arena, tmp_dir_path, zig_lib_dir)107 try Dir.path.relative(arena, cwd_path, environ_map, tmp_dir_path, zig_lib_dir)
112 else108 else
113 null,109 null,
110 environ_map,
114 );111 );
115112
116 try out_file_writer.end();113 try out_file_writer.end();
...@@ -129,10 +126,8 @@ fn printOutput(...@@ -129,10 +126,8 @@ fn printOutput(
129 input_path: []const u8,126 input_path: []const u8,
130 /// Relative to `tmp_dir_path`.127 /// Relative to `tmp_dir_path`.
131 opt_zig_lib_dir: ?[]const u8,128 opt_zig_lib_dir: ?[]const u8,
129 environ_map: *const process.Environ.Map,
132) !void {130) !void {
133 var env_map = try process.getEnvMap(arena);
134 try env_map.put("CLICOLOR_FORCE", "1");
135
136 const host = try std.zig.system.resolveTargetQuery(io, .{});131 const host = try std.zig.system.resolveTargetQuery(io, .{});
137 const obj_ext = builtin.object_format.fileExt(builtin.cpu.arch);132 const obj_ext = builtin.object_format.fileExt(builtin.cpu.arch);
138 const print = std.debug.print;133 const print = std.debug.print;
...@@ -201,14 +196,14 @@ fn printOutput(...@@ -201,14 +196,14 @@ fn printOutput(
201 try shell_out.print("\n", .{});196 try shell_out.print("\n", .{});
202197
203 if (expected_outcome == .build_fail) {198 if (expected_outcome == .build_fail) {
204 const result = try process.Child.run(arena, io, .{199 const result = try process.run(arena, io, .{
205 .argv = build_args.items,200 .argv = build_args.items,
206 .cwd = tmp_dir_path,201 .cwd = tmp_dir_path,
207 .env_map = &env_map,202 .environ_map = environ_map,
208 .max_output_bytes = max_doc_file_size,203 .max_output_bytes = max_doc_file_size,
209 });204 });
210 switch (result.term) {205 switch (result.term) {
211 .Exited => |exit_code| {206 .exited => |exit_code| {
212 if (exit_code == 0) {207 if (exit_code == 0) {
213 print("{s}\nThe following command incorrectly succeeded:\n", .{result.stderr});208 print("{s}\nThe following command incorrectly succeeded:\n", .{result.stderr});
214 dumpArgs(build_args.items);209 dumpArgs(build_args.items);
...@@ -226,7 +221,7 @@ fn printOutput(...@@ -226,7 +221,7 @@ fn printOutput(
226 try shell_out.writeAll(colored_stderr);221 try shell_out.writeAll(colored_stderr);
227 break :code_block;222 break :code_block;
228 }223 }
229 const exec_result = run(arena, io, &env_map, tmp_dir_path, build_args.items) catch224 const exec_result = run(arena, io, environ_map, tmp_dir_path, build_args.items) catch
230 fatal("example failed to compile", .{});225 fatal("example failed to compile", .{});
231226
232 if (code.verbose_cimport) {227 if (code.verbose_cimport) {
...@@ -257,26 +252,26 @@ fn printOutput(...@@ -257,26 +252,26 @@ fn printOutput(
257 var exited_with_signal = false;252 var exited_with_signal = false;
258253
259 const result = if (expected_outcome == .fail) blk: {254 const result = if (expected_outcome == .fail) blk: {
260 const result = try process.Child.run(arena, io, .{255 const result = try process.run(arena, io, .{
261 .argv = run_args,256 .argv = run_args,
262 .env_map = &env_map,257 .environ_map = environ_map,
263 .cwd = tmp_dir_path,258 .cwd = tmp_dir_path,
264 .max_output_bytes = max_doc_file_size,259 .max_output_bytes = max_doc_file_size,
265 });260 });
266 switch (result.term) {261 switch (result.term) {
267 .Exited => |exit_code| {262 .exited => |exit_code| {
268 if (exit_code == 0) {263 if (exit_code == 0) {
269 print("{s}\nThe following command incorrectly succeeded:\n", .{result.stderr});264 print("{s}\nThe following command incorrectly succeeded:\n", .{result.stderr});
270 dumpArgs(run_args);265 dumpArgs(run_args);
271 fatal("example incorrectly compiled", .{});266 fatal("example incorrectly compiled", .{});
272 }267 }
273 },268 },
274 .Signal => exited_with_signal = true,269 .signal => exited_with_signal = true,
275 else => {},270 else => {},
276 }271 }
277 break :blk result;272 break :blk result;
278 } else blk: {273 } else blk: {
279 break :blk run(arena, io, &env_map, tmp_dir_path, run_args) catch274 break :blk run(arena, io, environ_map, tmp_dir_path, run_args) catch
280 fatal("example crashed", .{});275 fatal("example crashed", .{});
281 };276 };
282277
...@@ -345,7 +340,7 @@ fn printOutput(...@@ -345,7 +340,7 @@ fn printOutput(
345 }340 }
346 }341 }
347342
348 const result = run(arena, io, &env_map, tmp_dir_path, test_args.items) catch343 const result = run(arena, io, environ_map, tmp_dir_path, test_args.items) catch
349 fatal("test failed", .{});344 fatal("test failed", .{});
350 const escaped_stderr = try escapeHtml(arena, result.stderr);345 const escaped_stderr = try escapeHtml(arena, result.stderr);
351 const escaped_stdout = try escapeHtml(arena, result.stdout);346 const escaped_stdout = try escapeHtml(arena, result.stdout);
...@@ -376,14 +371,14 @@ fn printOutput(...@@ -376,14 +371,14 @@ fn printOutput(
376 try test_args.append("-lc");371 try test_args.append("-lc");
377 try shell_out.print("-lc ", .{});372 try shell_out.print("-lc ", .{});
378 }373 }
379 const result = try process.Child.run(arena, io, .{374 const result = try process.run(arena, io, .{
380 .argv = test_args.items,375 .argv = test_args.items,
381 .env_map = &env_map,376 .environ_map = environ_map,
382 .cwd = tmp_dir_path,377 .cwd = tmp_dir_path,
383 .max_output_bytes = max_doc_file_size,378 .max_output_bytes = max_doc_file_size,
384 });379 });
385 switch (result.term) {380 switch (result.term) {
386 .Exited => |exit_code| {381 .exited => |exit_code| {
387 if (exit_code == 0) {382 if (exit_code == 0) {
388 print("{s}\nThe following command incorrectly succeeded:\n", .{result.stderr});383 print("{s}\nThe following command incorrectly succeeded:\n", .{result.stderr});
389 dumpArgs(test_args.items);384 dumpArgs(test_args.items);
...@@ -432,14 +427,14 @@ fn printOutput(...@@ -432,14 +427,14 @@ fn printOutput(
432 },427 },
433 }428 }
434429
435 const result = try process.Child.run(arena, io, .{430 const result = try process.run(arena, io, .{
436 .argv = test_args.items,431 .argv = test_args.items,
437 .env_map = &env_map,432 .environ_map = environ_map,
438 .cwd = tmp_dir_path,433 .cwd = tmp_dir_path,
439 .max_output_bytes = max_doc_file_size,434 .max_output_bytes = max_doc_file_size,
440 });435 });
441 switch (result.term) {436 switch (result.term) {
442 .Exited => |exit_code| {437 .exited => |exit_code| {
443 if (exit_code == 0) {438 if (exit_code == 0) {
444 print("{s}\nThe following command incorrectly succeeded:\n", .{result.stderr});439 print("{s}\nThe following command incorrectly succeeded:\n", .{result.stderr});
445 dumpArgs(test_args.items);440 dumpArgs(test_args.items);
...@@ -508,14 +503,14 @@ fn printOutput(...@@ -508,14 +503,14 @@ fn printOutput(
508 }503 }
509504
510 if (maybe_error_match) |error_match| {505 if (maybe_error_match) |error_match| {
511 const result = try process.Child.run(arena, io, .{506 const result = try process.run(arena, io, .{
512 .argv = build_args.items,507 .argv = build_args.items,
513 .env_map = &env_map,508 .environ_map = environ_map,
514 .cwd = tmp_dir_path,509 .cwd = tmp_dir_path,
515 .max_output_bytes = max_doc_file_size,510 .max_output_bytes = max_doc_file_size,
516 });511 });
517 switch (result.term) {512 switch (result.term) {
518 .Exited => |exit_code| {513 .exited => |exit_code| {
519 if (exit_code == 0) {514 if (exit_code == 0) {
520 print("{s}\nThe following command incorrectly succeeded:\n", .{result.stderr});515 print("{s}\nThe following command incorrectly succeeded:\n", .{result.stderr});
521 dumpArgs(build_args.items);516 dumpArgs(build_args.items);
...@@ -536,7 +531,7 @@ fn printOutput(...@@ -536,7 +531,7 @@ fn printOutput(
536 const colored_stderr = try termColor(arena, escaped_stderr);531 const colored_stderr = try termColor(arena, escaped_stderr);
537 try shell_out.print("\n{s} ", .{colored_stderr});532 try shell_out.print("\n{s} ", .{colored_stderr});
538 } else {533 } else {
539 _ = run(arena, io, &env_map, tmp_dir_path, build_args.items) catch fatal("example failed to compile", .{});534 _ = run(arena, io, environ_map, tmp_dir_path, build_args.items) catch fatal("example failed to compile", .{});
540 }535 }
541 try shell_out.writeAll("\n");536 try shell_out.writeAll("\n");
542 },537 },
...@@ -595,7 +590,7 @@ fn printOutput(...@@ -595,7 +590,7 @@ fn printOutput(
595 try test_args.append(option);590 try test_args.append(option);
596 try shell_out.print("{s} ", .{option});591 try shell_out.print("{s} ", .{option});
597 }592 }
598 const result = run(arena, io, &env_map, tmp_dir_path, test_args.items) catch fatal("test failed", .{});593 const result = run(arena, io, environ_map, tmp_dir_path, test_args.items) catch fatal("test failed", .{});
599 const escaped_stderr = try escapeHtml(arena, result.stderr);594 const escaped_stderr = try escapeHtml(arena, result.stderr);
600 const escaped_stdout = try escapeHtml(arena, result.stdout);595 const escaped_stdout = try escapeHtml(arena, result.stdout);
601 try shell_out.print("\n{s}{s}\n", .{ escaped_stderr, escaped_stdout });596 try shell_out.print("\n{s}{s}\n", .{ escaped_stderr, escaped_stdout });
...@@ -1128,24 +1123,29 @@ fn in(slice: []const u8, number: u8) bool {...@@ -1128,24 +1123,29 @@ fn in(slice: []const u8, number: u8) bool {
1128fn run(1123fn run(
1129 allocator: Allocator,1124 allocator: Allocator,
1130 io: Io,1125 io: Io,
1131 env_map: *process.EnvMap,1126 environ_map: *const process.Environ.Map,
1132 cwd: []const u8,1127 cwd: []const u8,
1133 args: []const []const u8,1128 args: []const []const u8,
1134) !process.Child.RunResult {1129) !process.RunResult {
1135 const result = try process.Child.run(allocator, io, .{1130 const result = try process.run(allocator, io, .{
1136 .argv = args,1131 .argv = args,
1137 .env_map = env_map,1132 .environ_map = environ_map,
1138 .cwd = cwd,1133 .cwd = cwd,
1139 .max_output_bytes = max_doc_file_size,1134 .max_output_bytes = max_doc_file_size,
1140 });1135 });
1141 switch (result.term) {1136 switch (result.term) {
1142 .Exited => |exit_code| {1137 .exited => |exit_code| {
1143 if (exit_code != 0) {1138 if (exit_code != 0) {
1144 std.debug.print("{s}\nThe following command exited with code {}:\n", .{ result.stderr, exit_code });1139 std.debug.print("{s}\nThe following command exited with code {}:\n", .{ result.stderr, exit_code });
1145 dumpArgs(args);1140 dumpArgs(args);
1146 return error.ChildExitError;1141 return error.ChildExitError;
1147 }1142 }
1148 },1143 },
1144 .signal => |sig| {
1145 std.debug.print("{s}\nThe following command terminated with signal {t}:\n", .{ result.stderr, sig });
1146 dumpArgs(args);
1147 return error.ChildCrashed;
1148 },
1149 else => {1149 else => {
1150 std.debug.print("{s}\nThe following command crashed:\n", .{result.stderr});1150 std.debug.print("{s}\nThe following command crashed:\n", .{result.stderr});
1151 dumpArgs(args);1151 dumpArgs(args);
tools/dump-cov.zig+5-14
...@@ -8,20 +8,11 @@ const Path = std.Build.Cache.Path;...@@ -8,20 +8,11 @@ const Path = std.Build.Cache.Path;
8const assert = std.debug.assert;8const assert = std.debug.assert;
9const SeenPcsHeader = std.Build.abi.fuzz.SeenPcsHeader;9const SeenPcsHeader = std.Build.abi.fuzz.SeenPcsHeader;
1010
11pub fn main() !void {11pub fn main(init: std.process.Init) !void {
12 var debug_allocator: std.heap.DebugAllocator(.{}) = .init;12 const gpa = init.gpa;
13 defer _ = debug_allocator.deinit();13 const arena = init.arena.allocator();
14 const gpa = debug_allocator.allocator();14 const io = init.io;
1515 const args = try init.minimal.args.toSlice(arena);
16 var arena_instance: std.heap.ArenaAllocator = .init(gpa);
17 defer arena_instance.deinit();
18 const arena = arena_instance.allocator();
19
20 var threaded: Io.Threaded = .init(gpa, .{});
21 defer threaded.deinit();
22 const io = threaded.io();
23
24 const args = try std.process.argsAlloc(arena);
2516
26 const target_query_str = switch (args.len) {17 const target_query_str = switch (args.len) {
27 3 => "native",18 3 => "native",
tools/fetch_them_macos_headers.zig+11-21
...@@ -6,13 +6,9 @@ const process = std.process;...@@ -6,13 +6,9 @@ const process = std.process;
6const assert = std.debug.assert;6const assert = std.debug.assert;
7const fatal = std.process.fatal;7const fatal = std.process.fatal;
8const info = std.log.info;8const info = std.log.info;
99const Allocator = std.mem.Allocator;
10const Allocator = mem.Allocator;
11const OsTag = std.Target.Os.Tag;10const OsTag = std.Target.Os.Tag;
1211
13var general_purpose_allocator = std.heap.GeneralPurposeAllocator(.{}){};
14const gpa = general_purpose_allocator.allocator();
15
16const Arch = enum {12const Arch = enum {
17 aarch64,13 aarch64,
18 x86_64,14 x86_64,
...@@ -66,14 +62,12 @@ const usage =...@@ -66,14 +62,12 @@ const usage =
66 \\-h, --help Print this help and exit62 \\-h, --help Print this help and exit
67;63;
6864
69pub fn main() anyerror!void {65pub fn main(init: std.process.Init) !void {
70 var arena = std.heap.ArenaAllocator.init(gpa);66 const io = init.io;
71 defer arena.deinit();67 const arena = init.arena.allocator();
72 const allocator = arena.allocator();68 const args = try init.minimal.args.toSlice(arena);
7369
74 const args = try std.process.argsAlloc(allocator);70 var argv = std.array_list.Managed([]const u8).init(arena);
75
76 var argv = std.array_list.Managed([]const u8).init(allocator);
77 var sysroot: ?[]const u8 = null;71 var sysroot: ?[]const u8 = null;
7872
79 var args_iter = ArgsIterator{ .args = args[1..] };73 var args_iter = ArgsIterator{ .args = args[1..] };
...@@ -85,23 +79,19 @@ pub fn main() anyerror!void {...@@ -85,23 +79,19 @@ pub fn main() anyerror!void {
85 } else try argv.append(arg);79 } else try argv.append(arg);
86 }80 }
8781
88 var threaded: Io.Threaded = .init(gpa, .{});
89 defer threaded.deinit();
90 const io = threaded.io();
91
92 const sysroot_path = sysroot orelse blk: {82 const sysroot_path = sysroot orelse blk: {
93 const target = try std.zig.system.resolveTargetQuery(io, .{});83 const target = try std.zig.system.resolveTargetQuery(io, .{});
94 break :blk std.zig.system.darwin.getSdk(allocator, io, &target) orelse84 break :blk std.zig.system.darwin.getSdk(arena, io, &target) orelse
95 fatal("no SDK found; you can provide one explicitly with '--sysroot' flag", .{});85 fatal("no SDK found; you can provide one explicitly with '--sysroot' flag", .{});
96 };86 };
9787
98 var sdk_dir = try Dir.cwd().openDir(io, sysroot_path, .{});88 var sdk_dir = try Dir.cwd().openDir(io, sysroot_path, .{});
99 defer sdk_dir.close(io);89 defer sdk_dir.close(io);
100 const sdk_info = try sdk_dir.readFileAlloc(io, "SDKSettings.json", allocator, .limited(std.math.maxInt(u32)));90 const sdk_info = try sdk_dir.readFileAlloc(io, "SDKSettings.json", arena, .limited(std.math.maxInt(u32)));
10191
102 const parsed_json = try std.json.parseFromSlice(struct {92 const parsed_json = try std.json.parseFromSlice(struct {
103 DefaultProperties: struct { MACOSX_DEPLOYMENT_TARGET: []const u8 },93 DefaultProperties: struct { MACOSX_DEPLOYMENT_TARGET: []const u8 },
104 }, allocator, sdk_info, .{ .ignore_unknown_fields = true });94 }, arena, sdk_info, .{ .ignore_unknown_fields = true });
10595
106 const version = Version.parse(parsed_json.value.DefaultProperties.MACOSX_DEPLOYMENT_TARGET) orelse96 const version = Version.parse(parsed_json.value.DefaultProperties.MACOSX_DEPLOYMENT_TARGET) orelse
107 fatal("don't know how to parse SDK version: {s}", .{97 fatal("don't know how to parse SDK version: {s}", .{
...@@ -117,7 +107,7 @@ pub fn main() anyerror!void {...@@ -117,7 +107,7 @@ pub fn main() anyerror!void {
117 .arch = arch,107 .arch = arch,
118 .os_ver = os_ver,108 .os_ver = os_ver,
119 };109 };
120 try fetchTarget(allocator, io, argv.items, sysroot_path, target, version, tmp_dir);110 try fetchTarget(arena, io, argv.items, sysroot_path, target, version, tmp_dir);
121 }111 }
122}112}
123113
...@@ -164,7 +154,7 @@ fn fetchTarget(...@@ -164,7 +154,7 @@ fn fetchTarget(
164 });154 });
165 try cc_argv.appendSlice(args);155 try cc_argv.appendSlice(args);
166156
167 const res = try std.process.Child.run(arena, io, .{ .argv = cc_argv.items });157 const res = try std.process.run(arena, io, .{ .argv = cc_argv.items });
168158
169 if (res.stderr.len != 0) {159 if (res.stderr.len != 0) {
170 std.log.err("{s}", .{res.stderr});160 std.log.err("{s}", .{res.stderr});
tools/gen_macos_headers_c.zig+4-12
...@@ -6,9 +6,6 @@ const info = std.log.info;...@@ -6,9 +6,6 @@ const info = std.log.info;
6const fatal = std.process.fatal;6const fatal = std.process.fatal;
7const Allocator = std.mem.Allocator;7const Allocator = std.mem.Allocator;
88
9var general_purpose_allocator = std.heap.GeneralPurposeAllocator(.{}){};
10const gpa = general_purpose_allocator.allocator();
11
12const usage =9const usage =
13 \\gen_macos_headers_c [dir]10 \\gen_macos_headers_c [dir]
14 \\11 \\
...@@ -16,16 +13,11 @@ const usage =...@@ -16,16 +13,11 @@ const usage =
16 \\-h, --help Print this help and exit13 \\-h, --help Print this help and exit
17;14;
1815
19pub fn main() anyerror!void {16pub fn main(init: std.process.Init) !void {
20 var arena_allocator = std.heap.ArenaAllocator.init(gpa);17 const arena = init.arena.allocator();
21 defer arena_allocator.deinit();18 const io = init.io;
22 const arena = arena_allocator.allocator();19 const args = try init.minimal.args.toSlice(arena);
23
24 var threaded: Io.Threaded = .init(gpa, .{});
25 defer threaded.deinit();
26 const io = threaded.io();
2720
28 const args = try std.process.argsAlloc(arena);
29 if (args.len == 1) fatal("no command or option specified", .{});21 if (args.len == 1) fatal("no command or option specified", .{});
3022
31 var positionals = std.array_list.Managed([]const u8).init(arena);23 var positionals = std.array_list.Managed([]const u8).init(arena);
tools/gen_outline_atomics.zig+3-8
...@@ -11,14 +11,9 @@ const AtomicOp = enum {...@@ -11,14 +11,9 @@ const AtomicOp = enum {
11 ldset,11 ldset,
12};12};
1313
14pub fn main() !void {14pub fn main(init: std.process.Init) !void {
15 var arena_instance = std.heap.ArenaAllocator.init(std.heap.page_allocator);15 const arena = init.arena.allocator();
16 defer arena_instance.deinit();16 const io = init.io;
17 const arena = arena_instance.allocator();
18
19 var threaded: std.Io.Threaded = .init(arena, .{});
20 defer threaded.deinit();
21 const io = threaded.io();
2217
23 //const args = try std.process.argsAlloc(arena);18 //const args = try std.process.argsAlloc(arena);
2419
tools/gen_spirv_spec.zig+49-49
...@@ -1,6 +1,7 @@...@@ -1,6 +1,7 @@
1const std = @import("std");1const std = @import("std");
2const Io = std.Io;2const Io = std.Io;
3const Allocator = std.mem.Allocator;3const Allocator = std.mem.Allocator;
4const assert = std.debug.assert;
45
5const g = @import("spirv/grammar.zig");6const g = @import("spirv/grammar.zig");
6const CoreRegistry = g.CoreRegistry;7const CoreRegistry = g.CoreRegistry;
...@@ -54,28 +55,22 @@ const set_names = std.StaticStringMap(struct { []const u8, []const u8 }).initCom...@@ -54,28 +55,22 @@ const set_names = std.StaticStringMap(struct { []const u8, []const u8 }).initCom
54 .{ "zig", .{ "zig", "Zig" } },55 .{ "zig", .{ "zig", "Zig" } },
55});56});
5657
57var arena = std.heap.ArenaAllocator.init(std.heap.smp_allocator);58pub fn main(init: std.process.Init) !void {
58const allocator = arena.allocator();59 const arena = init.arena.allocator();
5960 const args = try init.minimal.args.toSlice(arena);
60pub fn main() !void {
61 defer arena.deinit();
62
63 const args = try std.process.argsAlloc(allocator);
64 if (args.len != 3) {61 if (args.len != 3) {
65 usageAndExit(args[0], 1);62 usageAndExit(args[0], 1);
66 }63 }
6764
68 var threaded: std.Io.Threaded = .init(allocator, .{});65 const io = init.io;
69 defer threaded.deinit();
70 const io = threaded.io();
7166
72 const json_path = try Io.Dir.path.join(allocator, &.{ args[1], "include/spirv/unified1/" });67 const json_path = try Io.Dir.path.join(arena, &.{ args[1], "include/spirv/unified1/" });
73 const dir = try Io.Dir.cwd().openDir(io, json_path, .{ .iterate = true });68 const dir = try Io.Dir.cwd().openDir(io, json_path, .{ .iterate = true });
7469
75 const core_spec = try readRegistry(io, CoreRegistry, dir, "spirv.core.grammar.json");70 const core_spec = try readRegistry(io, arena, CoreRegistry, dir, "spirv.core.grammar.json");
76 std.mem.sortUnstable(Instruction, core_spec.instructions, CmpInst{}, CmpInst.lt);71 std.mem.sortUnstable(Instruction, core_spec.instructions, CmpInst{}, CmpInst.lt);
7772
78 var exts = std.array_list.Managed(Extension).init(allocator);73 var exts = std.array_list.Managed(Extension).init(arena);
7974
80 var it = dir.iterate();75 var it = dir.iterate();
81 while (try it.next(io)) |entry| {76 while (try it.next(io)) |entry| {
...@@ -83,48 +78,48 @@ pub fn main() !void {...@@ -83,48 +78,48 @@ pub fn main() !void {
83 continue;78 continue;
84 }79 }
8580
86 try readExtRegistry(io, &exts, dir, entry.name);81 try readExtRegistry(io, arena, &exts, dir, entry.name);
87 }82 }
8883
89 try readExtRegistry(io, &exts, Io.Dir.cwd(), args[2]);84 try readExtRegistry(io, arena, &exts, Io.Dir.cwd(), args[2]);
9085
91 var allocating: std.Io.Writer.Allocating = .init(allocator);86 var allocating: std.Io.Writer.Allocating = .init(arena);
92 defer allocating.deinit();87 defer allocating.deinit();
93 try render(&allocating.writer, core_spec, exts.items);88 try render(arena, &allocating.writer, core_spec, exts.items);
94 try allocating.writer.writeByte(0);89 try allocating.writer.writeByte(0);
95 const output = allocating.written()[0 .. allocating.written().len - 1 :0];90 const output = allocating.written()[0 .. allocating.written().len - 1 :0];
9691
97 var tree = try std.zig.Ast.parse(allocator, output, .zig);92 var tree = try std.zig.Ast.parse(arena, output, .zig);
9893
99 if (tree.errors.len != 0) {94 if (tree.errors.len != 0) {
100 try std.zig.printAstErrorsToStderr(allocator, io, tree, "", .auto);95 try std.zig.printAstErrorsToStderr(arena, io, tree, "", .auto);
101 return;96 return;
102 }97 }
10398
104 var zir = try std.zig.AstGen.generate(allocator, tree);99 var zir = try std.zig.AstGen.generate(arena, tree);
105 if (zir.hasCompileErrors()) {100 if (zir.hasCompileErrors()) {
106 var wip_errors: std.zig.ErrorBundle.Wip = undefined;101 var wip_errors: std.zig.ErrorBundle.Wip = undefined;
107 try wip_errors.init(allocator);102 try wip_errors.init(arena);
108 defer wip_errors.deinit();103 defer wip_errors.deinit();
109 try wip_errors.addZirErrorMessages(zir, tree, output, "");104 try wip_errors.addZirErrorMessages(zir, tree, output, "");
110 var error_bundle = try wip_errors.toOwnedBundle("");105 var error_bundle = try wip_errors.toOwnedBundle("");
111 defer error_bundle.deinit(allocator);106 defer error_bundle.deinit(arena);
112 try error_bundle.renderToStderr(io, .{}, .auto);107 try error_bundle.renderToStderr(io, .{}, .auto);
113 }108 }
114109
115 const formatted_output = try tree.renderAlloc(allocator);110 const formatted_output = try tree.renderAlloc(arena);
116 try Io.File.stdout().writeStreamingAll(io, formatted_output);111 try Io.File.stdout().writeStreamingAll(io, formatted_output);
117}112}
118113
119fn readExtRegistry(io: Io, exts: *std.array_list.Managed(Extension), dir: Io.Dir, sub_path: []const u8) !void {114fn readExtRegistry(io: Io, arena: Allocator, exts: *std.array_list.Managed(Extension), dir: Io.Dir, sub_path: []const u8) !void {
120 const filename = Io.Dir.path.basename(sub_path);115 const filename = Io.Dir.path.basename(sub_path);
121 if (!std.mem.startsWith(u8, filename, "extinst.")) {116 if (!std.mem.startsWith(u8, filename, "extinst.")) {
122 return;117 return;
123 }118 }
124119
125 std.debug.assert(std.mem.endsWith(u8, filename, ".grammar.json"));120 assert(std.mem.endsWith(u8, filename, ".grammar.json"));
126 const name = filename["extinst.".len .. filename.len - ".grammar.json".len];121 const name = filename["extinst.".len .. filename.len - ".grammar.json".len];
127 const spec = try readRegistry(io, ExtensionRegistry, dir, sub_path);122 const spec = try readRegistry(io, arena, ExtensionRegistry, dir, sub_path);
128123
129 const set_name = set_names.get(name) orelse {124 const set_name = set_names.get(name) orelse {
130 std.log.info("ignored instruction set '{s}'", .{name});125 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...@@ -140,16 +135,16 @@ fn readExtRegistry(io: Io, exts: *std.array_list.Managed(Extension), dir: Io.Dir
140 });135 });
141}136}
142137
143fn readRegistry(io: Io, comptime RegistryType: type, dir: Io.Dir, path: []const u8) !RegistryType {138fn readRegistry(io: Io, arena: Allocator, comptime RegistryType: type, dir: Io.Dir, path: []const u8) !RegistryType {
144 const spec = try dir.readFileAlloc(io, path, allocator, .unlimited);139 const spec = try dir.readFileAlloc(io, path, arena, .unlimited);
145 // Required for json parsing.140 // Required for json parsing.
146 // TODO: ALI141 // TODO: ALI
147 @setEvalBranchQuota(10000);142 @setEvalBranchQuota(10000);
148143
149 var scanner = std.json.Scanner.initCompleteInput(allocator, spec);144 var scanner = std.json.Scanner.initCompleteInput(arena, spec);
150 var diagnostics = std.json.Diagnostics{};145 var diagnostics = std.json.Diagnostics{};
151 scanner.enableDiagnostics(&diagnostics);146 scanner.enableDiagnostics(&diagnostics);
152 const parsed = std.json.parseFromTokenSource(RegistryType, allocator, &scanner, .{}) catch |err| {147 const parsed = std.json.parseFromTokenSource(RegistryType, arena, &scanner, .{}) catch |err| {
153 std.debug.print("{s}:{}:{}:\n", .{ path, diagnostics.getLine(), diagnostics.getColumn() });148 std.debug.print("{s}:{}:{}:\n", .{ path, diagnostics.getLine(), diagnostics.getColumn() });
154 return err;149 return err;
155 };150 };
...@@ -158,8 +153,8 @@ fn readRegistry(io: Io, comptime RegistryType: type, dir: Io.Dir, path: []const...@@ -158,8 +153,8 @@ fn readRegistry(io: Io, comptime RegistryType: type, dir: Io.Dir, path: []const
158153
159/// Returns a set with types that require an extra struct for the `Instruction` interface154/// Returns a set with types that require an extra struct for the `Instruction` interface
160/// to the spir-v spec, or whether the original type can be used.155/// to the spir-v spec, or whether the original type can be used.
161fn extendedStructs(kinds: []const OperandKind) !ExtendedStructSet {156fn extendedStructs(arena: Allocator, kinds: []const OperandKind) !ExtendedStructSet {
162 var map = ExtendedStructSet.init(allocator);157 var map = ExtendedStructSet.init(arena);
163 try map.ensureTotalCapacity(@as(u32, @intCast(kinds.len)));158 try map.ensureTotalCapacity(@as(u32, @intCast(kinds.len)));
164159
165 for (kinds) |kind| {160 for (kinds) |kind| {
...@@ -194,6 +189,7 @@ fn tagPriorityScore(tag: []const u8) usize {...@@ -194,6 +189,7 @@ fn tagPriorityScore(tag: []const u8) usize {
194}189}
195190
196fn render(191fn render(
192 arena: Allocator,
197 writer: *std.Io.Writer,193 writer: *std.Io.Writer,
198 registry: CoreRegistry,194 registry: CoreRegistry,
199 extensions: []const Extension,195 extensions: []const Extension,
...@@ -299,7 +295,7 @@ fn render(...@@ -299,7 +295,7 @@ fn render(
299 );295 );
300296
301 // Merge the operand kinds from all extensions together.297 // Merge the operand kinds from all extensions together.
302 var all_operand_kinds = OperandKindMap.init(allocator);298 var all_operand_kinds = OperandKindMap.init(arena);
303 for (registry.operand_kinds) |kind| {299 for (registry.operand_kinds) |kind| {
304 try all_operand_kinds.putNoClobber(.{ "core", kind.kind }, kind);300 try all_operand_kinds.putNoClobber(.{ "core", kind.kind }, kind);
305 }301 }
...@@ -312,22 +308,22 @@ fn render(...@@ -312,22 +308,22 @@ fn render(
312 try all_operand_kinds.ensureUnusedCapacity(ext.spec.operand_kinds.len);308 try all_operand_kinds.ensureUnusedCapacity(ext.spec.operand_kinds.len);
313 for (ext.spec.operand_kinds) |kind| {309 for (ext.spec.operand_kinds) |kind| {
314 var new_kind = kind;310 var new_kind = kind;
315 new_kind.kind = try std.mem.join(allocator, ".", &.{ ext.name, kind.kind });311 new_kind.kind = try std.mem.join(arena, ".", &.{ ext.name, kind.kind });
316 try all_operand_kinds.putNoClobber(.{ ext.name, kind.kind }, new_kind);312 try all_operand_kinds.putNoClobber(.{ ext.name, kind.kind }, new_kind);
317 }313 }
318 }314 }
319315
320 const extended_structs = try extendedStructs(all_operand_kinds.values());316 const extended_structs = try extendedStructs(arena, all_operand_kinds.values());
321 // Note: extensions don't seem to have class.317 // Note: extensions don't seem to have class.
322 try renderClass(writer, registry.instructions);318 try renderClass(arena, writer, registry.instructions);
323 try renderOperandKind(writer, all_operand_kinds.values());319 try renderOperandKind(writer, all_operand_kinds.values());
324320
325 try renderOpcodes(writer, "Opcode", true, registry.instructions, extended_structs);321 try renderOpcodes(arena, writer, "Opcode", true, registry.instructions, extended_structs);
326 for (extensions) |ext| {322 for (extensions) |ext| {
327 try renderOpcodes(writer, ext.opcode_name, false, ext.spec.instructions, extended_structs);323 try renderOpcodes(arena, writer, ext.opcode_name, false, ext.spec.instructions, extended_structs);
328 }324 }
329325
330 try renderOperandKinds(writer, all_operand_kinds.values(), extended_structs);326 try renderOperandKinds(arena, writer, all_operand_kinds.values(), extended_structs);
331 try renderInstructionSet(writer, registry, extensions, all_operand_kinds);327 try renderInstructionSet(writer, registry, extensions, all_operand_kinds);
332}328}
333329
...@@ -414,8 +410,8 @@ fn renderInstructionsCase(...@@ -414,8 +410,8 @@ fn renderInstructionsCase(
414 );410 );
415}411}
416412
417fn renderClass(writer: *std.Io.Writer, instructions: []const Instruction) !void {413fn renderClass(arena: Allocator, writer: *std.Io.Writer, instructions: []const Instruction) !void {
418 var class_map = std.StringArrayHashMap(void).init(allocator);414 var class_map = std.StringArrayHashMap(void).init(arena);
419415
420 for (instructions) |inst| {416 for (instructions) |inst| {
421 if (std.mem.eql(u8, inst.class.?, "@exclude")) continue;417 if (std.mem.eql(u8, inst.class.?, "@exclude")) continue;
...@@ -535,16 +531,17 @@ fn renderEnumerant(writer: *std.Io.Writer, enumerant: Enumerant) !void {...@@ -535,16 +531,17 @@ fn renderEnumerant(writer: *std.Io.Writer, enumerant: Enumerant) !void {
535}531}
536532
537fn renderOpcodes(533fn renderOpcodes(
534 arena: Allocator,
538 writer: *std.Io.Writer,535 writer: *std.Io.Writer,
539 opcode_type_name: []const u8,536 opcode_type_name: []const u8,
540 want_operands: bool,537 want_operands: bool,
541 instructions: []const Instruction,538 instructions: []const Instruction,
542 extended_structs: ExtendedStructSet,539 extended_structs: ExtendedStructSet,
543) !void {540) !void {
544 var inst_map = std.AutoArrayHashMap(u32, usize).init(allocator);541 var inst_map = std.AutoArrayHashMap(u32, usize).init(arena);
545 try inst_map.ensureTotalCapacity(instructions.len);542 try inst_map.ensureTotalCapacity(instructions.len);
546543
547 var aliases = std.array_list.Managed(struct { inst: usize, alias: usize }).init(allocator);544 var aliases = std.array_list.Managed(struct { inst: usize, alias: usize }).init(arena);
548 try aliases.ensureTotalCapacity(instructions.len);545 try aliases.ensureTotalCapacity(instructions.len);
549546
550 for (instructions, 0..) |inst, i| {547 for (instructions, 0..) |inst, i| {
...@@ -634,30 +631,32 @@ fn renderOpcodes(...@@ -634,30 +631,32 @@ fn renderOpcodes(
634}631}
635632
636fn renderOperandKinds(633fn renderOperandKinds(
634 arena: Allocator,
637 writer: *std.Io.Writer,635 writer: *std.Io.Writer,
638 kinds: []const OperandKind,636 kinds: []const OperandKind,
639 extended_structs: ExtendedStructSet,637 extended_structs: ExtendedStructSet,
640) !void {638) !void {
641 for (kinds) |kind| {639 for (kinds) |kind| {
642 switch (kind.category) {640 switch (kind.category) {
643 .ValueEnum => try renderValueEnum(writer, kind, extended_structs),641 .ValueEnum => try renderValueEnum(arena, writer, kind, extended_structs),
644 .BitEnum => try renderBitEnum(writer, kind, extended_structs),642 .BitEnum => try renderBitEnum(arena, writer, kind, extended_structs),
645 else => {},643 else => {},
646 }644 }
647 }645 }
648}646}
649647
650fn renderValueEnum(648fn renderValueEnum(
649 arena: Allocator,
651 writer: *std.Io.Writer,650 writer: *std.Io.Writer,
652 enumeration: OperandKind,651 enumeration: OperandKind,
653 extended_structs: ExtendedStructSet,652 extended_structs: ExtendedStructSet,
654) !void {653) !void {
655 const enumerants = enumeration.enumerants orelse return error.InvalidRegistry;654 const enumerants = enumeration.enumerants orelse return error.InvalidRegistry;
656655
657 var enum_map = std.AutoArrayHashMap(u32, usize).init(allocator);656 var enum_map = std.AutoArrayHashMap(u32, usize).init(arena);
658 try enum_map.ensureTotalCapacity(enumerants.len);657 try enum_map.ensureTotalCapacity(enumerants.len);
659658
660 var aliases = std.array_list.Managed(struct { enumerant: usize, alias: usize }).init(allocator);659 var aliases = std.array_list.Managed(struct { enumerant: usize, alias: usize }).init(arena);
661 try aliases.ensureTotalCapacity(enumerants.len);660 try aliases.ensureTotalCapacity(enumerants.len);
662661
663 for (enumerants, 0..) |enumerant, i| {662 for (enumerants, 0..) |enumerant, i| {
...@@ -726,6 +725,7 @@ fn renderValueEnum(...@@ -726,6 +725,7 @@ fn renderValueEnum(
726}725}
727726
728fn renderBitEnum(727fn renderBitEnum(
728 arena: Allocator,
729 writer: *std.Io.Writer,729 writer: *std.Io.Writer,
730 enumeration: OperandKind,730 enumeration: OperandKind,
731 extended_structs: ExtendedStructSet,731 extended_structs: ExtendedStructSet,
...@@ -735,7 +735,7 @@ fn renderBitEnum(...@@ -735,7 +735,7 @@ fn renderBitEnum(
735 var flags_by_bitpos = [_]?usize{null} ** 32;735 var flags_by_bitpos = [_]?usize{null} ** 32;
736 const enumerants = enumeration.enumerants orelse return error.InvalidRegistry;736 const enumerants = enumeration.enumerants orelse return error.InvalidRegistry;
737737
738 var aliases = std.array_list.Managed(struct { flag: usize, alias: u5 }).init(allocator);738 var aliases = std.array_list.Managed(struct { flag: usize, alias: u5 }).init(arena);
739 try aliases.ensureTotalCapacity(enumerants.len);739 try aliases.ensureTotalCapacity(enumerants.len);
740740
741 for (enumerants, 0..) |enumerant, i| {741 for (enumerants, 0..) |enumerant, i| {
...@@ -749,7 +749,7 @@ fn renderBitEnum(...@@ -749,7 +749,7 @@ fn renderBitEnum(
749 continue;749 continue;
750 }750 }
751751
752 std.debug.assert(@popCount(value) == 1);752 assert(@popCount(value) == 1);
753753
754 const bitpos = std.math.log2_int(u32, value);754 const bitpos = std.math.log2_int(u32, value);
755 if (flags_by_bitpos[bitpos]) |*existing| {755 if (flags_by_bitpos[bitpos]) |*existing| {
tools/gen_stubs.zig+4-10
...@@ -281,16 +281,10 @@ const Parse = struct {...@@ -281,16 +281,10 @@ const Parse = struct {
281 arch: Arch,281 arch: Arch,
282};282};
283283
284pub fn main() !void {284pub fn main(init: std.process.Init) !void {
285 var arena_instance = std.heap.ArenaAllocator.init(std.heap.page_allocator);285 const arena = init.arena.allocator();
286 defer arena_instance.deinit();286 const io = init.io;
287 const arena = arena_instance.allocator();287 const args = try init.minimal.args.toSlice(arena);
288
289 var threaded: std.Io.Threaded = .init(arena, .{});
290 defer threaded.deinit();
291 const io = threaded.io();
292
293 const args = try std.process.argsAlloc(arena);
294 const build_all_path = args[1];288 const build_all_path = args[1];
295289
296 var build_all_dir = try Io.Dir.cwd().openDir(io, build_all_path, .{});290 var build_all_dir = try Io.Dir.cwd().openDir(io, build_all_path, .{});
tools/generate_JSONTestSuite.zig+3-7
...@@ -3,13 +3,9 @@...@@ -3,13 +3,9 @@
3const std = @import("std");3const std = @import("std");
4const Io = std.Io;4const Io = std.Io;
55
6pub fn main() !void {6pub fn main(init: std.process.Init) !void {
7 var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init;7 const allocator = init.gpa;
8 var allocator = gpa.allocator();8 const io = init.io;
9
10 var threaded: std.Io.Threaded = .init(allocator, .{});
11 defer threaded.deinit();
12 const io = threaded.io();
139
14 var stdout_buffer: [2000]u8 = undefined;10 var stdout_buffer: [2000]u8 = undefined;
15 var stdout_writer = Io.File.stdout().writerStreaming(io, &stdout_buffer);11 var stdout_writer = Io.File.stdout().writerStreaming(io, &stdout_buffer);
tools/generate_c_size_and_align_checks.zig+3-10
...@@ -28,22 +28,15 @@ fn cName(ty: std.Target.CType) []const u8 {...@@ -28,22 +28,15 @@ fn cName(ty: std.Target.CType) []const u8 {
2828
29var general_purpose_allocator: std.heap.GeneralPurposeAllocator(.{}) = .init;29var general_purpose_allocator: std.heap.GeneralPurposeAllocator(.{}) = .init;
3030
31pub fn main() !void {31pub fn main(init: std.process.Init) !void {
32 const gpa = general_purpose_allocator.allocator();32 const args = try init.minimal.args.toSlice(init.arena.allocator());
33 defer std.debug.assert(general_purpose_allocator.deinit() == .ok);33 const io = init.io;
34
35 const args = try std.process.argsAlloc(gpa);
36 defer std.process.argsFree(gpa, args);
3734
38 if (args.len != 2) {35 if (args.len != 2) {
39 std.debug.print("Usage: {s} [target_triple]\n", .{args[0]});36 std.debug.print("Usage: {s} [target_triple]\n", .{args[0]});
40 std.process.exit(1);37 std.process.exit(1);
41 }38 }
4239
43 var threaded: std.Io.Threaded = .init(gpa, .{});
44 defer threaded.deinit();
45 const io = threaded.io();
46
47 const query = try std.Target.Query.parse(.{ .arch_os_abi = args[1] });40 const query = try std.Target.Query.parse(.{ .arch_os_abi = args[1] });
48 const target = try std.zig.system.resolveTargetQuery(io, query);41 const target = try std.zig.system.resolveTargetQuery(io, query);
4942
tools/generate_linux_syscalls.zig+5-11
...@@ -170,16 +170,11 @@ const architectures: []const Arch = &.{...@@ -170,16 +170,11 @@ const architectures: []const Arch = &.{
170 // .{ .@"var" = "Microblaze", .table = .{ .specific = "arch/microblaze/kernel/syscalls/syscall.tbl" } },170 // .{ .@"var" = "Microblaze", .table = .{ .specific = "arch/microblaze/kernel/syscalls/syscall.tbl" } },
171};171};
172172
173pub fn main() !void {173pub fn main(init: std.process.Init) !void {
174 var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);174 const gpa = init.gpa;
175 defer arena.deinit();175 const io = init.io;
176 const gpa = arena.allocator();
177176
178 var threaded: Io.Threaded = .init(gpa, .{});177 const args = try init.minimal.args.toSlice(init.arena.allocator());
179 defer threaded.deinit();
180 const io = threaded.io();
181
182 const args = try std.process.argsAlloc(gpa);
183 if (args.len < 2 or mem.eql(u8, args[1], "--help")) {178 if (args.len < 2 or mem.eql(u8, args[1], "--help")) {
184 const stderr = std.debug.lockStderr(&.{});179 const stderr = std.debug.lockStderr(&.{});
185 const w = &stderr.file_writer.interface;180 const w = &stderr.file_writer.interface;
...@@ -217,8 +212,7 @@ pub fn main() !void {...@@ -217,8 +212,7 @@ pub fn main() !void {
217 };212 };
218213
219 try Io.Writer.print(stdout,214 try Io.Writer.print(stdout,
220 \\// This file is automatically generated, DO NOT edit it manually.215 \\// This file is automatically generated by tools/generate_linux_syscalls.zig
221 \\// See tools/generate_linux_syscalls.zig for more info.
222 \\// This list current as of kernel: {f}216 \\// This list current as of kernel: {f}
223 \\217 \\
224 \\218 \\
tools/incr-check.zig+34-38
...@@ -27,18 +27,12 @@ fn logImpl(...@@ -27,18 +27,12 @@ fn logImpl(
27 );27 );
28}28}
2929
30pub fn main() !void {30pub fn main(init: std.process.Init) !void {
31 const fatal = std.process.fatal;31 const fatal = std.process.fatal;
3232 const arena = init.arena.allocator();
33 var arena_instance = std.heap.ArenaAllocator.init(std.heap.page_allocator);33 const io = init.io;
34 defer arena_instance.deinit();34 const environ_map = init.environ_map;
35 const arena = arena_instance.allocator();35 const cwd_path = try std.process.getCwdAlloc(arena);
36
37 const gpa = arena;
38
39 var threaded: Io.Threaded = .init(gpa, .{});
40 defer threaded.deinit();
41 const io = threaded.io();
4236
43 var opt_zig_exe: ?[]const u8 = null;37 var opt_zig_exe: ?[]const u8 = null;
44 var opt_input_file_name: ?[]const u8 = null;38 var opt_input_file_name: ?[]const u8 = null;
...@@ -52,7 +46,7 @@ pub fn main() !void {...@@ -52,7 +46,7 @@ pub fn main() !void {
5246
53 var debug_log_args: std.ArrayList([]const u8) = .empty;47 var debug_log_args: std.ArrayList([]const u8) = .empty;
5448
55 var arg_it = try std.process.argsWithAllocator(arena);49 var arg_it = try init.minimal.args.iterateAllocator(arena);
56 _ = arg_it.skip();50 _ = arg_it.skip();
57 while (arg_it.next()) |arg| {51 while (arg_it.next()) |arg| {
58 if (arg.len > 0 and arg[0] == '-') {52 if (arg.len > 0 and arg[0] == '-') {
...@@ -119,9 +113,9 @@ pub fn main() !void {...@@ -119,9 +113,9 @@ pub fn main() !void {
119 }113 }
120114
121 // Convert paths to be relative to the cwd of the subprocess.115 // Convert paths to be relative to the cwd of the subprocess.
122 const resolved_zig_exe = try Dir.path.relative(arena, tmp_dir_path, zig_exe);116 const resolved_zig_exe = try Dir.path.relative(arena, cwd_path, environ_map, tmp_dir_path, zig_exe);
123 const opt_resolved_lib_dir = if (opt_lib_dir) |lib_dir|117 const opt_resolved_lib_dir = if (opt_lib_dir) |lib_dir|
124 try Dir.path.relative(arena, tmp_dir_path, lib_dir)118 try Dir.path.relative(arena, cwd_path, environ_map, tmp_dir_path, lib_dir)
125 else119 else
126 null;120 null;
127121
...@@ -179,18 +173,10 @@ pub fn main() !void {...@@ -179,18 +173,10 @@ pub fn main() !void {
179 const zig_prog_node = target_prog_node.start("zig build-exe", 0);173 const zig_prog_node = target_prog_node.start("zig build-exe", 0);
180 defer zig_prog_node.end();174 defer zig_prog_node.end();
181175
182 var child = std.process.Child.init(child_args.items, arena);
183 child.stdin_behavior = .Pipe;
184 child.stdout_behavior = .Pipe;
185 child.stderr_behavior = .Pipe;
186 child.progress_node = zig_prog_node;
187 child.cwd_dir = tmp_dir;
188 child.cwd = tmp_dir_path;
189
190 var cc_child_args: std.ArrayList([]const u8) = .empty;176 var cc_child_args: std.ArrayList([]const u8) = .empty;
191 if (target.backend == .cbe) {177 if (target.backend == .cbe) {
192 const resolved_cc_zig_exe = if (opt_cc_zig) |cc_zig_exe|178 const resolved_cc_zig_exe = if (opt_cc_zig) |cc_zig_exe|
193 try Dir.path.relative(arena, tmp_dir_path, cc_zig_exe)179 try Dir.path.relative(arena, cwd_path, environ_map, tmp_dir_path, cc_zig_exe)
194 else180 else
195 resolved_zig_exe;181 resolved_zig_exe;
196182
...@@ -209,6 +195,17 @@ pub fn main() !void {...@@ -209,6 +195,17 @@ pub fn main() !void {
209 try cc_child_args.append(arena, "-o");195 try cc_child_args.append(arena, "-o");
210 }196 }
211197
198 var child = try std.process.spawn(io, .{
199 .argv = child_args.items,
200 .stdin = .pipe,
201 .stdout = .pipe,
202 .stderr = .pipe,
203 .progress_node = zig_prog_node,
204 .cwd_dir = tmp_dir,
205 .cwd = tmp_dir_path,
206 });
207 defer child.kill(io);
208
212 var eval: Eval = .{209 var eval: Eval = .{
213 .arena = arena,210 .arena = arena,
214 .io = io,211 .io = io,
...@@ -227,11 +224,6 @@ pub fn main() !void {...@@ -227,11 +224,6 @@ pub fn main() !void {
227 .enable_darling = enable_darling,224 .enable_darling = enable_darling,
228 };225 };
229226
230 try child.spawn(io);
231 errdefer {
232 _ = child.kill(io) catch {};
233 }
234
235 var poller = Io.poll(arena, Eval.StreamEnum, .{227 var poller = Io.poll(arena, Eval.StreamEnum, .{
236 .stdout = child.stdout.?,228 .stdout = child.stdout.?,
237 .stderr = child.stderr.?,229 .stderr = child.stderr.?,
...@@ -536,7 +528,7 @@ const Eval = struct {...@@ -536,7 +528,7 @@ const Eval = struct {
536 const run_prog_node = prog_node.start("run generated executable", 0);528 const run_prog_node = prog_node.start("run generated executable", 0);
537 defer run_prog_node.end();529 defer run_prog_node.end();
538530
539 const result = std.process.Child.run(eval.arena, io, .{531 const result = std.process.run(eval.arena, io, .{
540 .argv = argv,532 .argv = argv,
541 .cwd_dir = eval.tmp_dir,533 .cwd_dir = eval.tmp_dir,
542 .cwd = eval.tmp_dir_path,534 .cwd = eval.tmp_dir_path,
...@@ -564,7 +556,7 @@ const Eval = struct {...@@ -564,7 +556,7 @@ const Eval = struct {
564 }556 }
565557
566 switch (result.term) {558 switch (result.term) {
567 .Exited => |code| switch (update.outcome) {559 .exited => |code| switch (update.outcome) {
568 .unknown, .compile_errors => unreachable,560 .unknown, .compile_errors => unreachable,
569 .stdout => |expected_stdout| {561 .stdout => |expected_stdout| {
570 if (code != 0) {562 if (code != 0) {
...@@ -572,9 +564,12 @@ const Eval = struct {...@@ -572,9 +564,12 @@ const Eval = struct {
572 }564 }
573 try std.testing.expectEqualStrings(expected_stdout, result.stdout);565 try std.testing.expectEqualStrings(expected_stdout, result.stdout);
574 },566 },
575 .exit_code => |expected_code| try std.testing.expectEqual(expected_code, result.term.Exited),567 .exit_code => |expected_code| try std.testing.expectEqual(expected_code, code),
568 },
569 .signal => |sig| {
570 eval.fatal("generated executable '{s}' terminated with signal {t}", .{ binary_path, sig });
576 },571 },
577 .Signal, .Stopped, .Unknown => {572 .stopped, .unknown => {
578 eval.fatal("generated executable '{s}' terminated unexpectedly", .{binary_path});573 eval.fatal("generated executable '{s}' terminated unexpectedly", .{binary_path});
579 },574 },
580 }575 }
...@@ -622,7 +617,7 @@ const Eval = struct {...@@ -622,7 +617,7 @@ const Eval = struct {
622 try eval.cc_child_args.appendSlice(eval.arena, &.{ out_path, c_path });617 try eval.cc_child_args.appendSlice(eval.arena, &.{ out_path, c_path });
623 defer eval.cc_child_args.items.len -= 2;618 defer eval.cc_child_args.items.len -= 2;
624619
625 const result = std.process.Child.run(eval.arena, eval.io, .{620 const result = std.process.run(eval.arena, eval.io, .{
626 .argv = eval.cc_child_args.items,621 .argv = eval.cc_child_args.items,
627 .cwd_dir = eval.tmp_dir,622 .cwd_dir = eval.tmp_dir,
628 .cwd = eval.tmp_dir_path,623 .cwd = eval.tmp_dir_path,
...@@ -631,13 +626,13 @@ const Eval = struct {...@@ -631,13 +626,13 @@ const Eval = struct {
631 eval.fatal("failed to spawn zig cc for '{s}': {t}", .{ c_path, err });626 eval.fatal("failed to spawn zig cc for '{s}': {t}", .{ c_path, err });
632 };627 };
633 switch (result.term) {628 switch (result.term) {
634 .Exited => |code| if (code != 0) {629 .exited => |code| if (code != 0) {
635 if (result.stderr.len != 0) {630 if (result.stderr.len != 0) {
636 std.log.err("zig cc stderr:\n{s}", .{result.stderr});631 std.log.err("zig cc stderr:\n{s}", .{result.stderr});
637 }632 }
638 eval.fatal("zig cc for '{s}' failed with code {d}", .{ c_path, code });633 eval.fatal("zig cc for '{s}' failed with code {d}", .{ c_path, code });
639 },634 },
640 .Signal, .Stopped, .Unknown => {635 .signal, .stopped, .unknown => {
641 if (result.stderr.len != 0) {636 if (result.stderr.len != 0) {
642 std.log.err("zig cc stderr:\n{s}", .{result.stderr});637 std.log.err("zig cc stderr:\n{s}", .{result.stderr});
643 }638 }
...@@ -651,7 +646,7 @@ const Eval = struct {...@@ -651,7 +646,7 @@ const Eval = struct {
651 eval.tmp_dir.close(io);646 eval.tmp_dir.close(io);
652 if (!eval.preserve_tmp_on_fatal) {647 if (!eval.preserve_tmp_on_fatal) {
653 // Kill the child since it holds an open handle to its CWD which is the tmp dir path648 // Kill the child since it holds an open handle to its CWD which is the tmp dir path
654 _ = eval.child.kill(io) catch {};649 eval.child.kill(io);
655 Dir.cwd().deleteTree(io, eval.tmp_dir_path) catch |err| {650 Dir.cwd().deleteTree(io, eval.tmp_dir_path) catch |err| {
656 std.log.warn("failed to delete tree '{s}': {t}", .{ eval.tmp_dir_path, err });651 std.log.warn("failed to delete tree '{s}': {t}", .{ eval.tmp_dir_path, err });
657 };652 };
...@@ -917,8 +912,9 @@ fn waitChild(child: *std.process.Child, eval: *Eval) void {...@@ -917,8 +912,9 @@ fn waitChild(child: *std.process.Child, eval: *Eval) void {
917 requestExit(child, eval);912 requestExit(child, eval);
918 const term = child.wait(io) catch |err| eval.fatal("child process failed: {t}", .{err});913 const term = child.wait(io) catch |err| eval.fatal("child process failed: {t}", .{err});
919 switch (term) {914 switch (term) {
920 .Exited => |code| if (code != 0) eval.fatal("compiler failed with code {d}", .{code}),915 .exited => |code| if (code != 0) eval.fatal("compiler failed with code {d}", .{code}),
921 .Signal, .Stopped, .Unknown => eval.fatal("compiler terminated unexpectedly", .{}),916 .signal => |sig| eval.fatal("compiler terminated with signal {t}", .{sig}),
917 .stopped, .unknown => eval.fatal("compiler terminated unexpectedly", .{}),
922 }918 }
923}919}
924920
tools/migrate_langref.zig+4-11
...@@ -11,21 +11,14 @@ const fatal = std.process.fatal;...@@ -11,21 +11,14 @@ const fatal = std.process.fatal;
1111
12const max_doc_file_size = 10 * 1024 * 1024;12const max_doc_file_size = 10 * 1024 * 1024;
1313
14pub fn main() !void {14pub fn main(init: std.process.Init) !void {
15 var arena_instance = std.heap.ArenaAllocator.init(std.heap.page_allocator);15 const arena = init.arena.allocator();
16 defer arena_instance.deinit();16 const io = init.io;
17 const arena = arena_instance.allocator();17 const args = try init.minimal.args.toSlice(arena);
1818
19 const gpa = arena;
20
21 const args = try std.process.argsAlloc(arena);
22 const input_file = args[1];19 const input_file = args[1];
23 const output_file = args[2];20 const output_file = args[2];
2421
25 var threaded: std.Io.Threaded = .init(gpa, .{});
26 defer threaded.deinit();
27 const io = threaded.io();
28
29 var in_file = try Dir.cwd().openFile(io, input_file, .{ .mode = .read_only });22 var in_file = try Dir.cwd().openFile(io, input_file, .{ .mode = .read_only });
30 defer in_file.close(io);23 defer in_file.close(io);
3124
tools/process_headers.zig+24-26
...@@ -127,16 +127,14 @@ const LibCVendor = enum {...@@ -127,16 +127,14 @@ const LibCVendor = enum {
127 netbsd,127 netbsd,
128};128};
129129
130pub fn main() !void {130pub fn main(init: std.process.Init) !void {
131 var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);131 const arena = init.arena.allocator();
132 const allocator = arena.allocator();132 const io = init.io;
133133 const args = try init.minimal.args.toSlice(arena);
134 var threaded: Io.Threaded = .init(allocator, .{});134 const cwd_path = try std.process.getCwdAlloc(arena);
135 defer threaded.deinit();135 const environ_map = init.environ_map;
136 const io = threaded.io();136
137137 var search_paths = std.array_list.Managed([]const u8).init(arena);
138 const args = try std.process.argsAlloc(allocator);
139 var search_paths = std.array_list.Managed([]const u8).init(allocator);
140 var opt_out_dir: ?[]const u8 = null;138 var opt_out_dir: ?[]const u8 = null;
141 var opt_abi: ?[]const u8 = null;139 var opt_abi: ?[]const u8 = null;
142140
...@@ -172,7 +170,7 @@ pub fn main() !void {...@@ -172,7 +170,7 @@ pub fn main() !void {
172 usageAndExit(args[0]);170 usageAndExit(args[0]);
173 };171 };
174172
175 const generic_name = try std.fmt.allocPrint(allocator, "generic-{s}", .{abi_name});173 const generic_name = try std.fmt.allocPrint(arena, "generic-{s}", .{abi_name});
176 const libc_targets = switch (vendor) {174 const libc_targets = switch (vendor) {
177 .glibc => &glibc_targets,175 .glibc => &glibc_targets,
178 .musl => &musl_targets,176 .musl => &musl_targets,
...@@ -180,8 +178,8 @@ pub fn main() !void {...@@ -180,8 +178,8 @@ pub fn main() !void {
180 .netbsd => &netbsd_targets,178 .netbsd => &netbsd_targets,
181 };179 };
182180
183 var path_table = PathTable.init(allocator);181 var path_table = PathTable.init(arena);
184 var hash_to_contents = HashToContents.init(allocator);182 var hash_to_contents = HashToContents.init(arena);
185 var max_bytes_saved: usize = 0;183 var max_bytes_saved: usize = 0;
186 var total_bytes: usize = 0;184 var total_bytes: usize = 0;
187185
...@@ -189,7 +187,7 @@ pub fn main() !void {...@@ -189,7 +187,7 @@ pub fn main() !void {
189187
190 for (libc_targets) |libc_target| {188 for (libc_targets) |libc_target| {
191 const libc_dir = switch (vendor) {189 const libc_dir = switch (vendor) {
192 .glibc => try std.zig.target.glibcRuntimeTriple(allocator, libc_target.arch, .linux, libc_target.abi),190 .glibc => try std.zig.target.glibcRuntimeTriple(arena, libc_target.arch, .linux, libc_target.abi),
193 .musl => std.zig.target.muslArchName(libc_target.arch, libc_target.abi),191 .musl => std.zig.target.muslArchName(libc_target.arch, libc_target.abi),
194 .freebsd => switch (libc_target.arch) {192 .freebsd => switch (libc_target.arch) {
195 .arm => "armv7",193 .arm => "armv7",
...@@ -221,7 +219,7 @@ pub fn main() !void {...@@ -221,7 +219,7 @@ pub fn main() !void {
221 },219 },
222 };220 };
223221
224 const dest_target = if (libc_target.dest) |dest| dest else try std.fmt.allocPrint(allocator, "{s}-{s}-{s}", .{222 const dest_target = if (libc_target.dest) |dest| dest else try std.fmt.allocPrint(arena, "{s}-{s}-{s}", .{
225 @tagName(libc_target.arch),223 @tagName(libc_target.arch),
226 switch (vendor) {224 switch (vendor) {
227 .musl, .glibc => "linux",225 .musl, .glibc => "linux",
...@@ -239,8 +237,8 @@ pub fn main() !void {...@@ -239,8 +237,8 @@ pub fn main() !void {
239 => &[_][]const u8{ search_path, libc_dir, "usr", "include" },237 => &[_][]const u8{ search_path, libc_dir, "usr", "include" },
240 .musl => &[_][]const u8{ search_path, libc_dir, "usr", "local", "musl", "include" },238 .musl => &[_][]const u8{ search_path, libc_dir, "usr", "local", "musl", "include" },
241 };239 };
242 const target_include_dir = try Dir.path.join(allocator, sub_path);240 const target_include_dir = try Dir.path.join(arena, sub_path);
243 var dir_stack = std.array_list.Managed([]const u8).init(allocator);241 var dir_stack = std.array_list.Managed([]const u8).init(arena);
244 try dir_stack.append(target_include_dir);242 try dir_stack.append(target_include_dir);
245243
246 while (dir_stack.pop()) |full_dir_name| {244 while (dir_stack.pop()) |full_dir_name| {
...@@ -254,16 +252,16 @@ pub fn main() !void {...@@ -254,16 +252,16 @@ pub fn main() !void {
254 var dir_it = dir.iterate();252 var dir_it = dir.iterate();
255253
256 while (try dir_it.next(io)) |entry| {254 while (try dir_it.next(io)) |entry| {
257 const full_path = try Dir.path.join(allocator, &[_][]const u8{ full_dir_name, entry.name });255 const full_path = try Dir.path.join(arena, &[_][]const u8{ full_dir_name, entry.name });
258 switch (entry.kind) {256 switch (entry.kind) {
259 .directory => try dir_stack.append(full_path),257 .directory => try dir_stack.append(full_path),
260 .file, .sym_link => {258 .file, .sym_link => {
261 const rel_path = try Dir.path.relative(allocator, target_include_dir, full_path);259 const rel_path = try Dir.path.relative(arena, cwd_path, environ_map, target_include_dir, full_path);
262 const max_size = 2 * 1024 * 1024 * 1024;260 const max_size = 2 * 1024 * 1024 * 1024;
263 const raw_bytes = try Dir.cwd().readFileAlloc(io, full_path, allocator, .limited(max_size));261 const raw_bytes = try Dir.cwd().readFileAlloc(io, full_path, arena, .limited(max_size));
264 const trimmed = std.mem.trim(u8, raw_bytes, " \r\n\t");262 const trimmed = std.mem.trim(u8, raw_bytes, " \r\n\t");
265 total_bytes += raw_bytes.len;263 total_bytes += raw_bytes.len;
266 const hash = try allocator.alloc(u8, 32);264 const hash = try arena.alloc(u8, 32);
267 hasher = Blake3.init(.{});265 hasher = Blake3.init(.{});
268 hasher.update(rel_path);266 hasher.update(rel_path);
269 hasher.update(trimmed);267 hasher.update(trimmed);
...@@ -285,8 +283,8 @@ pub fn main() !void {...@@ -285,8 +283,8 @@ pub fn main() !void {
285 }283 }
286 const path_gop = try path_table.getOrPut(rel_path);284 const path_gop = try path_table.getOrPut(rel_path);
287 const target_to_hash = if (path_gop.found_existing) path_gop.value_ptr.* else blk: {285 const target_to_hash = if (path_gop.found_existing) path_gop.value_ptr.* else blk: {
288 const ptr = try allocator.create(TargetToHash);286 const ptr = try arena.create(TargetToHash);
289 ptr.* = TargetToHash.init(allocator);287 ptr.* = TargetToHash.init(arena);
290 path_gop.value_ptr.* = ptr;288 path_gop.value_ptr.* = ptr;
291 break :blk ptr;289 break :blk ptr;
292 };290 };
...@@ -327,7 +325,7 @@ pub fn main() !void {...@@ -327,7 +325,7 @@ pub fn main() !void {
327 // gets their header in a separate arch directory.325 // gets their header in a separate arch directory.
328 var path_it = path_table.iterator();326 var path_it = path_table.iterator();
329 while (path_it.next()) |path_kv| {327 while (path_it.next()) |path_kv| {
330 var contents_list = std.array_list.Managed(*Contents).init(allocator);328 var contents_list = std.array_list.Managed(*Contents).init(arena);
331 {329 {
332 var hash_it = path_kv.value_ptr.*.iterator();330 var hash_it = path_kv.value_ptr.*.iterator();
333 while (hash_it.next()) |hash_kv| {331 while (hash_it.next()) |hash_kv| {
...@@ -339,7 +337,7 @@ pub fn main() !void {...@@ -339,7 +337,7 @@ pub fn main() !void {
339 const best_contents = contents_list.pop().?;337 const best_contents = contents_list.pop().?;
340 if (best_contents.hit_count > 1) {338 if (best_contents.hit_count > 1) {
341 // worth it to make it generic339 // worth it to make it generic
342 const full_path = try Dir.path.join(allocator, &[_][]const u8{ out_dir, generic_name, path_kv.key_ptr.* });340 const full_path = try Dir.path.join(arena, &[_][]const u8{ out_dir, generic_name, path_kv.key_ptr.* });
343 try Dir.cwd().createDirPath(io, Dir.path.dirname(full_path).?);341 try Dir.cwd().createDirPath(io, Dir.path.dirname(full_path).?);
344 try Dir.cwd().writeFile(io, .{ .sub_path = full_path, .data = best_contents.bytes });342 try Dir.cwd().writeFile(io, .{ .sub_path = full_path, .data = best_contents.bytes });
345 best_contents.is_generic = true;343 best_contents.is_generic = true;
...@@ -360,7 +358,7 @@ pub fn main() !void {...@@ -360,7 +358,7 @@ pub fn main() !void {
360 if (contents.is_generic) continue;358 if (contents.is_generic) continue;
361359
362 const dest_target = hash_kv.key_ptr.*;360 const dest_target = hash_kv.key_ptr.*;
363 const full_path = try Dir.path.join(allocator, &[_][]const u8{ out_dir, dest_target, path_kv.key_ptr.* });361 const full_path = try Dir.path.join(arena, &[_][]const u8{ out_dir, dest_target, path_kv.key_ptr.* });
364 try Dir.cwd().createDirPath(io, Dir.path.dirname(full_path).?);362 try Dir.cwd().createDirPath(io, Dir.path.dirname(full_path).?);
365 try Dir.cwd().writeFile(io, .{ .sub_path = full_path, .data = contents.bytes });363 try Dir.cwd().writeFile(io, .{ .sub_path = full_path, .data = contents.bytes });
366 }364 }
tools/update-linux-headers.zig+7-9
...@@ -141,15 +141,13 @@ const HashToContents = std.StringHashMap(Contents);...@@ -141,15 +141,13 @@ const HashToContents = std.StringHashMap(Contents);
141const TargetToHash = std.ArrayHashMap(DestTarget, []const u8, DestTarget.HashContext, true);141const TargetToHash = std.ArrayHashMap(DestTarget, []const u8, DestTarget.HashContext, true);
142const PathTable = std.StringHashMap(*TargetToHash);142const PathTable = std.StringHashMap(*TargetToHash);
143143
144pub fn main() !void {144pub fn main(init: std.process.Init) !void {
145 var arena_state = std.heap.ArenaAllocator.init(std.heap.page_allocator);145 const arena = init.arena.allocator();
146 const arena = arena_state.allocator();146 const io = init.io;
147 const args = try init.minimal.args.toSlice(arena);
148 const environ_map = init.environ_map;
149 const cwd = try std.process.getCwdAlloc(arena);
147150
148 var threaded: Io.Threaded = .init(arena, .{});
149 defer threaded.deinit();
150 const io = threaded.io();
151
152 const args = try std.process.argsAlloc(arena);
153 var search_paths = std.array_list.Managed([]const u8).init(arena);151 var search_paths = std.array_list.Managed([]const u8).init(arena);
154 var opt_out_dir: ?[]const u8 = null;152 var opt_out_dir: ?[]const u8 = null;
155153
...@@ -211,7 +209,7 @@ pub fn main() !void {...@@ -211,7 +209,7 @@ pub fn main() !void {
211 switch (entry.kind) {209 switch (entry.kind) {
212 .directory => try dir_stack.append(full_path),210 .directory => try dir_stack.append(full_path),
213 .file => {211 .file => {
214 const rel_path = try Dir.path.relative(arena, target_include_dir, full_path);212 const rel_path = try Dir.path.relative(arena, cwd, environ_map, target_include_dir, full_path);
215 const max_size = 2 * 1024 * 1024 * 1024;213 const max_size = 2 * 1024 * 1024 * 1024;
216 const raw_bytes = try Dir.cwd().readFileAlloc(io, full_path, arena, .limited(max_size));214 const raw_bytes = try Dir.cwd().readFileAlloc(io, full_path, arena, .limited(max_size));
217 const trimmed = std.mem.trim(u8, raw_bytes, " \r\n\t");215 const trimmed = std.mem.trim(u8, raw_bytes, " \r\n\t");
tools/update_clang_options.zig+12-18
...@@ -627,16 +627,10 @@ const cpu_targets = struct {...@@ -627,16 +627,10 @@ const cpu_targets = struct {
627 pub const xtensa = std.Target.xtensa;627 pub const xtensa = std.Target.xtensa;
628};628};
629629
630pub fn main() anyerror!void {630pub fn main(init: std.process.Init) !void {
631 var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);631 const arena = init.arena.allocator();
632 defer arena.deinit();632 const args = try init.minimal.args.toSlice(arena);
633633 const io = init.io;
634 const allocator = arena.allocator();
635 const args = try std.process.argsAlloc(allocator);
636
637 var threaded: std.Io.Threaded = .init(allocator, .{});
638 defer threaded.deinit();
639 const io = threaded.io();
640634
641 var stdout_buffer: [4000]u8 = undefined;635 var stdout_buffer: [4000]u8 = undefined;
642 var stdout_writer = Io.File.stdout().writerStreaming(io, &stdout_buffer);636 var stdout_writer = Io.File.stdout().writerStreaming(io, &stdout_buffer);
...@@ -658,7 +652,7 @@ pub fn main() anyerror!void {...@@ -658,7 +652,7 @@ pub fn main() anyerror!void {
658 const llvm_src_root = args[2];652 const llvm_src_root = args[2];
659 if (std.mem.startsWith(u8, llvm_src_root, "-")) printUsageAndExit(args[0]);653 if (std.mem.startsWith(u8, llvm_src_root, "-")) printUsageAndExit(args[0]);
660654
661 var llvm_to_zig_cpu_features = std.StringHashMap([]const u8).init(allocator);655 var llvm_to_zig_cpu_features = std.StringHashMap([]const u8).init(arena);
662656
663 inline for (@typeInfo(cpu_targets).@"struct".decls) |decl| {657 inline for (@typeInfo(cpu_targets).@"struct".decls) |decl| {
664 const Feature = @field(cpu_targets, decl.name).Feature;658 const Feature = @field(cpu_targets, decl.name).Feature;
...@@ -675,12 +669,12 @@ pub fn main() anyerror!void {...@@ -675,12 +669,12 @@ pub fn main() anyerror!void {
675 const child_args = [_][]const u8{669 const child_args = [_][]const u8{
676 llvm_tblgen_exe,670 llvm_tblgen_exe,
677 "--dump-json",671 "--dump-json",
678 try std.fmt.allocPrint(allocator, "{s}/clang/include/clang/Driver/Options.td", .{llvm_src_root}),672 try std.fmt.allocPrint(arena, "{s}/clang/include/clang/Driver/Options.td", .{llvm_src_root}),
679 try std.fmt.allocPrint(allocator, "-I={s}/llvm/include", .{llvm_src_root}),673 try std.fmt.allocPrint(arena, "-I={s}/llvm/include", .{llvm_src_root}),
680 try std.fmt.allocPrint(allocator, "-I={s}/clang/include/clang/Driver", .{llvm_src_root}),674 try std.fmt.allocPrint(arena, "-I={s}/clang/include/clang/Driver", .{llvm_src_root}),
681 };675 };
682676
683 const child_result = try std.process.Child.run(allocator, io, .{677 const child_result = try std.process.run(arena, io, .{
684 .argv = &child_args,678 .argv = &child_args,
685 .max_output_bytes = 100 * 1024 * 1024,679 .max_output_bytes = 100 * 1024 * 1024,
686 });680 });
...@@ -688,7 +682,7 @@ pub fn main() anyerror!void {...@@ -688,7 +682,7 @@ pub fn main() anyerror!void {
688 std.debug.print("{s}\n", .{child_result.stderr});682 std.debug.print("{s}\n", .{child_result.stderr});
689683
690 const json_text = switch (child_result.term) {684 const json_text = switch (child_result.term) {
691 .Exited => |code| if (code == 0) child_result.stdout else {685 .exited => |code| if (code == 0) child_result.stdout else {
692 std.debug.print("llvm-tblgen exited with code {d}\n", .{code});686 std.debug.print("llvm-tblgen exited with code {d}\n", .{code});
693 std.process.exit(1);687 std.process.exit(1);
694 },688 },
...@@ -698,11 +692,11 @@ pub fn main() anyerror!void {...@@ -698,11 +692,11 @@ pub fn main() anyerror!void {
698 },692 },
699 };693 };
700694
701 const parsed = try json.parseFromSlice(json.Value, allocator, json_text, .{});695 const parsed = try json.parseFromSlice(json.Value, arena, json_text, .{});
702 defer parsed.deinit();696 defer parsed.deinit();
703 const root_map = &parsed.value.object;697 const root_map = &parsed.value.object;
704698
705 var all_objects = std.array_list.Managed(*json.ObjectMap).init(allocator);699 var all_objects = std.array_list.Managed(*json.ObjectMap).init(arena);
706 {700 {
707 var it = root_map.iterator();701 var it = root_map.iterator();
708 it_map: while (it.next()) |kv| {702 it_map: while (it.next()) |kv| {
tools/update_cpu_features.zig+6-15
...@@ -1883,20 +1883,11 @@ const targets = [_]ArchTarget{...@@ -1883,20 +1883,11 @@ const targets = [_]ArchTarget{
1883 },1883 },
1884};1884};
18851885
1886pub fn main() anyerror!void {1886pub fn main(init: std.process.Init) !void {
1887 var debug_allocator: std.heap.DebugAllocator(.{}) = .init;1887 const arena = init.arena.allocator();
1888 defer _ = debug_allocator.deinit();1888 const io = init.io;
1889 const gpa = debug_allocator.allocator();
18901889
1891 var arena_state: std.heap.ArenaAllocator = .init(gpa);1890 var args = try init.minimal.args.iterateAllocator(arena);
1892 defer arena_state.deinit();
1893 const arena = arena_state.allocator();
1894
1895 var threaded: std.Io.Threaded = .init(gpa, .{});
1896 defer threaded.deinit();
1897 const io = threaded.io();
1898
1899 var args = try std.process.argsWithAllocator(arena);
1900 const args0 = args.next().?;1891 const args0 = args.next().?;
19011892
1902 const llvm_tblgen_exe = args.next() orelse1893 const llvm_tblgen_exe = args.next() orelse
...@@ -1994,7 +1985,7 @@ fn processOneTarget(io: Io, job: Job) void {...@@ -1994,7 +1985,7 @@ fn processOneTarget(io: Io, job: Job) void {
1994 }),1985 }),
1995 };1986 };
19961987
1997 const child_result = try std.process.Child.run(arena, io, .{1988 const child_result = try std.process.run(arena, io, .{
1998 .argv = &child_args,1989 .argv = &child_args,
1999 .max_output_bytes = 500 * 1024 * 1024,1990 .max_output_bytes = 500 * 1024 * 1024,
2000 });1991 });
...@@ -2004,7 +1995,7 @@ fn processOneTarget(io: Io, job: Job) void {...@@ -2004,7 +1995,7 @@ fn processOneTarget(io: Io, job: Job) void {
2004 }1995 }
20051996
2006 const json_text = switch (child_result.term) {1997 const json_text = switch (child_result.term) {
2007 .Exited => |code| if (code == 0) child_result.stdout else {1998 .exited => |code| if (code == 0) child_result.stdout else {
2008 std.debug.print("llvm-tblgen exited with code {d}\n", .{code});1999 std.debug.print("llvm-tblgen exited with code {d}\n", .{code});
2009 std.process.exit(1);2000 std.process.exit(1);
2010 },2001 },
tools/update_crc_catalog.zig+7-9
...@@ -6,16 +6,14 @@ const ascii = std.ascii;...@@ -6,16 +6,14 @@ const ascii = std.ascii;
66
7const catalog_txt = @embedFile("crc/catalog.txt");7const catalog_txt = @embedFile("crc/catalog.txt");
88
9pub fn main() anyerror!void {9pub fn main(init: std.process.Init) !void {
10 var arena_state = std.heap.ArenaAllocator.init(std.heap.page_allocator);10 const arena = init.arena.allocator();
11 defer arena_state.deinit();11 const io = init.io;
12 const arena = arena_state.allocator();12 const args = try init.minimal.args.toSlice(arena);
1313 return @"i like cheese"(arena, io, args);
14 var threaded: Io.Threaded = .init(arena, .{});14}
15 defer threaded.deinit();
16 const io = threaded.io();
1715
18 const args = try std.process.argsAlloc(arena);16fn @"i like cheese"(arena: std.mem.Allocator, io: Io, args: []const []const u8) !void {
19 if (args.len <= 1) printUsageAndExit(args[0]);17 if (args.len <= 1) printUsageAndExit(args[0]);
2018
21 const zig_src_root = args[1];19 const zig_src_root = args[1];
tools/update_freebsd_libc.zig+4-9
...@@ -12,16 +12,11 @@ const exempt_files = [_][]const u8{...@@ -12,16 +12,11 @@ const exempt_files = [_][]const u8{
12 "abilists",12 "abilists",
13};13};
1414
15pub fn main() !void {15pub fn main(init: std.process.Init) !void {
16 var arena_instance = std.heap.ArenaAllocator.init(std.heap.page_allocator);16 const arena = init.arena.allocator();
17 defer arena_instance.deinit();17 const io = init.io;
18 const arena = arena_instance.allocator();18 const args = try init.minimal.args.toSlice(arena);
1919
20 var threaded: Io.Threaded = .init(arena, .{});
21 defer threaded.deinit();
22 const io = threaded.io();
23
24 const args = try std.process.argsAlloc(arena);
25 const freebsd_src_path = args[1];20 const freebsd_src_path = args[1];
26 const zig_src_path = args[2];21 const zig_src_path = args[2];
2722
tools/update_glibc.zig+4-9
...@@ -38,16 +38,11 @@ const exempt_extensions = [_][]const u8{...@@ -38,16 +38,11 @@ const exempt_extensions = [_][]const u8{
38 "-2.33.c",38 "-2.33.c",
39};39};
4040
41pub fn main() !void {41pub fn main(init: std.process.Init) !void {
42 var arena_instance = std.heap.ArenaAllocator.init(std.heap.page_allocator);42 const arena = init.arena.allocator();
43 defer arena_instance.deinit();43 const io = init.io;
44 const arena = arena_instance.allocator();44 const args = try init.minimal.args.toSlice(arena);
4545
46 var threaded: Io.Threaded = .init(arena, .{});
47 defer threaded.deinit();
48 const io = threaded.io();
49
50 const args = try std.process.argsAlloc(arena);
51 const glibc_src_path = args[1];46 const glibc_src_path = args[1];
52 const zig_src_path = args[2];47 const zig_src_path = args[2];
5348
tools/update_mingw.zig+4-9
...@@ -2,16 +2,11 @@ const std = @import("std");...@@ -2,16 +2,11 @@ const std = @import("std");
2const Io = std.Io;2const Io = std.Io;
3const Dir = std.Io.Dir;3const Dir = std.Io.Dir;
44
5pub fn main() !void {5pub fn main(init: std.process.Init) !void {
6 var arena_instance = std.heap.ArenaAllocator.init(std.heap.page_allocator);6 const arena = init.arena.allocator();
7 defer arena_instance.deinit();7 const io = init.io;
8 const arena = arena_instance.allocator();8 const args = try init.minimal.args.toSlice(arena);
99
10 var threaded: Io.Threaded = .init(arena, .{});
11 defer threaded.deinit();
12 const io = threaded.io();
13
14 const args = try std.process.argsAlloc(arena);
15 const zig_src_lib_path = args[1];10 const zig_src_lib_path = args[1];
16 const mingw_src_path = args[2];11 const mingw_src_path = args[2];
1712
tools/update_netbsd_libc.zig+4-9
...@@ -12,16 +12,11 @@ const exempt_files = [_][]const u8{...@@ -12,16 +12,11 @@ const exempt_files = [_][]const u8{
12 "abilists",12 "abilists",
13};13};
1414
15pub fn main() !void {15pub fn main(init: std.process.Init) !void {
16 var arena_instance = std.heap.ArenaAllocator.init(std.heap.page_allocator);16 const arena = init.arena.allocator();
17 defer arena_instance.deinit();17 const io = init.io;
18 const arena = arena_instance.allocator();18 const args = try init.minimal.args.toSlice(arena);
1919
20 var threaded: Io.Threaded = .init(arena, .{});
21 defer threaded.deinit();
22 const io = threaded.io();
23
24 const args = try std.process.argsAlloc(arena);
25 const netbsd_src_path = args[1];20 const netbsd_src_path = args[1];
26 const zig_src_path = args[2];21 const zig_src_path = args[2];
2722