authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-12-28 20:46:02-08:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-01-04 00:27:07-08:00
logd6a1e73142396732be80a7e0757cca1c07551d30
tree3b97b816f0bfa9eb1c88c6d202c304b286727dd4
parent86a9a9048e28efc8b49d42db12db123822c3450b

std: start wrangling environment variables and process args

this commit is unfinished. It marks a spot where I wanted to start moving child process stuff below the std.Io.VTable

29 files changed, 2093 insertions(+), 2104 deletions(-)

lib/compiler/build_runner.zig+7-7
...@@ -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,7 +37,7 @@ pub fn main() !void {...@@ -37,7 +37,7 @@ 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 defer threaded.deinit();43 defer threaded.deinit();
...@@ -83,7 +83,7 @@ pub fn main() !void {...@@ -83,7 +83,7 @@ pub fn main() !void {
83 .manifest_dir = try local_cache_directory.handle.createDirPathOpen(io, "h", .{}),83 .manifest_dir = try local_cache_directory.handle.createDirPathOpen(io, "h", .{}),
84 },84 },
85 .zig_exe = zig_exe,85 .zig_exe = zig_exe,
86 .env_map = try process.getEnvMap(arena),86 .env_map = try init.environ.createMap(arena),
87 .global_cache_root = global_cache_directory,87 .global_cache_root = global_cache_directory,
88 .zig_lib_directory = zig_lib_directory,88 .zig_lib_directory = zig_lib_directory,
89 .host = .{89 .host = .{
...@@ -126,13 +126,13 @@ pub fn main() !void {...@@ -126,13 +126,13 @@ pub fn main() !void {
126 var debounce_interval_ms: u16 = 50;126 var debounce_interval_ms: u16 = 50;
127 var webui_listen: ?Io.net.IpAddress = null;127 var webui_listen: ?Io.net.IpAddress = null;
128128
129 if (try std.zig.EnvVar.ZIG_BUILD_ERROR_STYLE.get(arena)) |str| {129 if (std.zig.EnvVar.ZIG_BUILD_ERROR_STYLE.get(&graph.env_map)) |str| {
130 if (std.meta.stringToEnum(ErrorStyle, str)) |style| {130 if (std.meta.stringToEnum(ErrorStyle, str)) |style| {
131 error_style = style;131 error_style = style;
132 }132 }
133 }133 }
134134
135 if (try std.zig.EnvVar.ZIG_BUILD_MULTILINE_ERRORS.get(arena)) |str| {135 if (std.zig.EnvVar.ZIG_BUILD_MULTILINE_ERRORS.get(&graph.env_map)) |str| {
136 if (std.meta.stringToEnum(MultilineErrors, str)) |style| {136 if (std.meta.stringToEnum(MultilineErrors, str)) |style| {
137 multiline_errors = style;137 multiline_errors = style;
138 }138 }
...@@ -429,8 +429,8 @@ pub fn main() !void {...@@ -429,8 +429,8 @@ pub fn main() !void {
429 }429 }
430 }430 }
431431
432 const NO_COLOR = std.zig.EnvVar.NO_COLOR.isSet();432 const NO_COLOR = std.zig.EnvVar.NO_COLOR.isSet(&graph.env_map);
433 const CLICOLOR_FORCE = std.zig.EnvVar.CLICOLOR_FORCE.isSet();433 const CLICOLOR_FORCE = std.zig.EnvVar.CLICOLOR_FORCE.isSet(&graph.env_map);
434434
435 graph.stderr_mode = switch (color) {435 graph.stderr_mode = switch (color) {
436 .auto => try .detect(io, .stderr(), NO_COLOR, CLICOLOR_FORCE),436 .auto => try .detect(io, .stderr(), NO_COLOR, CLICOLOR_FORCE),
lib/std/Build.zig+6-9
...@@ -12,7 +12,7 @@ const StringHashMap = std.StringHashMap;...@@ -12,7 +12,7 @@ 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;15const EnvMap = std.process.Environ.Map;
16const File = std.Io.File;16const File = std.Io.File;
17const Sha256 = std.crypto.hash.sha2.Sha256;17const Sha256 = std.crypto.hash.sha2.Sha256;
18const ArrayList = std.ArrayList;18const ArrayList = std.ArrayList;
...@@ -1840,13 +1840,12 @@ pub fn runAllowFail(...@@ -1840,13 +1840,12 @@ pub fn runAllowFail(
1840 const io = b.graph.io;1840 const io = b.graph.io;
18411841
1842 const max_output_size = 400 * 1024;1842 const max_output_size = 400 * 1024;
1843 var child = std.process.Child.init(argv, b.allocator);1843 var child = std.process.Child.init(b.allocator, argv, .{ .map = &b.graph.env_map });
1844 child.stdin_behavior = .Ignore;1844 child.stdin_behavior = .Ignore;
1845 child.stdout_behavior = .Pipe;1845 child.stdout_behavior = .Pipe;
1846 child.stderr_behavior = stderr_behavior;1846 child.stderr_behavior = stderr_behavior;
1847 child.env_map = &b.graph.env_map;
18481847
1849 try Step.handleVerbose2(b, null, child.env_map, argv);1848 try Step.handleVerbose2(b, null, child.environ.map, argv);
1850 try child.spawn(io);1849 try child.spawn(io);
18511850
1852 var stdout_reader = child.stdout.?.readerStreaming(io, &.{});1851 var stdout_reader = child.stdout.?.readerStreaming(io, &.{});
...@@ -1877,17 +1876,15 @@ pub fn runAllowFail(...@@ -1877,17 +1876,15 @@ pub fn runAllowFail(
1877pub fn run(b: *Build, argv: []const []const u8) []u8 {1876pub fn run(b: *Build, argv: []const []const u8) []u8 {
1878 if (!process.can_spawn) {1877 if (!process.can_spawn) {
1879 std.debug.print("unable to spawn the following command: cannot spawn child process\n{s}\n", .{1878 std.debug.print("unable to spawn the following command: cannot spawn child process\n{s}\n", .{
1880 try Step.allocPrintCmd(b.allocator, null, argv),1879 try Step.allocPrintCmd(b.allocator, null, null, argv),
1881 });1880 });
1882 process.exit(1);1881 process.exit(1);
1883 }1882 }
18841883
1885 var code: u8 = undefined;1884 var code: u8 = undefined;
1886 return b.runAllowFail(argv, &code, .Inherit) catch |err| {1885 return b.runAllowFail(argv, &code, .Inherit) catch |err| {
1887 const printed_cmd = Step.allocPrintCmd(b.allocator, null, argv) catch @panic("OOM");1886 const printed_cmd = Step.allocPrintCmd(b.allocator, null, null, argv) catch @panic("OOM");
1888 std.debug.print("unable to spawn the following command: {s}\n{s}\n", .{1887 std.debug.print("unable to spawn the following command: {t}\n{s}\n", .{ err, printed_cmd });
1889 @errorName(err), printed_cmd,
1890 });
1891 process.exit(1);1888 process.exit(1);
1892 };1889 };
1893}1890}
lib/std/Build/Step.zig+19-21
...@@ -349,18 +349,20 @@ pub fn captureChildProcess(...@@ -349,18 +349,20 @@ pub fn captureChildProcess(
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.Child.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.Child.run(arena, io, .{
363 .argv = argv,364 .argv = argv,
365 .environ = .{ .map = &graph.env_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);
...@@ -447,8 +449,7 @@ pub fn evalZigProcess(...@@ -447,8 +449,7 @@ pub fn evalZigProcess(
447 try handleChildProcUnsupported(s);449 try handleChildProcUnsupported(s);
448 try handleVerbose(s.owner, null, argv);450 try handleVerbose(s.owner, null, argv);
449451
450 var child = std.process.Child.init(argv, arena);452 var child = std.process.Child.init(arena, argv, .{ .map = &b.graph.env_map });
451 child.env_map = &b.graph.env_map;
452 child.stdin_behavior = .Pipe;453 child.stdin_behavior = .Pipe;
453 child.stdout_behavior = .Pipe;454 child.stdout_behavior = .Pipe;
454 child.stderr_behavior = .Pipe;455 child.stderr_behavior = .Pipe;
...@@ -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.env_map,
706 } else null, argv);
702 std.debug.print("{s}\n", .{text});707 std.debug.print("{s}\n", .{text});
703 }708 }
704}709}
...@@ -728,15 +733,10 @@ pub fn handleChildProcessTerm(s: *Step, term: std.process.Child.Term) error{ Mak...@@ -728,15 +733,10 @@ pub fn handleChildProcessTerm(s: *Step, term: std.process.Child.Term) error{ Mak
728pub fn allocPrintCmd(733pub fn allocPrintCmd(
729 gpa: Allocator,734 gpa: Allocator,
730 opt_cwd: ?[]const u8,735 opt_cwd: ?[]const u8,
731 argv: []const []const u8,736 opt_env: ?struct {
732) Allocator.Error![]u8 {737 child: *const std.process.Environ.Map,
733 return allocPrintCmd2(gpa, opt_cwd, null, argv);738 parent: *const std.process.Environ.Map,
734}739 },
735
736pub fn allocPrintCmd2(
737 gpa: Allocator,
738 opt_cwd: ?[]const u8,
739 opt_env: ?*const std.process.EnvMap,
740 argv: []const []const u8,740 argv: []const []const u8,
741) Allocator.Error![]u8 {741) Allocator.Error![]u8 {
742 const shell = struct {742 const shell = struct {
...@@ -779,13 +779,11 @@ pub fn allocPrintCmd2(...@@ -779,13 +779,11 @@ pub fn allocPrintCmd2(
779 const writer = &aw.writer;779 const writer = &aw.writer;
780 if (opt_cwd) |cwd| writer.print("cd {s} && ", .{cwd}) catch return error.OutOfMemory;780 if (opt_cwd) |cwd| writer.print("cd {s} && ", .{cwd}) catch return error.OutOfMemory;
781 if (opt_env) |env| {781 if (opt_env) |env| {
782 var process_env_map = std.process.getEnvMap(gpa) catch std.process.EnvMap.init(gpa);782 var it = env.child.iterator();
783 defer process_env_map.deinit();
784 var it = env.iterator();
785 while (it.next()) |entry| {783 while (it.next()) |entry| {
786 const key = entry.key_ptr.*;784 const key = entry.key_ptr.*;
787 const value = entry.value_ptr.*;785 const value = entry.value_ptr.*;
788 if (process_env_map.get(key)) |process_value| {786 if (env.parent.get(key)) |process_value| {
789 if (std.mem.eql(u8, value, process_value)) continue;787 if (std.mem.eql(u8, value, process_value)) continue;
790 }788 }
791 writer.print("{s}=", .{key}) catch return error.OutOfMemory;789 writer.print("{s}=", .{key}) catch return error.OutOfMemory;
lib/std/Build/Step/Options.zig+1-1
...@@ -546,7 +546,7 @@ test Options {...@@ -546,7 +546,7 @@ test Options {
546 .manifest_dir = Io.Dir.cwd(),546 .manifest_dir = Io.Dir.cwd(),
547 },547 },
548 .zig_exe = "test",548 .zig_exe = "test",
549 .env_map = std.process.EnvMap.init(arena.allocator()),549 .env_map = std.process.Environ.Map.init(arena.allocator()),
550 .global_cache_root = .{ .path = "test", .handle = Io.Dir.cwd() },550 .global_cache_root = .{ .path = "test", .handle = Io.Dir.cwd() },
551 .host = .{551 .host = .{
552 .query = .{},552 .query = .{},
lib/std/Build/Step/Run.zig+17-14
...@@ -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
...@@ -581,23 +581,24 @@ pub fn getEnvMap(run: *Run) *EnvMap {...@@ -581,23 +581,24 @@ 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 const arena = graph.arena;
585 return run.env_map orelse {586 return run.env_map orelse {
586 const env_map = arena.create(EnvMap) catch @panic("OOM");587 const cloned_map = arena.create(EnvMap) catch @panic("OOM");
587 env_map.* = process.getEnvMap(arena) catch @panic("unhandled error");588 cloned_map.* = graph.env_map.clone(arena) catch @panic("OOM");
588 run.env_map = env_map;589 run.env_map = cloned_map;
589 return env_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 env_map = run.getEnvMap();595 const env_map = run.getEnvMap();
596 env_map.put(b.dupe(key), b.dupe(value)) catch @panic("unhandled error");596 // This data structure already dupes keys and values.
597 env_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.
...@@ -1563,11 +1564,10 @@ fn spawnChildAndCollect(...@@ -1563,11 +1564,10 @@ fn spawnChildAndCollect(
1563 assert(run.stdio == .zig_test);1564 assert(run.stdio == .zig_test);
1564 }1565 }
15651566
1566 var child = std.process.Child.init(argv, arena);1567 var child = std.process.Child.init(arena, argv, .{ .map = env_map });
1567 if (run.cwd) |lazy_cwd| {1568 if (run.cwd) |lazy_cwd| {
1568 child.cwd = lazy_cwd.getPath2(b, &run.step);1569 child.cwd = lazy_cwd.getPath2(b, &run.step);
1569 }1570 }
1570 child.env_map = env_map;
1571 child.request_resource_usage_statistics = true;1571 child.request_resource_usage_statistics = true;
15721572
1573 child.stdin_behavior = switch (run.stdio) {1573 child.stdin_behavior = switch (run.stdio) {
...@@ -1597,7 +1597,10 @@ fn spawnChildAndCollect(...@@ -1597,7 +1597,10 @@ fn spawnChildAndCollect(
15971597
1598 // If an error occurs, it's caused by this command:1598 // If an error occurs, it's caused by this command:
1599 assert(run.step.result_failed_command == null);1599 assert(run.step.result_failed_command == null);
1600 run.step.result_failed_command = try Step.allocPrintCmd(options.gpa, child.cwd, argv);1600 run.step.result_failed_command = try Step.allocPrintCmd(options.gpa, child.cwd, .{
1601 .child = env_map,
1602 .parent = &graph.env_map,
1603 }, argv);
16011604
1602 if (run.stdio == .zig_test) {1605 if (run.stdio == .zig_test) {
1603 var timer = try std.time.Timer.start();1606 var timer = try std.time.Timer.start();
...@@ -1627,11 +1630,11 @@ fn setColorEnvironmentVariables(run: *Run, env_map: *EnvMap, terminal_mode: Io.T...@@ -1627,11 +1630,11 @@ fn setColorEnvironmentVariables(run: *Run, env_map: *EnvMap, terminal_mode: Io.T
1627 .manual => {},1630 .manual => {},
1628 .enable => {1631 .enable => {
1629 try env_map.put("CLICOLOR_FORCE", "1");1632 try env_map.put("CLICOLOR_FORCE", "1");
1630 env_map.remove("NO_COLOR");1633 _ = env_map.swapRemove("NO_COLOR");
1631 },1634 },
1632 .disable => {1635 .disable => {
1633 try env_map.put("NO_COLOR", "1");1636 try env_map.put("NO_COLOR", "1");
1634 env_map.remove("CLICOLOR_FORCE");1637 _ = env_map.swapRemove("CLICOLOR_FORCE");
1635 },1638 },
1636 .inherit => switch (terminal_mode) {1639 .inherit => switch (terminal_mode) {
1637 .no_color, .windows_api => continue :color .disable,1640 .no_color, .windows_api => continue :color .disable,
lib/std/Build/WebServer.zig+6-6
...@@ -528,13 +528,13 @@ pub fn serveTarFile(ws: *WebServer, request: *http.Server.Request, paths: []cons...@@ -528,13 +528,13 @@ pub fn serveTarFile(ws: *WebServer, request: *http.Server.Request, paths: []cons
528}528}
529529
530fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.OptimizeMode) !Cache.Path {530fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.OptimizeMode) !Cache.Path {
531 const io = ws.graph.io;
532 const root_name = "build-web";531 const root_name = "build-web";
533 const arch_os_abi = "wasm32-freestanding";532 const arch_os_abi = "wasm32-freestanding";
534 const cpu_features = "baseline+atomics+bulk_memory+multivalue+mutable_globals+nontrapping_fptoint+reference_types+sign_ext";533 const cpu_features = "baseline+atomics+bulk_memory+multivalue+mutable_globals+nontrapping_fptoint+reference_types+sign_ext";
535534
536 const gpa = ws.gpa;535 const gpa = ws.gpa;
537 const graph = ws.graph;536 const graph = ws.graph;
537 const io = graph.io;
538538
539 const main_src_path: Cache.Path = .{539 const main_src_path: Cache.Path = .{
540 .root_dir = graph.zig_lib_directory,540 .root_dir = graph.zig_lib_directory,
...@@ -572,7 +572,7 @@ fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.Optim...@@ -572,7 +572,7 @@ fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.Optim
572 "--listen=-",572 "--listen=-",
573 });573 });
574574
575 var child: std.process.Child = .init(argv.items, gpa);575 var child: std.process.Child = .init(gpa, argv.items, .{ .map = &graph.env_map });
576 child.stdin_behavior = .Pipe;576 child.stdin_behavior = .Pipe;
577 child.stdout_behavior = .Pipe;577 child.stdout_behavior = .Pipe;
578 child.stderr_behavior = .Pipe;578 child.stderr_behavior = .Pipe;
...@@ -640,7 +640,7 @@ fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.Optim...@@ -640,7 +640,7 @@ fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.Optim
640 if (code != 0) {640 if (code != 0) {
641 log.err(641 log.err(
642 "the following command exited with error code {d}:\n{s}",642 "the following command exited with error code {d}:\n{s}",
643 .{ code, try Build.Step.allocPrintCmd(arena, null, argv.items) },643 .{ code, try Build.Step.allocPrintCmd(arena, null, null, argv.items) },
644 );644 );
645 return error.WasmCompilationFailed;645 return error.WasmCompilationFailed;
646 }646 }
...@@ -648,7 +648,7 @@ fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.Optim...@@ -648,7 +648,7 @@ fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.Optim
648 .Signal, .Stopped, .Unknown => {648 .Signal, .Stopped, .Unknown => {
649 log.err(649 log.err(
650 "the following command terminated unexpectedly:\n{s}",650 "the following command terminated unexpectedly:\n{s}",
651 .{try Build.Step.allocPrintCmd(arena, null, argv.items)},651 .{try Build.Step.allocPrintCmd(arena, null, null, argv.items)},
652 );652 );
653 return error.WasmCompilationFailed;653 return error.WasmCompilationFailed;
654 },654 },
...@@ -658,14 +658,14 @@ fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.Optim...@@ -658,14 +658,14 @@ fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.Optim
658 try result_error_bundle.renderToStderr(io, .{}, .auto);658 try result_error_bundle.renderToStderr(io, .{}, .auto);
659 log.err("the following command failed with {d} compilation errors:\n{s}", .{659 log.err("the following command failed with {d} compilation errors:\n{s}", .{
660 result_error_bundle.errorMessageCount(),660 result_error_bundle.errorMessageCount(),
661 try Build.Step.allocPrintCmd(arena, null, argv.items),661 try Build.Step.allocPrintCmd(arena, null, null, argv.items),
662 });662 });
663 return error.WasmCompilationFailed;663 return error.WasmCompilationFailed;
664 }664 }
665665
666 const base_path = result orelse {666 const base_path = result orelse {
667 log.err("child process failed to report result\n{s}", .{667 log.err("child process failed to report result\n{s}", .{
668 try Build.Step.allocPrintCmd(arena, null, argv.items),668 try Build.Step.allocPrintCmd(arena, null, null, argv.items),
669 });669 });
670 return error.WasmCompilationFailed;670 return error.WasmCompilationFailed;
671 };671 };
lib/std/Io.zig+6
...@@ -2232,3 +2232,9 @@ pub fn tryLockStderr(io: Io, buffer: []u8, terminal_mode: ?Terminal.Mode) Cancel...@@ -2232,3 +2232,9 @@ pub fn tryLockStderr(io: Io, buffer: []u8, terminal_mode: ?Terminal.Mode) Cancel
2232pub fn unlockStderr(io: Io) void {2232pub fn unlockStderr(io: Io) void {
2233 return io.vtable.unlockStderr(io.userdata);2233 return io.vtable.unlockStderr(io.userdata);
2234}2234}
2235
2236pub fn environ(io: Io, name: []const u8) ?[]const u8 {
2237 _ = io;
2238 _ = name;
2239 if (true) @panic("TODO");
2240}
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+1-3
...@@ -72,7 +72,7 @@ pub const Argv0 = switch (native_os) {...@@ -72,7 +72,7 @@ pub const Argv0 = switch (native_os) {
7272
73pub const Environ = struct {73pub const Environ = struct {
74 /// Unmodified data directly from the OS.74 /// Unmodified data directly from the OS.
75 block: Block = &.{},75 block: std.process.Environ.Block = &.{},
76 /// Protected by `mutex`. Determines whether the other fields have been76 /// Protected by `mutex`. Determines whether the other fields have been
77 /// memoized based on `block`.77 /// memoized based on `block`.
78 initialized: bool = false,78 initialized: bool = false,
...@@ -89,8 +89,6 @@ pub const Environ = struct {...@@ -89,8 +89,6 @@ pub const Environ = struct {
8989
90 pub const Error = Allocator.Error || Io.UnexpectedError;90 pub const Error = Allocator.Error || Io.UnexpectedError;
9191
92 pub const Block = []const [*:0]const u8;
93
94 pub const Exist = struct {92 pub const Exist = struct {
95 NO_COLOR: bool = false,93 NO_COLOR: bool = false,
96 CLICOLOR_FORCE: bool = false,94 CLICOLOR_FORCE: bool = false,
lib/std/Progress.zig+1-1
...@@ -476,7 +476,7 @@ pub fn start(io: Io, options: Options) Node {...@@ -476,7 +476,7 @@ pub fn start(io: Io, options: Options) Node {
476476
477 global_progress.io = io;477 global_progress.io = io;
478478
479 if (std.process.parseEnvVarInt("ZIG_PROGRESS", u31, 10)) |ipc_fd| {479 if (std.process.Environ.parseInt(io, "ZIG_PROGRESS", u31, 10)) |ipc_fd| {
480 global_progress.update_worker = io.concurrent(ipcThreadRun, .{480 global_progress.update_worker = io.concurrent(ipcThreadRun, .{
481 io,481 io,
482 @as(Io.File, .{ .handle = switch (@typeInfo(Io.File.Handle)) {482 @as(Io.File, .{ .handle = switch (@typeInfo(Io.File.Handle)) {
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+4-4
...@@ -66,16 +66,16 @@ pub const DebugInfoSearchPaths = struct {...@@ -66,16 +66,16 @@ pub const DebugInfoSearchPaths = struct {
66 .exe_dir = null,66 .exe_dir = null,
67 };67 };
6868
69 pub fn native(exe_path: []const u8) DebugInfoSearchPaths {69 pub fn native(exe_path: []const u8, io: Io) DebugInfoSearchPaths {
70 return .{70 return .{
71 .debuginfod_client = p: {71 .debuginfod_client = p: {
72 if (std.posix.getenv("DEBUGINFOD_CACHE_PATH")) |p| {72 if (io.environ("DEBUGINFOD_CACHE_PATH")) |p| {
73 break :p .{ p, "" };73 break :p .{ p, "" };
74 }74 }
75 if (std.posix.getenv("XDG_CACHE_HOME")) |cache_path| {75 if (io.environ("XDG_CACHE_HOME")) |cache_path| {
76 break :p .{ cache_path, "/debuginfod_client" };76 break :p .{ cache_path, "/debuginfod_client" };
77 }77 }
78 if (std.posix.getenv("HOME")) |home_path| {78 if (io.environ("HOME")) |home_path| {
79 break :p .{ home_path, "/.cache/debuginfod_client" };79 break :p .{ home_path, "/.cache/debuginfod_client" };
80 }80 }
81 break :p null;81 break :p null;
lib/std/debug/SelfInfo/Elf.zig+3-2
...@@ -322,11 +322,12 @@ const Module = struct {...@@ -322,11 +322,12 @@ 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;
328 defer file.close(io);329 defer file.close(io);
329 break :res std.debug.ElfFile.load(gpa, io, file, mod.build_id, &.native(mod.name));330 break :res std.debug.ElfFile.load(gpa, io, file, mod.build_id, &.native(mod.name, io));
330 } else res: {331 } else res: {
331 const path = std.process.executablePathAlloc(io, gpa) catch |err| switch (err) {332 const path = std.process.executablePathAlloc(io, gpa) catch |err| switch (err) {
332 error.OutOfMemory => |e| return e,333 error.OutOfMemory => |e| return e,
...@@ -335,7 +336,7 @@ const Module = struct {...@@ -335,7 +336,7 @@ const Module = struct {
335 defer gpa.free(path);336 defer gpa.free(path);
336 var file = Io.Dir.cwd().openFile(io, path, .{}) catch return error.MissingDebugInfo;337 var file = Io.Dir.cwd().openFile(io, path, .{}) catch return error.MissingDebugInfo;
337 defer file.close(io);338 defer file.close(io);
338 break :res std.debug.ElfFile.load(gpa, io, file, mod.build_id, &.native(path));339 break :res std.debug.ElfFile.load(gpa, io, file, mod.build_id, &.native(path, io));
339 };340 };
340341
341 var elf_file = load_result catch |err| switch (err) {342 var elf_file = load_result catch |err| switch (err) {
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/posix.zig+3-115
...@@ -857,127 +857,15 @@ pub fn execveZ(...@@ -857,127 +857,15 @@ pub fn execveZ(
857 }857 }
858}858}
859859
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.860/// 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`.861/// If `file` is an absolute path, this is the same as `execveZ`.
920pub fn execvpeZ(862pub fn execvpeZ(
921 file: [*:0]const u8,863 file: [*:0]const u8,
922 argv_ptr: [*:null]const ?[*:0]const u8,864 argv_ptr: [*:null]const ?[*:0]const u8,
923 envp: [*:null]const ?[*:0]const u8,865 envp: [*:null]const ?[*:0]const u8,
866 optional_PATH: ?[]const u8,
924) ExecveError {867) ExecveError {
925 return execvpeZ_expandArg0(.no_expand, file, argv_ptr, envp);868 return execvpeZ_expandArg0(.no_expand, file, argv_ptr, envp, optional_PATH);
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}869}
982870
983pub const GetCwdError = error{871pub const GetCwdError = error{
...@@ -1945,7 +1833,7 @@ pub const FStatError = std.Io.File.StatError;...@@ -1945,7 +1833,7 @@ pub const FStatError = std.Io.File.StatError;
1945/// Return information about a file descriptor.1833/// Return information about a file descriptor.
1946pub fn fstat(fd: fd_t) FStatError!Stat {1834pub fn fstat(fd: fd_t) FStatError!Stat {
1947 if (native_os == .wasi and !builtin.link_libc) {1835 if (native_os == .wasi and !builtin.link_libc) {
1948 return Stat.fromFilestat(try std.os.fstat_wasi(fd));1836 @compileError("unsupported OS");
1949 }1837 }
19501838
1951 var stat = mem.zeroes(Stat);1839 var stat = mem.zeroes(Stat);
lib/std/process.zig+154-1740
...@@ -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,39 @@ pub const GetCwdError = posix.GetCwdError;...@@ -28,6 +26,39 @@ 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. Threadsafe.
44 gpa: Allocator,
45 /// An appropriate default Io implementation based on the target
46 /// configuration. Debug mode will set up leak checking.
47 io: Io,
48 /// Environment variables, initialized with `gpa`. Not threadsafe.
49 env_map: *Environ.Map,
50
51 /// Alternative to `Init` as the first parameter of the main function.
52 pub const Minimal = struct {
53 /// Environment variables.
54 environ: Environ,
55 /// Command line arguments.
56 args: Args,
57 };
58};
59
60pub const GetCwdError = posix.GetCwdError;
61
31/// The result is a slice of `out_buffer`, from index `0`.62/// 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/).63/// 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.64/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
...@@ -73,1484 +104,6 @@ test getCwdAlloc {...@@ -73,1484 +104,6 @@ test getCwdAlloc {
73 testing.allocator.free(cwd);104 testing.allocator.free(cwd);
74}105}
75106
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 {107pub const UserInfo = struct {
1555 uid: posix.uid_t,108 uid: posix.uid_t,
1556 gid: posix.gid_t,109 gid: posix.gid_t,
...@@ -1706,73 +259,143 @@ pub fn getBaseAddress() usize {...@@ -1706,73 +259,143 @@ pub fn getBaseAddress() usize {
1706 }259 }
1707}260}
1708261
1709/// Tells whether calling the `execv` or `execve` functions will be a compile error.262/// Deprecated in favor of `Child.can_spawn`.
1710pub const can_execv = switch (native_os) {263pub const can_spawn = Child.can_spawn;
264/// Deprecated in favor of `can_replace`.
265pub const can_execv = can_replace;
266
267/// Tells whether the target operating system supports replacing the current
268/// process image. If this is `false` then calling `execv` or `replace`
269/// functions will cause compilation to fail.
270pub const can_replace = switch (native_os) {
1711 .windows, .haiku, .wasi => false,271 .windows, .haiku, .wasi => false,
1712 else => true,272 else => true,
1713};273};
1714274
1715/// Tells whether spawning child processes is supported (e.g. via Child)275pub const ReplaceError = std.posix.ExecveError || error{OutOfMemory};
1716pub const can_spawn = switch (native_os) {
1717 .wasi, .ios, .tvos, .visionos, .watchos => false,
1718 else => true,
1719};
1720276
1721pub const ExecvError = std.posix.ExecveError || error{OutOfMemory};277/// Replaces the current process image with the executed process. If this
278/// function succeeds, it does not return.
279///
280/// `argv[0]` is the name of the process to replace the current one with. If it
281/// is not already a file path (i.e. it contains '/'), it is resolved into a
282/// file path based on PATH from the parent environment.
283///
284/// This operation is not available on targets for which `can_replace` is
285/// `false`.
286///
287/// This function must allocate memory to add a null terminating bytes on path
288/// and each arg.
289///
290/// Due to the heap allocation, it is illegal to call this function in a fork()
291/// child.
292pub fn replace(io: Io, gpa: Allocator, argv: []const []const u8, env: Environ.Block) ReplaceError {
293 if (!can_replace) @compileError("unsupported operation: replace");
1722294
1723/// Replaces the current process image with the executed process.295 var arena_allocator = std.heap.ArenaAllocator.init(gpa);
1724/// This function must allocate memory to add a null terminating bytes on path and each arg.296 defer arena_allocator.deinit();
1725/// It must also convert to KEY=VALUE\0 format for environment variables, and include null297 const arena = arena_allocator.allocator();
1726/// pointers after the args and after the environment variables.298
1727/// `argv[0]` is the executable path.299 const argv_buf = try arena.allocSentinel(?[*:0]const u8, argv.len, null);
1728/// This function also uses the PATH environment variable to get the full path to the executable.300 for (argv, 0..) |arg, i| argv_buf[i] = (try arena.dupeZ(u8, arg)).ptr;
1729/// Due to the heap-allocation, it is illegal to call this function in a fork() child.301
1730/// For that use case, use the `std.posix` functions directly.302 return posix.execvpeZ_expandArg0(.no_expand, argv_buf.ptr[0].?, argv_buf.ptr, env);
1731pub fn execv(allocator: Allocator, argv: []const []const u8) ExecvError {
1732 return execve(allocator, argv, null);
1733}303}
1734304
1735/// Replaces the current process image with the executed process.305/// 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.306/// function succeeds, it does not return.
1737/// It must also convert to KEY=VALUE\0 format for environment variables, and include null307///
1738/// pointers after the args and after the environment variables.308/// `argv[0]` is the file path of the process to replace the current one with,
1739/// `argv[0]` is the executable path.309/// 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.310/// not contain '/'.
1741/// Due to the heap-allocation, it is illegal to call this function in a fork() child.311///
1742/// For that use case, use the `std.posix` functions directly.312/// This operation is not available on targets for which `can_replace` is
1743pub fn execve(313/// `false`.
1744 allocator: Allocator,314///
1745 argv: []const []const u8,315/// This function must allocate memory to add a null terminating bytes on path
1746 env_map: ?*const EnvMap,316/// and each arg.
1747) ExecvError {317///
1748 if (!can_execv) @compileError("The target OS does not support execv");318/// Due to the heap allocation, it is illegal to call this
1749319/// function in a fork() child. For that use case, use the `std.posix`
1750 var arena_allocator = std.heap.ArenaAllocator.init(allocator);320/// functions directly.
321pub fn replaceFile(io: Io, gpa: Allocator, argv: []const []const u8, env: Environ.Block) ReplaceError {
322 if (!can_replace) @compileError("unsupported operation: replaceFile");
323
324 var arena_allocator = std.heap.ArenaAllocator.init(gpa);
1751 defer arena_allocator.deinit();325 defer arena_allocator.deinit();
1752 const arena = arena_allocator.allocator();326 const arena = arena_allocator.allocator();
1753327
1754 const argv_buf = try arena.allocSentinel(?[*:0]const u8, argv.len, null);328 const argv_buf = try arena.allocSentinel(?[*:0]const u8, argv.len, null);
1755 for (argv, 0..) |arg, i| argv_buf[i] = (try arena.dupeZ(u8, arg)).ptr;329 for (argv, 0..) |arg, i| argv_buf[i] = (try arena.dupeZ(u8, arg)).ptr;
1756330
1757 const envp = m: {331 return posix.execvpeZ_expandArg0(.no_expand, argv_buf.ptr[0].?, argv_buf.ptr, env);
1758 if (env_map) |m| {332}
1759 const envp_buf = try createNullDelimitedEnvMap(arena, m);333
1760 break :m envp_buf.ptr;334pub const Arg0Expand = enum { expand, no_expand };
1761 } else if (builtin.link_libc) {335
1762 break :m std.c.environ;336/// Replaces the current process image with the executed process. If this
1763 } else if (builtin.output_mode == .Exe) {337/// function succeeds, it does not return.
1764 // Then we have Zig start code and this works.338///
1765 // TODO type-safety for null-termination of `os.environ`.339/// This operation is not available on all targets. `can_execv`
1766 break :m @as([*:null]const ?[*:0]const u8, @ptrCast(std.os.environ.ptr));340///
1767 } else {341/// This function also uses the PATH environment variable to get the full path to the executable.
1768 // TODO come up with a solution for this.342/// If `file` is an absolute path, this is the same as `execveZ`.
1769 @compileError("missing std lib enhancement: std.process.execv implementation has no way to collect the environment variables to forward to the child process");343///
1770 }344/// Like `execvpeZ` except if `arg0_expand` is `.expand`, then `argv` is mutable,
345/// and `argv[0]` is expanded to be the same absolute path that is passed to the execve syscall.
346/// If this function returns with an error, `argv[0]` will be restored to the value it was when it was passed in.
347pub fn replace(
348 comptime arg0_expand: Arg0Expand,
349 file: [*:0]const u8,
350 child_argv: switch (arg0_expand) {
351 .expand => [*:null]?[*:0]const u8,
352 .no_expand => [*:null]const ?[*:0]const u8,
353 },
354 envp: [*:null]const ?[*:0]const u8,
355 optional_PATH: ?[]const u8,
356) ExecveError {
357 const file_slice = mem.sliceTo(file, 0);
358 if (mem.findScalar(u8, file_slice, '/') != null) return execveZ(file, child_argv, envp);
359
360 const PATH = optional_PATH orelse "/usr/local/bin:/bin/:/usr/bin";
361 // Use of PATH_MAX here is valid as the path_buf will be passed
362 // directly to the operating system in execveZ.
363 var path_buf: [PATH_MAX]u8 = undefined;
364 var it = mem.tokenizeScalar(u8, PATH, ':');
365 var seen_eacces = false;
366 var err: ExecveError = error.FileNotFound;
367
368 // In case of expanding arg0 we must put it back if we return with an error.
369 const prev_arg0 = child_argv[0];
370 defer switch (arg0_expand) {
371 .expand => child_argv[0] = prev_arg0,
372 .no_expand => {},
1771 };373 };
1772374
1773 return posix.execvpeZ_expandArg0(.no_expand, argv_buf.ptr[0].?, argv_buf.ptr, envp);375 while (it.next()) |search_path| {
376 const path_len = search_path.len + file_slice.len + 1;
377 if (path_buf.len < path_len + 1) return error.NameTooLong;
378 @memcpy(path_buf[0..search_path.len], search_path);
379 path_buf[search_path.len] = '/';
380 @memcpy(path_buf[search_path.len + 1 ..][0..file_slice.len], file_slice);
381 path_buf[path_len] = 0;
382 const full_path = path_buf[0..path_len :0].ptr;
383 switch (arg0_expand) {
384 .expand => child_argv[0] = full_path,
385 .no_expand => {},
386 }
387 err = execveZ(full_path, child_argv, envp);
388 switch (err) {
389 error.AccessDenied => seen_eacces = true,
390 error.FileNotFound, error.NotDir => {},
391 else => |e| return e,
392 }
393 }
394 if (seen_eacces) return error.AccessDenied;
395 return err;
1774}396}
1775397
398
1776pub const TotalSystemMemoryError = error{399pub const TotalSystemMemoryError = error{
1777 UnknownTotalSystemMemory,400 UnknownTotalSystemMemory,
1778};401};
...@@ -1903,215 +526,6 @@ test raiseFileDescriptorLimit {...@@ -1903,215 +526,6 @@ test raiseFileDescriptorLimit {
1903 raiseFileDescriptorLimit();526 raiseFileDescriptorLimit();
1904}527}
1905528
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.529/// Logs an error and then terminates the process with exit code 1.
2116pub fn fatal(comptime format: []const u8, format_arguments: anytype) noreturn {530pub fn fatal(comptime format: []const u8, format_arguments: anytype) noreturn {
2117 std.log.err(format, format_arguments);531 std.log.err(format, format_arguments);
lib/std/process/Args.zig created+958
...@@ -0,0 +1,958 @@
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.debug.testing;
10
11vector: Vector,
12
13pub const Vector = switch (native_os) {
14 .windows => []const u16, // WTF-16 encoded
15 else => []const [*:0]const u8,
16};
17
18/// Cross-platform access to command line one argument at a time.
19pub const Iterator = struct {
20 const Inner = switch (native_os) {
21 .windows => Windows,
22 .wasi => if (builtin.link_libc) Posix else Wasi,
23 else => Posix,
24 };
25
26 inner: Inner,
27
28 /// Initialize the args iterator. Consider using `initAllocator` instead
29 /// for cross-platform compatibility.
30 pub fn init(a: Args) Iterator {
31 if (native_os == .wasi) {
32 @compileError("In WASI, use initAllocator instead.");
33 }
34 if (native_os == .windows) {
35 @compileError("In Windows, use initAllocator instead.");
36 }
37
38 return .{ .inner = .init(a) };
39 }
40
41 pub const InitError = Inner.InitError;
42
43 /// You must deinitialize iterator's internal buffers by calling `deinit` when done.
44 pub fn initAllocator(a: Args, gpa: Allocator) InitError!Iterator {
45 if (native_os == .wasi and !builtin.link_libc) {
46 return .{ .inner = try .init(a, gpa) };
47 }
48 if (native_os == .windows) {
49 return .{ .inner = try .init(a, gpa) };
50 }
51
52 return .{ .inner = .init(a) };
53 }
54
55 /// Return subsequent argument, or `null` if no more remaining.
56 ///
57 /// Returned slice is pointing to the iterator's internal buffer.
58 /// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/).
59 /// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
60 pub fn next(it: *Iterator) ?([:0]const u8) {
61 return it.inner.next();
62 }
63
64 /// Parse past 1 argument without capturing it.
65 /// Returns `true` if skipped an arg, `false` if we are at the end.
66 pub fn skip(it: *Iterator) bool {
67 return it.inner.skip();
68 }
69
70 /// Required to release resources if the iterator was initialized with
71 /// `initAllocator` function.
72 pub fn deinit(it: *Iterator) void {
73 // Unless we're targeting WASI or Windows, this is a no-op.
74 if (native_os == .wasi and !builtin.link_libc) it.inner.deinit();
75 if (native_os == .windows) it.inner.deinit();
76 }
77
78 /// Iterator that implements the Windows command-line parsing algorithm.
79 ///
80 /// The implementation is intended to be compatible with the post-2008 C runtime,
81 /// but is *not* intended to be compatible with `CommandLineToArgvW` since
82 /// `CommandLineToArgvW` uses the pre-2008 parsing rules.
83 ///
84 /// This iterator faithfully implements the parsing behavior observed from the C runtime with
85 /// one exception: if the command-line string is empty, the iterator will immediately complete
86 /// without returning any arguments (whereas the C runtime will return a single argument
87 /// representing the name of the current executable).
88 ///
89 /// The essential parts of the algorithm are described in Microsoft's documentation:
90 ///
91 /// - https://learn.microsoft.com/en-us/cpp/cpp/main-function-command-line-args?view=msvc-170#parsing-c-command-line-arguments
92 ///
93 /// David Deley explains some additional undocumented quirks in great detail:
94 ///
95 /// - https://daviddeley.com/autohotkey/parameters/parameters.htm#WINCRULES
96 pub const Windows = struct {
97 allocator: Allocator,
98 /// Encoded as WTF-16 LE.
99 cmd_line: []const u16,
100 index: usize = 0,
101 /// Owned by the iterator. Long enough to hold contiguous NUL-terminated slices
102 /// of each argument encoded as WTF-8.
103 buffer: []u8,
104 start: usize = 0,
105 end: usize = 0,
106
107 pub const InitError = error{OutOfMemory};
108
109 /// `cmd_line_w` *must* be a WTF16-LE-encoded string.
110 ///
111 /// The iterator stores and uses `cmd_line_w`, so its memory must be valid for
112 /// at least as long as the returned Windows.
113 pub fn init(allocator: Allocator, cmd_line_w: []const u16) Windows.InitError!Windows {
114 const wtf8_len = std.unicode.calcWtf8Len(cmd_line_w);
115
116 // This buffer must be large enough to contain contiguous NUL-terminated slices
117 // of each argument.
118 // - During parsing, the length of a parsed argument will always be equal to
119 // to less than its unparsed length
120 // - The first argument needs one extra byte of space allocated for its NUL
121 // terminator, but for each subsequent argument the necessary whitespace
122 // between arguments guarantees room for their NUL terminator(s).
123 const buffer = try allocator.alloc(u8, wtf8_len + 1);
124 errdefer allocator.free(buffer);
125
126 return .{
127 .allocator = allocator,
128 .cmd_line = cmd_line_w,
129 .buffer = buffer,
130 };
131 }
132
133 /// Returns the next argument and advances the iterator. Returns `null` if at the end of the
134 /// command-line string. The iterator owns the returned slice.
135 /// The result is encoded as [WTF-8](https://wtf-8.codeberg.page/).
136 pub fn next(self: *Windows) ?[:0]const u8 {
137 return self.nextWithStrategy(next_strategy);
138 }
139
140 /// Skips the next argument and advances the iterator. Returns `true` if an argument was
141 /// skipped, `false` if at the end of the command-line string.
142 pub fn skip(self: *Windows) bool {
143 return self.nextWithStrategy(skip_strategy);
144 }
145
146 const next_strategy = struct {
147 const T = ?[:0]const u8;
148
149 const eof = null;
150
151 /// Returns '\' if any backslashes are emitted, otherwise returns `last_emitted_code_unit`.
152 fn emitBackslashes(self: *Windows, count: usize, last_emitted_code_unit: ?u16) ?u16 {
153 for (0..count) |_| {
154 self.buffer[self.end] = '\\';
155 self.end += 1;
156 }
157 return if (count != 0) '\\' else last_emitted_code_unit;
158 }
159
160 /// If `last_emitted_code_unit` and `code_unit` form a surrogate pair, then
161 /// the previously emitted high surrogate is overwritten by the codepoint encoded
162 /// by the surrogate pair, and `null` is returned.
163 /// Otherwise, `code_unit` is emitted and returned.
164 fn emitCharacter(self: *Windows, code_unit: u16, last_emitted_code_unit: ?u16) ?u16 {
165 // Because we are emitting WTF-8, we need to
166 // check to see if we've emitted two consecutive surrogate
167 // codepoints that form a valid surrogate pair in order
168 // to ensure that we're always emitting well-formed WTF-8
169 // (https://wtf-8.codeberg.page/#concatenating).
170 //
171 // If we do have a valid surrogate pair, we need to emit
172 // the UTF-8 sequence for the codepoint that they encode
173 // instead of the WTF-8 encoding for the two surrogate pairs
174 // separately.
175 //
176 // This is relevant when dealing with a WTF-16 encoded
177 // command line like this:
178 // "<0xD801>"<0xDC37>
179 // which would get parsed and converted to WTF-8 as:
180 // <0xED><0xA0><0x81><0xED><0xB0><0xB7>
181 // but instead, we need to recognize the surrogate pair
182 // and emit the codepoint it encodes, which in this
183 // example is U+10437 (𐐷), which is encoded in UTF-8 as:
184 // <0xF0><0x90><0x90><0xB7>
185 if (last_emitted_code_unit != null and
186 std.unicode.utf16IsLowSurrogate(code_unit) and
187 std.unicode.utf16IsHighSurrogate(last_emitted_code_unit.?))
188 {
189 const codepoint = std.unicode.utf16DecodeSurrogatePair(&.{ last_emitted_code_unit.?, code_unit }) catch unreachable;
190
191 // Unpaired surrogate is 3 bytes long
192 const dest = self.buffer[self.end - 3 ..];
193 const len = std.unicode.utf8Encode(codepoint, dest) catch unreachable;
194 // All codepoints that require a surrogate pair (> U+FFFF) are encoded as 4 bytes
195 assert(len == 4);
196 self.end += 1;
197 return null;
198 }
199
200 const wtf8_len = std.unicode.wtf8Encode(code_unit, self.buffer[self.end..]) catch unreachable;
201 self.end += wtf8_len;
202 return code_unit;
203 }
204
205 fn yieldArg(self: *Windows) [:0]const u8 {
206 self.buffer[self.end] = 0;
207 const arg = self.buffer[self.start..self.end :0];
208 self.end += 1;
209 self.start = self.end;
210 return arg;
211 }
212 };
213
214 const skip_strategy = struct {
215 const T = bool;
216
217 const eof = false;
218
219 fn emitBackslashes(_: *Windows, _: usize, last_emitted_code_unit: ?u16) ?u16 {
220 return last_emitted_code_unit;
221 }
222
223 fn emitCharacter(_: *Windows, _: u16, last_emitted_code_unit: ?u16) ?u16 {
224 return last_emitted_code_unit;
225 }
226
227 fn yieldArg(_: *Windows) bool {
228 return true;
229 }
230 };
231
232 fn nextWithStrategy(self: *Windows, comptime strategy: type) strategy.T {
233 var last_emitted_code_unit: ?u16 = null;
234 // The first argument (the executable name) uses different parsing rules.
235 if (self.index == 0) {
236 if (self.cmd_line.len == 0 or self.cmd_line[0] == 0) {
237 // Immediately complete the iterator.
238 // The C runtime would return the name of the current executable here.
239 return strategy.eof;
240 }
241
242 var inside_quotes = false;
243 while (true) : (self.index += 1) {
244 const char = if (self.index != self.cmd_line.len)
245 std.mem.littleToNative(u16, self.cmd_line[self.index])
246 else
247 0;
248 switch (char) {
249 0 => {
250 return strategy.yieldArg(self);
251 },
252 '"' => {
253 inside_quotes = !inside_quotes;
254 },
255 ' ', '\t' => {
256 if (inside_quotes) {
257 last_emitted_code_unit = strategy.emitCharacter(self, char, last_emitted_code_unit);
258 } else {
259 self.index += 1;
260 return strategy.yieldArg(self);
261 }
262 },
263 else => {
264 last_emitted_code_unit = strategy.emitCharacter(self, char, last_emitted_code_unit);
265 },
266 }
267 }
268 }
269
270 // Skip spaces and tabs. The iterator completes if we reach the end of the string here.
271 while (true) : (self.index += 1) {
272 const char = if (self.index != self.cmd_line.len)
273 std.mem.littleToNative(u16, self.cmd_line[self.index])
274 else
275 0;
276 switch (char) {
277 0 => return strategy.eof,
278 ' ', '\t' => continue,
279 else => break,
280 }
281 }
282
283 // Parsing rules for subsequent arguments:
284 //
285 // - The end of the string always terminates the current argument.
286 // - When not in 'inside_quotes' mode, a space or tab terminates the current argument.
287 // - 2n backslashes followed by a quote emit n backslashes (note: n can be zero).
288 // If in 'inside_quotes' and the quote is immediately followed by a second quote,
289 // one quote is emitted and the other is skipped, otherwise, the quote is skipped
290 // and 'inside_quotes' is toggled.
291 // - 2n + 1 backslashes followed by a quote emit n backslashes followed by a quote.
292 // - n backslashes not followed by a quote emit n backslashes.
293 var backslash_count: usize = 0;
294 var inside_quotes = false;
295 while (true) : (self.index += 1) {
296 const char = if (self.index != self.cmd_line.len)
297 std.mem.littleToNative(u16, self.cmd_line[self.index])
298 else
299 0;
300 switch (char) {
301 0 => {
302 last_emitted_code_unit = strategy.emitBackslashes(self, backslash_count, last_emitted_code_unit);
303 return strategy.yieldArg(self);
304 },
305 ' ', '\t' => {
306 last_emitted_code_unit = strategy.emitBackslashes(self, backslash_count, last_emitted_code_unit);
307 backslash_count = 0;
308 if (inside_quotes) {
309 last_emitted_code_unit = strategy.emitCharacter(self, char, last_emitted_code_unit);
310 } else return strategy.yieldArg(self);
311 },
312 '"' => {
313 const char_is_escaped_quote = backslash_count % 2 != 0;
314 last_emitted_code_unit = strategy.emitBackslashes(self, backslash_count / 2, last_emitted_code_unit);
315 backslash_count = 0;
316 if (char_is_escaped_quote) {
317 last_emitted_code_unit = strategy.emitCharacter(self, '"', last_emitted_code_unit);
318 } else {
319 if (inside_quotes and
320 self.index + 1 != self.cmd_line.len and
321 std.mem.littleToNative(u16, self.cmd_line[self.index + 1]) == '"')
322 {
323 last_emitted_code_unit = strategy.emitCharacter(self, '"', last_emitted_code_unit);
324 self.index += 1;
325 } else {
326 inside_quotes = !inside_quotes;
327 }
328 }
329 },
330 '\\' => {
331 backslash_count += 1;
332 },
333 else => {
334 last_emitted_code_unit = strategy.emitBackslashes(self, backslash_count, last_emitted_code_unit);
335 backslash_count = 0;
336 last_emitted_code_unit = strategy.emitCharacter(self, char, last_emitted_code_unit);
337 },
338 }
339 }
340 }
341
342 /// Frees the iterator's copy of the command-line string and all previously returned
343 /// argument slices.
344 pub fn deinit(self: *Windows) void {
345 self.allocator.free(self.buffer);
346 }
347 };
348
349 pub const Posix = struct {
350 remaining: Vector,
351
352 pub const InitError = error{};
353
354 pub fn init(a: Args) Posix {
355 return .{ .remaining = a.vector };
356 }
357
358 pub fn next(it: *Posix) ?[:0]const u8 {
359 if (it.remaining.len == 0) return null;
360 const arg = it.remaining[0];
361 it.remaining = it.remaining[1..];
362 return std.mem.sliceTo(arg, 0);
363 }
364
365 pub fn skip(it: *Posix) bool {
366 if (it.remaining.len == 0) return false;
367 it.remaining = it.remaining[1..];
368 return true;
369 }
370 };
371
372 pub const Wasi = struct {
373 allocator: Allocator,
374 index: usize,
375 args: [][:0]u8,
376
377 pub const InitError = error{OutOfMemory} || std.posix.UnexpectedError;
378
379 /// You must call deinit to free the internal buffer of the
380 /// iterator after you are done.
381 pub fn init(allocator: Allocator) Wasi.InitError!Wasi {
382 const fetched_args = try Wasi.internalInit(allocator);
383 return Wasi{
384 .allocator = allocator,
385 .index = 0,
386 .args = fetched_args,
387 };
388 }
389
390 fn internalInit(allocator: Allocator) Wasi.InitError![][:0]u8 {
391 var count: usize = undefined;
392 var buf_size: usize = undefined;
393
394 switch (std.os.wasi.args_sizes_get(&count, &buf_size)) {
395 .SUCCESS => {},
396 else => |err| return std.posix.unexpectedErrno(err),
397 }
398
399 if (count == 0) {
400 return &[_][:0]u8{};
401 }
402
403 const argv = try allocator.alloc([*:0]u8, count);
404 defer allocator.free(argv);
405
406 const argv_buf = try allocator.alloc(u8, buf_size);
407
408 switch (std.os.wasi.args_get(argv.ptr, argv_buf.ptr)) {
409 .SUCCESS => {},
410 else => |err| return std.posix.unexpectedErrno(err),
411 }
412
413 var result_args = try allocator.alloc([:0]u8, count);
414 var i: usize = 0;
415 while (i < count) : (i += 1) {
416 result_args[i] = std.mem.sliceTo(argv[i], 0);
417 }
418
419 return result_args;
420 }
421
422 pub fn next(self: *Wasi) ?[:0]const u8 {
423 if (self.index == self.args.len) return null;
424
425 const arg = self.args[self.index];
426 self.index += 1;
427 return arg;
428 }
429
430 pub fn skip(self: *Wasi) bool {
431 if (self.index == self.args.len) return false;
432
433 self.index += 1;
434 return true;
435 }
436
437 /// Call to free the internal buffer of the iterator.
438 pub fn deinit(self: *Wasi) void {
439 // Nothing is allocated when there are no args
440 if (self.args.len == 0) return;
441
442 const last_item = self.args[self.args.len - 1];
443 const last_byte_addr = @intFromPtr(last_item.ptr) + last_item.len + 1; // null terminated
444 const first_item_ptr = self.args[0].ptr;
445 const len = last_byte_addr - @intFromPtr(first_item_ptr);
446 self.allocator.free(first_item_ptr[0..len]);
447 self.allocator.free(self.args);
448 }
449 };
450};
451
452/// Holds the command-line arguments, with the program name as the first entry.
453/// Use `iterateAllocator` for cross-platform code.
454pub fn iterate(a: Args) Iterator {
455 return .init(a);
456}
457
458/// You must deinitialize iterator's internal buffers by calling `deinit` when
459/// done.
460pub fn iterateAllocator(a: Args, gpa: Allocator) Iterator.InitError!Iterator {
461 return .initAllocator(a, gpa);
462}
463
464/// Returned value may reference several allocations; call `freeSlice` to
465/// release.
466///
467/// * On Windows, the result is encoded as
468/// [WTF-8](https://wtf-8.codeberg.page/).
469/// * On other platforms, the result is an opaque sequence of bytes with no
470/// particular encoding.
471pub fn toSlice(a: Args, gpa: Allocator) Allocator.Error![][:0]u8 {
472 var it = try a.iterateAllocator(gpa);
473 defer it.deinit();
474
475 var contents = std.array_list.Managed(u8).init(gpa);
476 defer contents.deinit();
477
478 var slice_list = std.array_list.Managed(usize).init(gpa);
479 defer slice_list.deinit();
480
481 while (it.next()) |arg| {
482 try contents.appendSlice(arg[0 .. arg.len + 1]);
483 try slice_list.append(arg.len);
484 }
485
486 const contents_slice = contents.items;
487 const slice_sizes = slice_list.items;
488 const slice_list_bytes = std.math.mul(usize, @sizeOf([]u8), slice_sizes.len) catch return error.OutOfMemory;
489 const total_bytes = std.math.add(usize, slice_list_bytes, contents_slice.len) catch return error.OutOfMemory;
490 const buf = try gpa.alignedAlloc(u8, .of([]u8), total_bytes);
491 errdefer gpa.free(buf);
492
493 const result_slice_list = std.mem.bytesAsSlice([:0]u8, buf[0..slice_list_bytes]);
494 const result_contents = buf[slice_list_bytes..];
495 @memcpy(result_contents[0..contents_slice.len], contents_slice);
496
497 var contents_index: usize = 0;
498 for (slice_sizes, 0..) |len, i| {
499 const new_index = contents_index + len;
500 result_slice_list[i] = result_contents[contents_index..new_index :0];
501 contents_index = new_index + 1;
502 }
503
504 return result_slice_list;
505}
506
507/// Frees memory allocate by `toSlice`.
508pub fn freeSlice(gpa: Allocator, to_slice_result: []const [:0]u8) void {
509 var total_bytes: usize = 0;
510 for (to_slice_result) |arg| {
511 total_bytes += @sizeOf([]u8) + arg.len + 1;
512 }
513 const unaligned_allocated_buf = @as([*]const u8, @ptrCast(to_slice_result.ptr))[0..total_bytes];
514 const aligned_allocated_buf: []align(@alignOf([]u8)) const u8 = @alignCast(unaligned_allocated_buf);
515 return gpa.free(aligned_allocated_buf);
516}
517
518test "Iterator.Windows" {
519 const t = testArgIteratorWindows;
520
521 try t(
522 \\"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\")"
523 , &.{
524 \\C:\Program Files\zig\zig.exe
525 ,
526 \\run
527 ,
528 \\.\src\main.zig
529 ,
530 \\-target
531 ,
532 \\x86_64-windows-gnu
533 ,
534 \\-O
535 ,
536 \\ReleaseSafe
537 ,
538 \\--
539 ,
540 \\--emoji=🗿
541 ,
542 \\--eval=new Regex("Dwayne \"The Rock\" Johnson")
543 ,
544 });
545
546 // Empty
547 try t("", &.{});
548
549 // Separators
550 try t("aa bb cc", &.{ "aa", "bb", "cc" });
551 try t("aa\tbb\tcc", &.{ "aa", "bb", "cc" });
552 try t("aa\nbb\ncc", &.{"aa\nbb\ncc"});
553 try t("aa\r\nbb\r\ncc", &.{"aa\r\nbb\r\ncc"});
554 try t("aa\rbb\rcc", &.{"aa\rbb\rcc"});
555 try t("aa\x07bb\x07cc", &.{"aa\x07bb\x07cc"});
556 try t("aa\x7Fbb\x7Fcc", &.{"aa\x7Fbb\x7Fcc"});
557 try t("aa🦎bb🦎cc", &.{"aa🦎bb🦎cc"});
558
559 // Leading/trailing whitespace
560 try t(" ", &.{""});
561 try t(" aa bb ", &.{ "", "aa", "bb" });
562 try t("\t\t", &.{""});
563 try t("\t\taa\t\tbb\t\t", &.{ "", "aa", "bb" });
564 try t("\n\n", &.{"\n\n"});
565 try t("\n\naa\n\nbb\n\n", &.{"\n\naa\n\nbb\n\n"});
566
567 // Executable name with quotes/backslashes
568 try t("\"aa bb\tcc\ndd\"", &.{"aa bb\tcc\ndd"});
569 try t("\"", &.{""});
570 try t("\"\"", &.{""});
571 try t("\"\"\"", &.{""});
572 try t("\"\"\"\"", &.{""});
573 try t("\"\"\"\"\"", &.{""});
574 try t("aa\"bb\"cc\"dd", &.{"aabbccdd"});
575 try t("aa\"bb cc\"dd", &.{"aabb ccdd"});
576 try t("\"aa\\\"bb\"", &.{"aa\\bb"});
577 try t("\"aa\\\\\"", &.{"aa\\\\"});
578 try t("aa\\\"bb", &.{"aa\\bb"});
579 try t("aa\\\\\"bb", &.{"aa\\\\bb"});
580
581 // Arguments with quotes/backslashes
582 try t(". \"aa bb\tcc\ndd\"", &.{ ".", "aa bb\tcc\ndd" });
583 try t(". aa\" \"bb\"\t\"cc\"\n\"dd\"", &.{ ".", "aa bb\tcc\ndd" });
584 try t(". ", &.{"."});
585 try t(". \"", &.{ ".", "" });
586 try t(". \"\"", &.{ ".", "" });
587 try t(". \"\"\"", &.{ ".", "\"" });
588 try t(". \"\"\"\"", &.{ ".", "\"" });
589 try t(". \"\"\"\"\"", &.{ ".", "\"\"" });
590 try t(". \"\"\"\"\"\"", &.{ ".", "\"\"" });
591 try t(". \" \"", &.{ ".", " " });
592 try t(". \" \"\"", &.{ ".", " \"" });
593 try t(". \" \"\"\"", &.{ ".", " \"" });
594 try t(". \" \"\"\"\"", &.{ ".", " \"\"" });
595 try t(". \" \"\"\"\"\"", &.{ ".", " \"\"" });
596 try t(". \" \"\"\"\"\"\"", &.{ ".", " \"\"\"" });
597 try t(". \\\"", &.{ ".", "\"" });
598 try t(". \\\"\"", &.{ ".", "\"" });
599 try t(". \\\"\"\"", &.{ ".", "\"" });
600 try t(". \\\"\"\"\"", &.{ ".", "\"\"" });
601 try t(". \\\"\"\"\"\"", &.{ ".", "\"\"" });
602 try t(". \\\"\"\"\"\"\"", &.{ ".", "\"\"\"" });
603 try t(". \" \\\"", &.{ ".", " \"" });
604 try t(". \" \\\"\"", &.{ ".", " \"" });
605 try t(". \" \\\"\"\"", &.{ ".", " \"\"" });
606 try t(". \" \\\"\"\"\"", &.{ ".", " \"\"" });
607 try t(". \" \\\"\"\"\"\"", &.{ ".", " \"\"\"" });
608 try t(". \" \\\"\"\"\"\"\"", &.{ ".", " \"\"\"" });
609 try t(". aa\\bb\\\\cc\\\\\\dd", &.{ ".", "aa\\bb\\\\cc\\\\\\dd" });
610 try t(". \\\\\\\"aa bb\"", &.{ ".", "\\\"aa", "bb" });
611 try t(". \\\\\\\\\"aa bb\"", &.{ ".", "\\\\aa bb" });
612
613 // From https://learn.microsoft.com/en-us/cpp/cpp/main-function-command-line-args#results-of-parsing-command-lines
614 try t(
615 \\foo.exe "abc" d e
616 , &.{ "foo.exe", "abc", "d", "e" });
617 try t(
618 \\foo.exe a\\b d"e f"g h
619 , &.{ "foo.exe", "a\\\\b", "de fg", "h" });
620 try t(
621 \\foo.exe a\\\"b c d
622 , &.{ "foo.exe", "a\\\"b", "c", "d" });
623 try t(
624 \\foo.exe a\\\\"b c" d e
625 , &.{ "foo.exe", "a\\\\b c", "d", "e" });
626 try t(
627 \\foo.exe a"b"" c d
628 , &.{ "foo.exe", "ab\" c d" });
629
630 // From https://daviddeley.com/autohotkey/parameters/parameters.htm#WINCRULESEX
631 try t("foo.exe CallMeIshmael", &.{ "foo.exe", "CallMeIshmael" });
632 try t("foo.exe \"Call Me Ishmael\"", &.{ "foo.exe", "Call Me Ishmael" });
633 try t("foo.exe Cal\"l Me I\"shmael", &.{ "foo.exe", "Call Me Ishmael" });
634 try t("foo.exe CallMe\\\"Ishmael", &.{ "foo.exe", "CallMe\"Ishmael" });
635 try t("foo.exe \"CallMe\\\"Ishmael\"", &.{ "foo.exe", "CallMe\"Ishmael" });
636 try t("foo.exe \"Call Me Ishmael\\\\\"", &.{ "foo.exe", "Call Me Ishmael\\" });
637 try t("foo.exe \"CallMe\\\\\\\"Ishmael\"", &.{ "foo.exe", "CallMe\\\"Ishmael" });
638 try t("foo.exe a\\\\\\b", &.{ "foo.exe", "a\\\\\\b" });
639 try t("foo.exe \"a\\\\\\b\"", &.{ "foo.exe", "a\\\\\\b" });
640
641 // Surrogate pair encoding of 𐐷 separated by quotes.
642 // Encoded as WTF-16:
643 // "<0xD801>"<0xDC37>
644 // Encoded as WTF-8:
645 // "<0xED><0xA0><0x81>"<0xED><0xB0><0xB7>
646 // During parsing, the quotes drop out and the surrogate pair
647 // should end up encoded as its normal UTF-8 representation.
648 try t("foo.exe \"\xed\xa0\x81\"\xed\xb0\xb7", &.{ "foo.exe", "𐐷" });
649}
650
651fn testArgIteratorWindows(cmd_line: []const u8, expected_args: []const []const u8) !void {
652 const cmd_line_w = try std.unicode.wtf8ToWtf16LeAllocZ(testing.allocator, cmd_line);
653 defer testing.allocator.free(cmd_line_w);
654
655 // next
656 {
657 var it = try Iterator.Windows.init(testing.allocator, cmd_line_w);
658 defer it.deinit();
659
660 for (expected_args) |expected| {
661 if (it.next()) |actual| {
662 try testing.expectEqualStrings(expected, actual);
663 } else {
664 return error.TestUnexpectedResult;
665 }
666 }
667 try testing.expect(it.next() == null);
668 }
669
670 // skip
671 {
672 var it = try Iterator.Windows.init(testing.allocator, cmd_line_w);
673 defer it.deinit();
674
675 for (0..expected_args.len) |_| {
676 try testing.expect(it.skip());
677 }
678 try testing.expect(!it.skip());
679 }
680}
681
682test "general arg parsing" {
683 try testGeneralCmdLine("a b\tc d", &.{ "a", "b", "c", "d" });
684 try testGeneralCmdLine("\"abc\" d e", &.{ "abc", "d", "e" });
685 try testGeneralCmdLine("a\\\\\\b d\"e f\"g h", &.{ "a\\\\\\b", "de fg", "h" });
686 try testGeneralCmdLine("a\\\\\\\"b c d", &.{ "a\\\"b", "c", "d" });
687 try testGeneralCmdLine("a\\\\\\\\\"b c\" d e", &.{ "a\\\\b c", "d", "e" });
688 try testGeneralCmdLine("a b\tc \"d f", &.{ "a", "b", "c", "d f" });
689 try testGeneralCmdLine("j k l\\", &.{ "j", "k", "l\\" });
690 try testGeneralCmdLine("\"\" x y z\\\\", &.{ "", "x", "y", "z\\\\" });
691
692 try testGeneralCmdLine("\".\\..\\zig-cache\\build\" \"bin\\zig.exe\" \".\\..\" \".\\..\\zig-cache\" \"--help\"", &.{
693 ".\\..\\zig-cache\\build",
694 "bin\\zig.exe",
695 ".\\..",
696 ".\\..\\zig-cache",
697 "--help",
698 });
699
700 try testGeneralCmdLine(
701 \\ 'foo' "bar"
702 , &.{ "'foo'", "bar" });
703}
704
705fn testGeneralCmdLine(input_cmd_line: []const u8, expected_args: []const []const u8) !void {
706 var it = try ArgIteratorGeneral(.{}).init(std.testing.allocator, input_cmd_line);
707 defer it.deinit();
708 for (expected_args) |expected_arg| {
709 const arg = it.next().?;
710 try testing.expectEqualStrings(expected_arg, arg);
711 }
712 try testing.expect(it.next() == null);
713}
714
715/// Optional parameters for `ArgIteratorGeneral`
716pub const ArgIteratorGeneralOptions = struct {
717 comments: bool = false,
718 single_quotes: bool = false,
719};
720
721/// A general Iterator to parse a string into a set of arguments
722pub fn ArgIteratorGeneral(comptime options: ArgIteratorGeneralOptions) type {
723 return struct {
724 allocator: Allocator,
725 index: usize = 0,
726 cmd_line: []const u8,
727
728 /// Should the cmd_line field be free'd (using the allocator) on deinit()?
729 free_cmd_line_on_deinit: bool,
730
731 /// buffer MUST be long enough to hold the cmd_line plus a null terminator.
732 /// buffer will we free'd (using the allocator) on deinit()
733 buffer: []u8,
734 start: usize = 0,
735 end: usize = 0,
736
737 pub const Self = @This();
738
739 pub const InitError = error{OutOfMemory};
740
741 /// cmd_line_utf8 MUST remain valid and constant while using this instance
742 pub fn init(allocator: Allocator, cmd_line_utf8: []const u8) InitError!Self {
743 const buffer = try allocator.alloc(u8, cmd_line_utf8.len + 1);
744 errdefer allocator.free(buffer);
745
746 return Self{
747 .allocator = allocator,
748 .cmd_line = cmd_line_utf8,
749 .free_cmd_line_on_deinit = false,
750 .buffer = buffer,
751 };
752 }
753
754 /// cmd_line_utf8 will be free'd (with the allocator) on deinit()
755 pub fn initTakeOwnership(allocator: Allocator, cmd_line_utf8: []const u8) InitError!Self {
756 const buffer = try allocator.alloc(u8, cmd_line_utf8.len + 1);
757 errdefer allocator.free(buffer);
758
759 return Self{
760 .allocator = allocator,
761 .cmd_line = cmd_line_utf8,
762 .free_cmd_line_on_deinit = true,
763 .buffer = buffer,
764 };
765 }
766
767 // Skips over whitespace in the cmd_line.
768 // Returns false if the terminating sentinel is reached, true otherwise.
769 // Also skips over comments (if supported).
770 fn skipWhitespace(self: *Self) bool {
771 while (true) : (self.index += 1) {
772 const character = if (self.index != self.cmd_line.len) self.cmd_line[self.index] else 0;
773 switch (character) {
774 0 => return false,
775 ' ', '\t', '\r', '\n' => continue,
776 '#' => {
777 if (options.comments) {
778 while (true) : (self.index += 1) {
779 switch (self.cmd_line[self.index]) {
780 '\n' => break,
781 0 => return false,
782 else => continue,
783 }
784 }
785 continue;
786 } else {
787 break;
788 }
789 },
790 else => break,
791 }
792 }
793 return true;
794 }
795
796 pub fn skip(self: *Self) bool {
797 if (!self.skipWhitespace()) {
798 return false;
799 }
800
801 var backslash_count: usize = 0;
802 var in_quote = false;
803 while (true) : (self.index += 1) {
804 const character = if (self.index != self.cmd_line.len) self.cmd_line[self.index] else 0;
805 switch (character) {
806 0 => return true,
807 '"', '\'' => {
808 if (!options.single_quotes and character == '\'') {
809 backslash_count = 0;
810 continue;
811 }
812 const quote_is_real = backslash_count % 2 == 0;
813 if (quote_is_real) {
814 in_quote = !in_quote;
815 }
816 },
817 '\\' => {
818 backslash_count += 1;
819 },
820 ' ', '\t', '\r', '\n' => {
821 if (!in_quote) {
822 return true;
823 }
824 backslash_count = 0;
825 },
826 else => {
827 backslash_count = 0;
828 continue;
829 },
830 }
831 }
832 }
833
834 /// Returns a slice of the internal buffer that contains the next argument.
835 /// Returns null when it reaches the end.
836 pub fn next(self: *Self) ?[:0]const u8 {
837 if (!self.skipWhitespace()) {
838 return null;
839 }
840
841 var backslash_count: usize = 0;
842 var in_quote = false;
843 while (true) : (self.index += 1) {
844 const character = if (self.index != self.cmd_line.len) self.cmd_line[self.index] else 0;
845 switch (character) {
846 0 => {
847 self.emitBackslashes(backslash_count);
848 self.buffer[self.end] = 0;
849 const token = self.buffer[self.start..self.end :0];
850 self.end += 1;
851 self.start = self.end;
852 return token;
853 },
854 '"', '\'' => {
855 if (!options.single_quotes and character == '\'') {
856 self.emitBackslashes(backslash_count);
857 backslash_count = 0;
858 self.emitCharacter(character);
859 continue;
860 }
861 const quote_is_real = backslash_count % 2 == 0;
862 self.emitBackslashes(backslash_count / 2);
863 backslash_count = 0;
864
865 if (quote_is_real) {
866 in_quote = !in_quote;
867 } else {
868 self.emitCharacter('"');
869 }
870 },
871 '\\' => {
872 backslash_count += 1;
873 },
874 ' ', '\t', '\r', '\n' => {
875 self.emitBackslashes(backslash_count);
876 backslash_count = 0;
877 if (in_quote) {
878 self.emitCharacter(character);
879 } else {
880 self.buffer[self.end] = 0;
881 const token = self.buffer[self.start..self.end :0];
882 self.end += 1;
883 self.start = self.end;
884 return token;
885 }
886 },
887 else => {
888 self.emitBackslashes(backslash_count);
889 backslash_count = 0;
890 self.emitCharacter(character);
891 },
892 }
893 }
894 }
895
896 fn emitBackslashes(self: *Self, emit_count: usize) void {
897 var i: usize = 0;
898 while (i < emit_count) : (i += 1) {
899 self.emitCharacter('\\');
900 }
901 }
902
903 fn emitCharacter(self: *Self, char: u8) void {
904 self.buffer[self.end] = char;
905 self.end += 1;
906 }
907
908 /// Call to free the internal buffer of the iterator.
909 pub fn deinit(self: *Self) void {
910 self.allocator.free(self.buffer);
911
912 if (self.free_cmd_line_on_deinit) {
913 self.allocator.free(self.cmd_line);
914 }
915 }
916 };
917}
918
919test "response file arg parsing" {
920 try testResponseFileCmdLine(
921 \\a b
922 \\c d\
923 , &.{ "a", "b", "c", "d\\" });
924 try testResponseFileCmdLine("a b c d\\", &.{ "a", "b", "c", "d\\" });
925
926 try testResponseFileCmdLine(
927 \\j
928 \\ k l # this is a comment \\ \\\ \\\\ "none" "\\" "\\\"
929 \\ "m" #another comment
930 \\
931 , &.{ "j", "k", "l", "m" });
932
933 try testResponseFileCmdLine(
934 \\ "" q ""
935 \\ "r s # t" "u\" v" #another comment
936 \\
937 , &.{ "", "q", "", "r s # t", "u\" v" });
938
939 try testResponseFileCmdLine(
940 \\ -l"advapi32" a# b#c d#
941 \\e\\\
942 , &.{ "-ladvapi32", "a#", "b#c", "d#", "e\\\\\\" });
943
944 try testResponseFileCmdLine(
945 \\ 'foo' "bar"
946 , &.{ "foo", "bar" });
947}
948
949fn testResponseFileCmdLine(input_cmd_line: []const u8, expected_args: []const []const u8) !void {
950 var it = try ArgIteratorGeneral(.{ .comments = true, .single_quotes = true })
951 .init(std.testing.allocator, input_cmd_line);
952 defer it.deinit();
953 for (expected_args) |expected_arg| {
954 const arg = it.next().?;
955 try testing.expectEqualStrings(expected_arg, arg);
956 }
957 try testing.expect(it.next() == null);
958}
lib/std/process/Child.zig+34-34
...@@ -13,12 +13,17 @@ const windows = std.os.windows;...@@ -13,12 +13,17 @@ const windows = std.os.windows;
13const linux = std.os.linux;13const linux = std.os.linux;
14const posix = std.posix;14const posix = std.posix;
15const mem = std.mem;15const mem = std.mem;
16const EnvMap = std.process.EnvMap;
17const maxInt = std.math.maxInt;16const maxInt = std.math.maxInt;
18const assert = std.debug.assert;17const assert = std.debug.assert;
19const Allocator = std.mem.Allocator;18const Allocator = std.mem.Allocator;
20const ArrayList = std.ArrayList;19const ArrayList = std.ArrayList;
2120
21/// Tells whether spawning child processes is supported.
22pub const can_spawn = switch (native_os) {
23 .wasi, .ios, .tvos, .visionos, .watchos => false,
24 else => true,
25};
26
22pub const Id = switch (native_os) {27pub const Id = switch (native_os) {
23 .windows => windows.HANDLE,28 .windows => windows.HANDLE,
24 .wasi => void,29 .wasi => void,
...@@ -54,9 +59,8 @@ term: ?(SpawnError!Term),...@@ -54,9 +59,8 @@ term: ?(SpawnError!Term),
5459
55argv: []const []const u8,60argv: []const []const u8,
5661
57/// Leave as null to use the current env map using the supplied allocator.62parent_environ: process.Environ,
58/// Required if unable to access the current env map (e.g. building a library on63/// `null` means to use `parent_environ` also for the spawned process.
59/// some platforms).
60env_map: ?*const EnvMap,64env_map: ?*const EnvMap,
6165
62stdin_behavior: StdIo,66stdin_behavior: StdIo,
...@@ -229,15 +233,15 @@ pub const StdIo = enum {...@@ -229,15 +233,15 @@ pub const StdIo = enum {
229};233};
230234
231/// First argument in argv is the executable.235/// First argument in argv is the executable.
232pub fn init(argv: []const []const u8, allocator: Allocator) Child {236pub fn init(gpa: Allocator, argv: []const []const u8, environ: Environ) Child {
233 return .{237 return .{
234 .allocator = allocator,238 .allocator = gpa,
235 .argv = argv,239 .argv = argv,
240 .environ = environ,
236 .id = undefined,241 .id = undefined,
237 .thread_handle = undefined,242 .thread_handle = undefined,
238 .err_pipe = if (native_os == .windows) {} else null,243 .err_pipe = if (native_os == .windows) {} else null,
239 .term = null,244 .term = null,
240 .env_map = null,
241 .cwd = null,245 .cwd = null,
242 .uid = if (native_os == .windows or native_os == .wasi) {} else null,246 .uid = if (native_os == .windows or native_os == .wasi) {} else null,
243 .gid = if (native_os == .windows or native_os == .wasi) {} else null,247 .gid = if (native_os == .windows or native_os == .wasi) {} else null,
...@@ -435,41 +439,38 @@ pub const RunError = posix.GetCwdError || posix.ReadError || SpawnError || posix...@@ -435,41 +439,38 @@ pub const RunError = posix.GetCwdError || posix.ReadError || SpawnError || posix
435439
436/// Spawns a child process, waits for it, collecting stdout and stderr, and then returns.440/// 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.441/// If it succeeds, the caller owns result.stdout and result.stderr memory.
438pub fn run(allocator: Allocator, io: Io, args: struct {442pub fn run(gpa: Allocator, io: Io, args: struct {
439 argv: []const []const u8,443 argv: []const []const u8,
444 environ: Environ,
440 cwd: ?[]const u8 = null,445 cwd: ?[]const u8 = null,
441 cwd_dir: ?Io.Dir = null,446 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,447 max_output_bytes: usize = 50 * 1024,
446 expand_arg0: Arg0Expand = .no_expand,448 expand_arg0: Arg0Expand = .no_expand,
447 progress_node: std.Progress.Node = std.Progress.Node.none,449 progress_node: std.Progress.Node = std.Progress.Node.none,
448}) RunError!RunResult {450}) RunError!RunResult {
449 var child = Child.init(args.argv, allocator);451 var child = Child.init(gpa, args.argv, args.environ);
450 child.stdin_behavior = .Ignore;452 child.stdin_behavior = .Ignore;
451 child.stdout_behavior = .Pipe;453 child.stdout_behavior = .Pipe;
452 child.stderr_behavior = .Pipe;454 child.stderr_behavior = .Pipe;
453 child.cwd = args.cwd;455 child.cwd = args.cwd;
454 child.cwd_dir = args.cwd_dir;456 child.cwd_dir = args.cwd_dir;
455 child.env_map = args.env_map;
456 child.expand_arg0 = args.expand_arg0;457 child.expand_arg0 = args.expand_arg0;
457 child.progress_node = args.progress_node;458 child.progress_node = args.progress_node;
458459
459 var stdout: ArrayList(u8) = .empty;460 var stdout: ArrayList(u8) = .empty;
460 defer stdout.deinit(allocator);461 defer stdout.deinit(gpa);
461 var stderr: ArrayList(u8) = .empty;462 var stderr: ArrayList(u8) = .empty;
462 defer stderr.deinit(allocator);463 defer stderr.deinit(gpa);
463464
464 try child.spawn(io);465 try child.spawn(io);
465 errdefer {466 errdefer {
466 _ = child.kill(io) catch {};467 _ = child.kill(io) catch {};
467 }468 }
468 try child.collectOutput(allocator, &stdout, &stderr, args.max_output_bytes);469 try child.collectOutput(gpa, &stdout, &stderr, args.max_output_bytes);
469470
470 return .{471 return .{
471 .stdout = try stdout.toOwnedSlice(allocator),472 .stdout = try stdout.toOwnedSlice(gpa),
472 .stderr = try stderr.toOwnedSlice(allocator),473 .stderr = try stderr.toOwnedSlice(gpa),
473 .term = try child.wait(io),474 .term = try child.wait(io),
474 };475 };
475}476}
...@@ -643,23 +644,16 @@ fn spawnPosix(self: *Child, io: Io) SpawnError!void {...@@ -643,23 +644,16 @@ fn spawnPosix(self: *Child, io: Io) SpawnError!void {
643644
644 const envp: [*:null]const ?[*:0]const u8 = m: {645 const envp: [*:null]const ?[*:0]const u8 = m: {
645 const prog_fd: i32 = if (prog_pipe[1] == -1) -1 else prog_fileno;646 const prog_fd: i32 = if (prog_pipe[1] == -1) -1 else prog_fileno;
646 if (self.env_map) |env_map| {647 switch (self.environ) {
647 break :m (try process.createEnvironFromMap(arena, env_map, .{648 .empty => break :m (try process.Environ.createBlock(.{ .block = &.{} }, arena, .{
648 .zig_progress_fd = prog_fd,649 .zig_progress_fd = prog_fd,
649 })).ptr;650 })).ptr,
650 } else if (builtin.link_libc) {651 .inherit => |b| break :m (try b.createBlock(arena, .{
651 break :m (try process.createEnvironFromExisting(arena, std.c.environ, .{
652 .zig_progress_fd = prog_fd,652 .zig_progress_fd = prog_fd,
653 })).ptr;653 })).ptr,
654 } else if (builtin.output_mode == .Exe) {654 .map => |m| break :m (try m.createBlock(arena, .{
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,655 .zig_progress_fd = prog_fd,
659 })).ptr;656 })).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 }657 }
664 };658 };
665659
...@@ -701,9 +695,15 @@ fn spawnPosix(self: *Child, io: Io) SpawnError!void {...@@ -701,9 +695,15 @@ fn spawnPosix(self: *Child, io: Io) SpawnError!void {
701 posix.kill(posix.getpid(), .STOP) catch |err| forkChildErrReport(io, err_pipe[1], err);695 posix.kill(posix.getpid(), .STOP) catch |err| forkChildErrReport(io, err_pipe[1], err);
702 }696 }
703697
698 const parent_PATH: ?[]const u8 = switch(self.environ) {
699 .empty => null,
700 .inherit =>
701 .map => |m| m.get("PATH"),
702 };
703
704 const err = switch (self.expand_arg0) {704 const err = switch (self.expand_arg0) {
705 .expand => posix.execvpeZ_expandArg0(.expand, argv_buf.ptr[0].?, argv_buf.ptr, envp),705 .expand => posix.execvpeZ_expandArg0(.expand, argv_buf.ptr[0].?, argv_buf.ptr, envp, parent_PATH),
706 .no_expand => posix.execvpeZ_expandArg0(.no_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, parent_PATH),
707 };707 };
708 forkChildErrReport(io, err_pipe[1], err);708 forkChildErrReport(io, err_pipe[1], err);
709 }709 }
lib/std/process/Environ.zig created+764
...@@ -0,0 +1,764 @@
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.debug.testing;
10const unicode = std.unicode;
11const posix = std.posix;
12const mem = std.mem;
13
14block: Block,
15
16pub const Block = switch (native_os) {
17 .windows => []const u16,
18 else => []const [*:0]const u8,
19};
20
21pub const Map = struct {
22 array_hash_map: ArrayHashMap,
23 allocator: Allocator,
24
25 const ArrayHashMap = std.ArrayHashMapUnmanaged([]const u8, []const u8, EnvNameHashContext, false);
26
27 pub const Size = usize;
28
29 pub const EnvNameHashContext = struct {
30 fn upcase(c: u21) u21 {
31 if (c <= std.math.maxInt(u16))
32 return std.os.windows.ntdll.RtlUpcaseUnicodeChar(@as(u16, @intCast(c)));
33 return c;
34 }
35
36 pub fn hash(self: @This(), s: []const u8) u32 {
37 _ = self;
38 if (native_os == .windows) {
39 var h = std.hash.Wyhash.init(0);
40 var it = unicode.Wtf8View.initUnchecked(s).iterator();
41 while (it.nextCodepoint()) |cp| {
42 const cp_upper = upcase(cp);
43 h.update(&[_]u8{
44 @as(u8, @intCast((cp_upper >> 16) & 0xff)),
45 @as(u8, @intCast((cp_upper >> 8) & 0xff)),
46 @as(u8, @intCast((cp_upper >> 0) & 0xff)),
47 });
48 }
49 return h.final();
50 }
51 return std.array_hash_map.hashString(s);
52 }
53
54 pub fn eql(self: @This(), a: []const u8, b: []const u8, b_index: usize) bool {
55 _ = self;
56 _ = b_index;
57 if (native_os == .windows) {
58 var it_a = unicode.Wtf8View.initUnchecked(a).iterator();
59 var it_b = unicode.Wtf8View.initUnchecked(b).iterator();
60 while (true) {
61 const c_a = it_a.nextCodepoint() orelse break;
62 const c_b = it_b.nextCodepoint() orelse return false;
63 if (upcase(c_a) != upcase(c_b))
64 return false;
65 }
66 return if (it_b.nextCodepoint()) |_| false else true;
67 }
68 return std.array_hash_map.eqlString(a, b);
69 }
70 };
71
72 /// Create a Map backed by a specific allocator.
73 /// That allocator will be used for both backing allocations
74 /// and string deduplication.
75 pub fn init(allocator: Allocator) Map {
76 return .{ .array_hash_map = .empty, .allocator = allocator };
77 }
78
79 /// Free the backing storage of the map, as well as all
80 /// of the stored keys and values.
81 pub fn deinit(self: *Map) void {
82 const gpa = self.allocator;
83 var it = self.array_hash_map.iterator();
84 while (it.next()) |entry| {
85 gpa.free(entry.key_ptr.*);
86 gpa.free(entry.value_ptr.*);
87 }
88 self.array_hash_map.deinit(gpa);
89 self.* = undefined;
90 }
91
92 pub fn keys(m: *Map) [][]const u8 {
93 return m.array_hash_map.keys();
94 }
95
96 pub fn values(m: *Map) [][]const u8 {
97 return m.array_hash_map.values();
98 }
99
100 /// Same as `put` but the key and value become owned by the Map rather
101 /// than being copied.
102 /// If `putMove` fails, the ownership of key and value does not transfer.
103 /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string.
104 pub fn putMove(self: *Map, key: []u8, value: []u8) !void {
105 const gpa = self.allocator;
106 assert(unicode.wtf8ValidateSlice(key));
107 const get_or_put = try self.array_hash_map.getOrPut(gpa, key);
108 if (get_or_put.found_existing) {
109 gpa.free(get_or_put.key_ptr.*);
110 gpa.free(get_or_put.value_ptr.*);
111 get_or_put.key_ptr.* = key;
112 }
113 get_or_put.value_ptr.* = value;
114 }
115
116 /// `key` and `value` are copied into the Map.
117 /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string.
118 pub fn put(self: *Map, key: []const u8, value: []const u8) !void {
119 assert(unicode.wtf8ValidateSlice(key));
120 const gpa = self.allocator;
121 const value_copy = try gpa.dupe(u8, value);
122 errdefer gpa.free(value_copy);
123 const get_or_put = try self.array_hash_map.getOrPut(gpa, key);
124 errdefer {
125 if (!get_or_put.found_existing) assert(self.array_hash_map.pop() != null);
126 }
127 if (get_or_put.found_existing) {
128 gpa.free(get_or_put.value_ptr.*);
129 } else {
130 get_or_put.key_ptr.* = try gpa.dupe(u8, key);
131 }
132 get_or_put.value_ptr.* = value_copy;
133 }
134
135 /// Find the address of the value associated with a key.
136 /// The returned pointer is invalidated if the map resizes.
137 /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string.
138 pub fn getPtr(self: Map, key: []const u8) ?*[]const u8 {
139 assert(unicode.wtf8ValidateSlice(key));
140 return self.array_hash_map.getPtr(key);
141 }
142
143 /// Return the map's copy of the value associated with
144 /// a key. The returned string is invalidated if this
145 /// key is removed from the map.
146 /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string.
147 pub fn get(self: Map, key: []const u8) ?[]const u8 {
148 assert(unicode.wtf8ValidateSlice(key));
149 return self.array_hash_map.get(key);
150 }
151
152 pub fn contains(m: *const Map, key: []const u8) bool {
153 return m.contains(key);
154 }
155
156 /// If there is an entry with a matching key, it is deleted from the hash
157 /// map. The entry is removed from the underlying array by swapping it with
158 /// the last element.
159 ///
160 /// Returns true if an entry was removed, false otherwise.
161 ///
162 /// This invalidates the value returned by get() for this key.
163 /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string.
164 pub fn swapRemove(self: *Map, key: []const u8) bool {
165 assert(unicode.wtf8ValidateSlice(key));
166 const kv = self.array_hash_map.fetchSwapRemove(key) orelse return false;
167 const gpa = self.allocator;
168 gpa.free(kv.key);
169 gpa.free(kv.value);
170 return true;
171 }
172
173 /// If there is an entry with a matching key, it is deleted from the map.
174 /// The entry is removed from the underlying array by shifting all elements
175 /// forward, thereby maintaining the current ordering.
176 ///
177 /// Returns true if an entry was removed, false otherwise.
178 ///
179 /// This invalidates the value returned by get() for this key.
180 /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string.
181 pub fn orderedRemove(self: *Map, key: []const u8) bool {
182 assert(unicode.wtf8ValidateSlice(key));
183 const kv = self.array_hash_map.fetchOrderedRemove(key) orelse return false;
184 const gpa = self.allocator;
185 gpa.free(kv.key);
186 gpa.free(kv.value);
187 return true;
188 }
189
190 /// Returns the number of KV pairs stored in the map.
191 pub fn count(self: Map) Size {
192 return self.array_hash_map.count();
193 }
194
195 /// Returns an iterator over entries in the map.
196 pub fn iterator(self: *const Map) ArrayHashMap.Iterator {
197 return self.array_hash_map.iterator();
198 }
199
200 /// Returns a full copy of `em` allocated with `gpa`, which is not necessarily
201 /// the same allocator used to allocate `em`.
202 pub fn clone(m: *const Map, gpa: Allocator) Allocator.Error!Map {
203 // Since we need to dupe the keys and values, the only way for error handling to not be a
204 // nightmare is to add keys to an empty map one-by-one. This could be avoided if this
205 // abstraction were a bit less... OOP-esque.
206 var new: Map = .init(gpa);
207 errdefer new.deinit();
208 try new.array_hash_map.ensureUnusedCapacity(gpa, m.array_hash_map.count());
209 for (m.array_hash_map.keys(), m.array_hash_map.values()) |key, value| {
210 try new.put(key, value);
211 }
212 return new;
213 }
214
215 /// Creates a null-delimited environment variable block in the format
216 /// expected by POSIX, from a hash map plus options.
217 pub fn createBlock(
218 map: *const Map,
219 arena: Allocator,
220 options: CreateBlockOptions,
221 ) Allocator.Error![:null]?[*:0]u8 {
222 const ZigProgressAction = enum { nothing, edit, delete, add };
223 const zig_progress_action: ZigProgressAction = a: {
224 const fd = options.zig_progress_fd orelse break :a .nothing;
225 const exists = map.get("ZIG_PROGRESS") != null;
226 if (fd >= 0) {
227 break :a if (exists) .edit else .add;
228 } else {
229 if (exists) break :a .delete;
230 }
231 break :a .nothing;
232 };
233
234 const envp_count: usize = c: {
235 var c: usize = map.count();
236 switch (zig_progress_action) {
237 .add => c += 1,
238 .delete => c -= 1,
239 .nothing, .edit => {},
240 }
241 break :c c;
242 };
243
244 const envp_buf = try arena.allocSentinel(?[*:0]u8, envp_count, null);
245 var i: usize = 0;
246
247 if (zig_progress_action == .add) {
248 envp_buf[i] = try std.fmt.allocPrintSentinel(arena, "ZIG_PROGRESS={d}", .{options.zig_progress_fd.?}, 0);
249 i += 1;
250 }
251
252 {
253 var it = map.iterator();
254 while (it.next()) |pair| {
255 if (mem.eql(u8, pair.key_ptr.*, "ZIG_PROGRESS")) switch (zig_progress_action) {
256 .add => unreachable,
257 .delete => continue,
258 .edit => {
259 envp_buf[i] = try std.fmt.allocPrintSentinel(arena, "{s}={d}", .{
260 pair.key_ptr.*, options.zig_progress_fd.?,
261 }, 0);
262 i += 1;
263 continue;
264 },
265 .nothing => {},
266 };
267
268 envp_buf[i] = try std.fmt.allocPrintSentinel(arena, "{s}={s}", .{ pair.key_ptr.*, pair.value_ptr.* }, 0);
269 i += 1;
270 }
271 }
272
273 assert(i == envp_count);
274 return envp_buf;
275 }
276};
277
278pub const CreateMapError = error{
279 OutOfMemory,
280 /// WASI-only. `environ_sizes_get` or `environ_get` failed for an
281 /// unexpected reason.
282 Unexpected,
283};
284
285/// Allocates a `Map` and copies environment block into it.
286pub fn createMap(env: Environ, allocator: Allocator) CreateMapError!Map {
287 var result = Map.init(allocator);
288 errdefer result.deinit();
289
290 if (native_os == .windows) {
291 const ptr = env.block;
292
293 var i: usize = 0;
294 while (ptr[i] != 0) {
295 const key_start = i;
296
297 // There are some special environment variables that start with =,
298 // so we need a special case to not treat = as a key/value separator
299 // if it's the first character.
300 // https://devblogs.microsoft.com/oldnewthing/20100506-00/?p=14133
301 if (ptr[key_start] == '=') i += 1;
302
303 while (ptr[i] != 0 and ptr[i] != '=') : (i += 1) {}
304 const key_w = ptr[key_start..i];
305 const key = try unicode.wtf16LeToWtf8Alloc(allocator, key_w);
306 errdefer allocator.free(key);
307
308 if (ptr[i] == '=') i += 1;
309
310 const value_start = i;
311 while (ptr[i] != 0) : (i += 1) {}
312 const value_w = ptr[value_start..i];
313 const value = try unicode.wtf16LeToWtf8Alloc(allocator, value_w);
314 errdefer allocator.free(value);
315
316 i += 1; // skip over null byte
317
318 try result.putMove(key, value);
319 }
320 return result;
321 } else if (native_os == .wasi and !builtin.link_libc) {
322 var environ_count: usize = undefined;
323 var environ_buf_size: usize = undefined;
324
325 const environ_sizes_get_ret = std.os.wasi.environ_sizes_get(&environ_count, &environ_buf_size);
326 if (environ_sizes_get_ret != .SUCCESS) {
327 return posix.unexpectedErrno(environ_sizes_get_ret);
328 }
329
330 if (environ_count == 0) {
331 return result;
332 }
333
334 const environ = try allocator.alloc([*:0]u8, environ_count);
335 defer allocator.free(environ);
336 const environ_buf = try allocator.alloc(u8, environ_buf_size);
337 defer allocator.free(environ_buf);
338
339 const environ_get_ret = std.os.wasi.environ_get(environ.ptr, environ_buf.ptr);
340 if (environ_get_ret != .SUCCESS) {
341 return posix.unexpectedErrno(environ_get_ret);
342 }
343
344 for (environ) |line| {
345 const pair = mem.sliceTo(line, 0);
346 var parts = mem.splitScalar(u8, pair, '=');
347 const key = parts.first();
348 const value = parts.rest();
349 try result.put(key, value);
350 }
351 return result;
352 } else if (builtin.link_libc) {
353 var ptr = env.block;
354 while (ptr[0]) |line| : (ptr += 1) {
355 var line_i: usize = 0;
356 while (line[line_i] != 0 and line[line_i] != '=') : (line_i += 1) {}
357 const key = line[0..line_i];
358
359 var end_i: usize = line_i;
360 while (line[end_i] != 0) : (end_i += 1) {}
361 const value = line[line_i + 1 .. end_i];
362
363 try result.put(key, value);
364 }
365 return result;
366 } else {
367 for (env.block) |line| {
368 var line_i: usize = 0;
369 while (line[line_i] != 0 and line[line_i] != '=') : (line_i += 1) {}
370 const key = line[0..line_i];
371
372 var end_i: usize = line_i;
373 while (line[end_i] != 0) : (end_i += 1) {}
374 const value = line[line_i + 1 .. end_i];
375
376 try result.put(key, value);
377 }
378 return result;
379 }
380}
381
382test createMap {
383 var env = try createMap(testing.allocator);
384 defer env.deinit();
385}
386
387pub const GetEnvVarOwnedError = error{
388 OutOfMemory,
389 EnvironmentVariableNotFound,
390
391 /// On Windows, environment variable keys provided by the user must be valid WTF-8.
392 /// https://wtf-8.codeberg.page/
393 InvalidWtf8,
394};
395
396/// Caller must free returned memory.
397/// On Windows, if `key` is not valid [WTF-8](https://wtf-8.codeberg.page/),
398/// then `error.InvalidWtf8` is returned.
399/// On Windows, the value is encoded as [WTF-8](https://wtf-8.codeberg.page/).
400/// On other platforms, the value is an opaque sequence of bytes with no particular encoding.
401pub fn getEnvVarOwned(allocator: Allocator, key: []const u8) GetEnvVarOwnedError![]u8 {
402 if (native_os == .windows) {
403 const result_w = blk: {
404 var stack_alloc = std.heap.stackFallback(256 * @sizeOf(u16), allocator);
405 const stack_allocator = stack_alloc.get();
406 const key_w = try unicode.wtf8ToWtf16LeAllocZ(stack_allocator, key);
407 defer stack_allocator.free(key_w);
408
409 break :blk getenvW(key_w) orelse return error.EnvironmentVariableNotFound;
410 };
411 // wtf16LeToWtf8Alloc can only fail with OutOfMemory
412 return unicode.wtf16LeToWtf8Alloc(allocator, result_w);
413 } else if (native_os == .wasi and !builtin.link_libc) {
414 var envmap = createMap(allocator) catch return error.OutOfMemory;
415 defer envmap.deinit();
416 const val = envmap.get(key) orelse return error.EnvironmentVariableNotFound;
417 return allocator.dupe(u8, val);
418 } else {
419 const result = posix.getenv(key) orelse return error.EnvironmentVariableNotFound;
420 return allocator.dupe(u8, result);
421 }
422}
423
424/// On Windows, `key` must be valid WTF-8.
425pub inline fn hasEnvVarConstant(comptime key: []const u8) bool {
426 if (native_os == .windows) {
427 const key_w = comptime unicode.wtf8ToWtf16LeStringLiteral(key);
428 return getenvW(key_w) != null;
429 } else if (native_os == .wasi and !builtin.link_libc) {
430 return false;
431 } else {
432 return posix.getenv(key) != null;
433 }
434}
435
436/// On Windows, `key` must be valid WTF-8.
437pub inline fn hasNonEmptyEnvVarConstant(comptime key: []const u8) bool {
438 if (native_os == .windows) {
439 const key_w = comptime unicode.wtf8ToWtf16LeStringLiteral(key);
440 const value = getenvW(key_w) orelse return false;
441 return value.len != 0;
442 } else if (native_os == .wasi and !builtin.link_libc) {
443 return false;
444 } else {
445 const value = posix.getenv(key) orelse return false;
446 return value.len != 0;
447 }
448}
449
450pub const ParseIntError = std.fmt.ParseIntError || error{EnvironmentVariableNotFound};
451
452/// Parses an environment variable as an integer.
453///
454/// On Windows, `key` must be valid WTF-8.
455pub fn parseInt(io: std.Io, key: []const u8, comptime I: type, base: u8) ParseIntError!I {
456 const text = io.environ(key) orelse return error.EnvironmentVariableNotFound;
457 return std.fmt.parseInt(I, text, base);
458}
459
460pub const HasEnvVarError = error{
461 OutOfMemory,
462
463 /// On Windows, environment variable keys provided by the user must be valid WTF-8.
464 /// https://wtf-8.codeberg.page/
465 InvalidWtf8,
466};
467
468/// On Windows, if `key` is not valid [WTF-8](https://wtf-8.codeberg.page/),
469/// then `error.InvalidWtf8` is returned.
470pub fn hasEnvVar(allocator: Allocator, key: []const u8) HasEnvVarError!bool {
471 if (native_os == .windows) {
472 var stack_alloc = std.heap.stackFallback(256 * @sizeOf(u16), allocator);
473 const stack_allocator = stack_alloc.get();
474 const key_w = try unicode.wtf8ToWtf16LeAllocZ(stack_allocator, key);
475 defer stack_allocator.free(key_w);
476 return getenvW(key_w) != null;
477 } else if (native_os == .wasi and !builtin.link_libc) {
478 var envmap = createMap(allocator) catch return error.OutOfMemory;
479 defer envmap.deinit();
480 return envmap.getPtr(key) != null;
481 } else {
482 return posix.getenv(key) != null;
483 }
484}
485
486/// On Windows, if `key` is not valid [WTF-8](https://wtf-8.codeberg.page/),
487/// then `error.InvalidWtf8` is returned.
488pub fn hasNonEmptyEnvVar(allocator: Allocator, key: []const u8) HasEnvVarError!bool {
489 if (native_os == .windows) {
490 var stack_alloc = std.heap.stackFallback(256 * @sizeOf(u16), allocator);
491 const stack_allocator = stack_alloc.get();
492 const key_w = try unicode.wtf8ToWtf16LeAllocZ(stack_allocator, key);
493 defer stack_allocator.free(key_w);
494 const value = getenvW(key_w) orelse return false;
495 return value.len != 0;
496 } else if (native_os == .wasi and !builtin.link_libc) {
497 var envmap = createMap(allocator) catch return error.OutOfMemory;
498 defer envmap.deinit();
499 const value = envmap.getPtr(key) orelse return false;
500 return value.len != 0;
501 } else {
502 const value = posix.getenv(key) orelse return false;
503 return value.len != 0;
504 }
505}
506
507/// Windows-only. Get an environment variable with a null-terminated, WTF-16 encoded name.
508/// The returned slice points to memory in the PEB.
509///
510/// This function performs a Unicode-aware case-insensitive lookup using RtlEqualUnicodeString.
511///
512/// See also:
513/// * `std.posix.getenv`
514/// * `createMap`
515/// * `getEnvVarOwned`
516/// * `hasEnvVarConstant`
517/// * `hasEnvVar`
518pub fn getenvW(key: [*:0]const u16) ?[:0]const u16 {
519 if (native_os != .windows) {
520 @compileError("Windows-only");
521 }
522 const key_slice = mem.sliceTo(key, 0);
523 // '=' anywhere but the start makes this an invalid environment variable name
524 if (key_slice.len > 0 and std.mem.findScalar(u16, key_slice[1..], '=') != null) {
525 return null;
526 }
527 const ptr = std.os.windows.peb().ProcessParameters.Environment;
528 var i: usize = 0;
529 while (ptr[i] != 0) {
530 const key_value = mem.sliceTo(ptr[i..], 0);
531
532 // There are some special environment variables that start with =,
533 // so we need a special case to not treat = as a key/value separator
534 // if it's the first character.
535 // https://devblogs.microsoft.com/oldnewthing/20100506-00/?p=14133
536 const equal_search_start: usize = if (key_value[0] == '=') 1 else 0;
537 const equal_index = std.mem.findScalarPos(u16, key_value, equal_search_start, '=') orelse {
538 // This is enforced by CreateProcess.
539 // If violated, CreateProcess will fail with INVALID_PARAMETER.
540 unreachable; // must contain a =
541 };
542
543 const this_key = key_value[0..equal_index];
544 if (std.os.windows.eqlIgnoreCaseWtf16(key_slice, this_key)) {
545 return key_value[equal_index + 1 ..];
546 }
547
548 // skip past the NUL terminator
549 i += key_value.len + 1;
550 }
551 return null;
552}
553
554test getEnvVarOwned {
555 try testing.expectError(
556 error.EnvironmentVariableNotFound,
557 getEnvVarOwned(std.testing.allocator, "BADENV"),
558 );
559}
560
561test hasEnvVarConstant {
562 if (native_os == .wasi and !builtin.link_libc) return error.SkipZigTest;
563
564 try testing.expect(!hasEnvVarConstant("BADENV"));
565}
566
567test hasEnvVar {
568 const has_env = try hasEnvVar(std.testing.allocator, "BADENV");
569 try testing.expect(!has_env);
570}
571
572pub const CreateBlockOptions = struct {
573 /// `null` means to leave the `ZIG_PROGRESS` environment variable unmodified.
574 /// If non-null, negative means to remove the environment variable, and >= 0
575 /// means to provide it with the given integer.
576 zig_progress_fd: ?i32 = null,
577};
578
579/// Creates a null-delimited environment variable block in the format expected
580/// by POSIX, from a different one.
581pub fn createBlock(existing: Environ, arena: Allocator, options: CreateBlockOptions) Allocator.Error![:null]?[*:0]u8 {
582 const existing_count, const contains_zig_progress = c: {
583 var count: usize = 0;
584 var contains = false;
585 while (existing.block[count]) |line| : (count += 1) {
586 contains = contains or mem.eql(u8, mem.sliceTo(line, '='), "ZIG_PROGRESS");
587 }
588 break :c .{ count, contains };
589 };
590 const ZigProgressAction = enum { nothing, edit, delete, add };
591 const zig_progress_action: ZigProgressAction = a: {
592 const fd = options.zig_progress_fd orelse break :a .nothing;
593 if (fd >= 0) {
594 break :a if (contains_zig_progress) .edit else .add;
595 } else {
596 if (contains_zig_progress) break :a .delete;
597 }
598 break :a .nothing;
599 };
600
601 const envp_count: usize = c: {
602 var count: usize = existing_count;
603 switch (zig_progress_action) {
604 .add => count += 1,
605 .delete => count -= 1,
606 .nothing, .edit => {},
607 }
608 break :c count;
609 };
610
611 const envp_buf = try arena.allocSentinel(?[*:0]u8, envp_count, null);
612 var i: usize = 0;
613 var existing_index: usize = 0;
614
615 if (zig_progress_action == .add) {
616 envp_buf[i] = try std.fmt.allocPrintSentinel(arena, "ZIG_PROGRESS={d}", .{options.zig_progress_fd.?}, 0);
617 i += 1;
618 }
619
620 while (existing.block[existing_index]) |line| : (existing_index += 1) {
621 if (mem.eql(u8, mem.sliceTo(line, '='), "ZIG_PROGRESS")) switch (zig_progress_action) {
622 .add => unreachable,
623 .delete => continue,
624 .edit => {
625 envp_buf[i] = try std.fmt.allocPrintSentinel(arena, "ZIG_PROGRESS={d}", .{options.zig_progress_fd.?}, 0);
626 i += 1;
627 continue;
628 },
629 .nothing => {},
630 };
631 envp_buf[i] = try arena.dupeZ(u8, mem.span(line));
632 i += 1;
633 }
634
635 assert(i == envp_count);
636 return envp_buf;
637}
638
639test "Map.createBlock" {
640 const allocator = testing.allocator;
641 var envmap = Map.init(allocator);
642 defer envmap.deinit();
643
644 try envmap.put("HOME", "/home/ifreund");
645 try envmap.put("WAYLAND_DISPLAY", "wayland-1");
646 try envmap.put("DISPLAY", ":1");
647 try envmap.put("DEBUGINFOD_URLS", " ");
648 try envmap.put("XCURSOR_SIZE", "24");
649
650 var arena = std.heap.ArenaAllocator.init(allocator);
651 defer arena.deinit();
652 const environ = try envmap.createBlock(arena.allocator(), .{});
653
654 try testing.expectEqual(@as(usize, 5), environ.len);
655
656 inline for (.{
657 "HOME=/home/ifreund",
658 "WAYLAND_DISPLAY=wayland-1",
659 "DISPLAY=:1",
660 "DEBUGINFOD_URLS= ",
661 "XCURSOR_SIZE=24",
662 }) |target| {
663 for (environ) |variable| {
664 if (mem.eql(u8, mem.span(variable orelse continue), target)) break;
665 } else {
666 try testing.expect(false); // Environment variable not found
667 }
668 }
669}
670
671/// Caller must free result.
672pub fn createWindowsEnvBlock(allocator: mem.Allocator, env_map: *const Map) ![]u16 {
673 // count bytes needed
674 const max_chars_needed = x: {
675 // Only need 2 trailing NUL code units for an empty environment
676 var max_chars_needed: usize = if (env_map.count() == 0) 2 else 1;
677 var it = env_map.iterator();
678 while (it.next()) |pair| {
679 // +1 for '='
680 // +1 for null byte
681 max_chars_needed += pair.key_ptr.len + pair.value_ptr.len + 2;
682 }
683 break :x max_chars_needed;
684 };
685 const result = try allocator.alloc(u16, max_chars_needed);
686 errdefer allocator.free(result);
687
688 var it = env_map.iterator();
689 var i: usize = 0;
690 while (it.next()) |pair| {
691 i += try unicode.wtf8ToWtf16Le(result[i..], pair.key_ptr.*);
692 result[i] = '=';
693 i += 1;
694 i += try unicode.wtf8ToWtf16Le(result[i..], pair.value_ptr.*);
695 result[i] = 0;
696 i += 1;
697 }
698 result[i] = 0;
699 i += 1;
700 // An empty environment is a special case that requires a redundant
701 // NUL terminator. CreateProcess will read the second code unit even
702 // though theoretically the first should be enough to recognize that the
703 // environment is empty (see https://nullprogram.com/blog/2023/08/23/)
704 if (env_map.count() == 0) {
705 result[i] = 0;
706 i += 1;
707 }
708 return try allocator.realloc(result, i);
709}
710
711test Map {
712 var env = Map.init(testing.allocator);
713 defer env.deinit();
714
715 try env.put("SOMETHING_NEW", "hello");
716 try testing.expectEqualStrings("hello", env.get("SOMETHING_NEW").?);
717 try testing.expectEqual(@as(Map.Size, 1), env.count());
718
719 // overwrite
720 try env.put("SOMETHING_NEW", "something");
721 try testing.expectEqualStrings("something", env.get("SOMETHING_NEW").?);
722 try testing.expectEqual(@as(Map.Size, 1), env.count());
723
724 // a new longer name to test the Windows-specific conversion buffer
725 try env.put("SOMETHING_NEW_AND_LONGER", "1");
726 try testing.expectEqualStrings("1", env.get("SOMETHING_NEW_AND_LONGER").?);
727 try testing.expectEqual(@as(Map.Size, 2), env.count());
728
729 // case insensitivity on Windows only
730 if (native_os == .windows) {
731 try testing.expectEqualStrings("1", env.get("something_New_aNd_LONGER").?);
732 } else {
733 try testing.expect(null == env.get("something_New_aNd_LONGER"));
734 }
735
736 var it = env.iterator();
737 var count: Map.Size = 0;
738 while (it.next()) |entry| {
739 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.*);
740 try testing.expect(is_an_expected_name);
741 count += 1;
742 }
743 try testing.expectEqual(@as(Map.Size, 2), count);
744
745 env.remove("SOMETHING_NEW");
746 try testing.expect(env.get("SOMETHING_NEW") == null);
747
748 try testing.expectEqual(@as(Map.Size, 1), env.count());
749
750 if (native_os == .windows) {
751 // test Unicode case-insensitivity on Windows
752 try env.put("КИРиллИЦА", "something else");
753 try testing.expectEqualStrings("something else", env.get("кириллица").?);
754
755 // and WTF-8 that's not valid UTF-8
756 const wtf8_with_surrogate_pair = try unicode.wtf16LeToWtf8Alloc(testing.allocator, &[_]u16{
757 std.mem.nativeToLittle(u16, 0xD83D), // unpaired high surrogate
758 });
759 defer testing.allocator.free(wtf8_with_surrogate_pair);
760
761 try env.put(wtf8_with_surrogate_pair, wtf8_with_surrogate_pair);
762 try testing.expectEqualSlices(u8, wtf8_with_surrogate_pair, env.get(wtf8_with_surrogate_pair).?);
763 }
764}
lib/std/start.zig+81-51
...@@ -524,7 +524,10 @@ fn WinStartup() callconv(.withStackAlign(.c, 1)) noreturn {...@@ -524,7 +524,10 @@ fn WinStartup() callconv(.withStackAlign(.c, 1)) noreturn {
524524
525 std.debug.maybeEnableSegfaultHandler();525 std.debug.maybeEnableSegfaultHandler();
526526
527 std.os.windows.ntdll.RtlExitUserProcess(callMain());527 std.os.windows.ntdll.RtlExitUserProcess(callMain(
528 std.os.windows.peb().ProcessParameters.CommandLine,
529 std.os.windows.peb().ProcessParameters.Environment,
530 ));
528}531}
529532
530fn wWinMainCRTStartup() callconv(.withStackAlign(.c, 1)) noreturn {533fn wWinMainCRTStartup() callconv(.withStackAlign(.c, 1)) noreturn {
...@@ -666,17 +669,12 @@ fn expandStackSize(phdrs: []elf.Phdr) void {...@@ -666,17 +669,12 @@ fn expandStackSize(phdrs: []elf.Phdr) void {
666}669}
667670
668inline fn callMainWithArgs(argc: usize, argv: [*][*:0]u8, envp: [][*:0]u8) u8 {671inline fn callMainWithArgs(argc: usize, argv: [*][*:0]u8, envp: [][*:0]u8) u8 {
669 std.os.argv = argv[0..argc];
670 std.os.environ = envp;
671
672 if (std.Options.debug_threaded_io) |t| {672 if (std.Options.debug_threaded_io) |t| {
673 if (@sizeOf(std.Io.Threaded.Argv0) != 0) t.argv0.value = argv[0];673 if (@sizeOf(std.Io.Threaded.Argv0) != 0) t.argv0.value = argv[0];
674 t.environ = .{ .block = envp };674 t.environ = .{ .block = envp };
675 }675 }
676
677 std.debug.maybeEnableSegfaultHandler();676 std.debug.maybeEnableSegfaultHandler();
678677 return callMain(argv[0..argc], envp);
679 return callMain();
680}678}
681679
682fn main(c_argc: c_int, c_argv: [*][*:0]c_char, c_envp: [*:null]?[*:0]c_char) callconv(.c) c_int {680fn main(c_argc: c_int, c_argv: [*][*:0]c_char, c_envp: [*:null]?[*:0]c_char) callconv(.c) c_int {
...@@ -695,62 +693,94 @@ fn main(c_argc: c_int, c_argv: [*][*:0]c_char, c_envp: [*:null]?[*:0]c_char) cal...@@ -695,62 +693,94 @@ fn main(c_argc: c_int, c_argv: [*][*:0]c_char, c_envp: [*:null]?[*:0]c_char) cal
695}693}
696694
697fn mainWithoutEnv(c_argc: c_int, c_argv: [*][*:0]c_char) callconv(.c) c_int {695fn 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)];696 const argv = @as([*][*:0]u8, @ptrCast(c_argv))[0..@intCast(c_argc)];
699
700 if (@sizeOf(std.Io.Threaded.Argv0) != 0) {697 if (@sizeOf(std.Io.Threaded.Argv0) != 0) {
701 if (std.Options.debug_threaded_io) |t| t.argv0.value = std.os.argv[0];698 if (std.Options.debug_threaded_io) |t| t.argv0.value = argv[0];
702 }699 }
703700 return callMain(argv, &.{});
704 return callMain();
705}701}
706702
707// General error message for a malformed return type703/// 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'";704const bad_main_ret = "expected return type of main to be 'void', '!void', 'noreturn', 'u8', or '!u8'";
709705
710pub inline fn callMain() u8 {706const use_debug_allocator = !builtin.link_libc and !native_arch.isWasm() and builtin.mode == .Debug;
711 const ReturnType = @typeInfo(@TypeOf(root.main)).@"fn".return_type.?;707var debug_allocator: std.heap.DebugAllocator(.{}) = .init;
708
709inline fn callMain(args: std.process.Args.Vector, environ: std.process.Environ.Block) u8 {
710 const fn_info = @typeInfo(@TypeOf(root.main)).@"fn";
711 if (fn_info.params.len == 0) return wrapMain(root.main());
712 if (fn_info.params[0].type.? == std.process.Init.Minimal) return wrapMain(root.main(.{
713 .args = .{ .vector = args },
714 .environ = .{ .block = environ },
715 }));
716
717 const gpa = if (builtin.link_libc)
718 std.heap.c_allocator
719 else if (native_arch.isWasm())
720 std.heap.wasm_allocator
721 else if (use_debug_allocator)
722 debug_allocator.allocator()
723 else
724 std.heap.smp_allocator;
725
726 defer if (use_debug_allocator) switch (debug_allocator.deinit()) {
727 .leak => std.process.exit(1),
728 .ok => {},
729 };
712730
713 switch (ReturnType) {731 var arena_allocator = std.heap.ArenaAllocator.init(std.heap.page_allocator);
714 void => {732 defer arena_allocator.deinit();
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 };
743733
744 return switch (@TypeOf(result)) {734 var threaded: std.Io.Threaded = .init(gpa, .{
745 void => 0,735 .argv0 = if (@sizeOf(std.Io.Threaded.Argv0) != 0) .{ .value = args[0] } else .{},
746 u8 => result,736 .environ = environ,
747 else => @compileError(bad_main_ret),737 });
748 };738 defer threaded.deinit();
739
740 var env_map = environ.getEnvMap(gpa) catch |err|
741 std.process.fatal("failed to parse environment variables: {t}", .{err});
742 defer env_map.deinit();
743
744 return wrapMain(root.main(.{
745 .minimal = .{
746 .args = .{ .vector = args },
747 .environ = .{ .block = environ },
749 },748 },
749 .arena = &arena_allocator,
750 .gpa = gpa,
751 .io = threaded.io(),
752 .env_map = env_map,
753 }));
754}
755
756inline fn wrapMain(result: anytype) u8 {
757 const ReturnType = @TypeOf(result);
758 switch (ReturnType) {
759 void => return 0,
760 noreturn => unreachable,
761 u8 => return result,
762 else => {},
750 }763 }
764 if (@typeInfo(ReturnType) != .error_union) @compileError(bad_main_ret);
765
766 const unwrapped_result = result catch |err| {
767 std.log.err("{t}", .{err});
768 switch (native_os) {
769 .freestanding, .other => {},
770 else => if (@errorReturnTrace()) |trace| std.debug.dumpStackTrace(trace),
771 }
772 return 1;
773 };
774
775 return switch (@TypeOf(unwrapped_result)) {
776 noreturn => unreachable,
777 void => 0,
778 u8 => unwrapped_result,
779 else => @compileError(bad_main_ret),
780 };
751}781}
752782
753pub fn call_wWinMain() std.os.windows.INT {783fn call_wWinMain() std.os.windows.INT {
754 const peb = std.os.windows.peb();784 const peb = std.os.windows.peb();
755 const MAIN_HINSTANCE = @typeInfo(@TypeOf(root.wWinMain)).@"fn".params[0].type.?;785 const MAIN_HINSTANCE = @typeInfo(@TypeOf(root.wWinMain)).@"fn".params[0].type.?;
756 const hInstance: MAIN_HINSTANCE = @ptrCast(peb.ImageBaseAddress);786 const hInstance: MAIN_HINSTANCE = @ptrCast(peb.ImageBaseAddress);
lib/std/std.zig+3-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
...@@ -193,6 +190,9 @@ pub const Options = struct {...@@ -193,6 +190,9 @@ pub const Options = struct {
193190
194 /// Overrides `std.Io.File.Permissions`.191 /// Overrides `std.Io.File.Permissions`.
195 pub const FilePermissions: ?type = if (@hasDecl(root, "std_options_FilePermissions")) root.std_options_FilePermissions else null;192 pub const FilePermissions: ?type = if (@hasDecl(root, "std_options_FilePermissions")) root.std_options_FilePermissions else null;
193
194 /// Overrides `std.Io.Dir.cwd`.
195 pub const cwd: ?fn () Io.Dir = if (@hasDecl(root, "std_options_cwd")) root.std_options_cwd else null;
196};196};
197197
198// This forces the start.zig file to be imported, and the comptime logic inside that198// This forces the start.zig file to be imported, and the comptime logic inside that
lib/std/zig.zig+4-13
...@@ -746,21 +746,12 @@ pub const EnvVar = enum {...@@ -746,21 +746,12 @@ pub const EnvVar = enum {
746 LOCALAPPDATA,746 LOCALAPPDATA,
747 HOME,747 HOME,
748748
749 pub fn isSet(comptime ev: EnvVar) bool {749 pub fn isSet(ev: EnvVar, map: *const std.process.Environ.Map) bool {
750 return std.process.hasNonEmptyEnvVarConstant(@tagName(ev));750 return map.contains(@tagName(ev));
751 }751 }
752752
753 pub fn get(ev: EnvVar, arena: std.mem.Allocator) !?[]u8 {753 pub fn get(ev: EnvVar, map: *const std.process.Environ.Map) ?[]const u8 {
754 if (std.process.getEnvVarOwned(arena, @tagName(ev))) |value| {754 return map.get(@tagName(ev));
755 return value;
756 } else |err| switch (err) {
757 error.EnvironmentVariableNotFound => return null,
758 else => |e| return e,
759 }
760 }
761
762 pub fn getPosix(comptime ev: EnvVar) ?[:0]const u8 {
763 return std.posix.getenvZ(@tagName(ev));
764 }755 }
765};756};
766757
lib/std/zig/system/darwin.zig+5-2
...@@ -35,7 +35,7 @@ pub fn isSdkInstalled(gpa: Allocator, io: Io) bool {...@@ -35,7 +35,7 @@ pub fn isSdkInstalled(gpa: Allocator, io: Io) bool {
35/// Caller owns the memory.35/// Caller owns the memory.
36/// stderr from xcrun is ignored.36/// stderr from xcrun is ignored.
37/// If error.OutOfMemory occurs in Allocator, this function returns null.37/// If error.OutOfMemory occurs in Allocator, this function returns null.
38pub fn getSdk(gpa: Allocator, io: Io, target: *const Target) ?[]const u8 {38pub fn getSdk(gpa: Allocator, io: Io, environ: std.process.Child.Environ, target: *const Target) ?[]const u8 {
39 const is_simulator_abi = target.abi == .simulator;39 const is_simulator_abi = target.abi == .simulator;
40 const sdk = switch (target.os.tag) {40 const sdk = switch (target.os.tag) {
41 .driverkit => "driverkit",41 .driverkit => "driverkit",
...@@ -47,7 +47,10 @@ pub fn getSdk(gpa: Allocator, io: Io, target: *const Target) ?[]const u8 {...@@ -47,7 +47,10 @@ 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.Child.run(gpa, io, .{
51 .argv = argv,
52 .environ = environ,
53 }) catch return null;
51 defer {54 defer {
52 gpa.free(result.stderr);55 gpa.free(result.stderr);
53 gpa.free(result.stdout);56 gpa.free(result.stdout);
test/link/macho.zig+1-1
...@@ -871,7 +871,7 @@ fn testLinkDirectlyCppTbd(b: *Build, opts: Options) *Step {...@@ -871,7 +871,7 @@ fn testLinkDirectlyCppTbd(b: *Build, opts: Options) *Step {
871 const io = b.graph.io;871 const io = b.graph.io;
872 const test_step = addTestStep(b, "link-directly-cpp-tbd", opts);872 const test_step = addTestStep(b, "link-directly-cpp-tbd", opts);
873873
874 const sdk = std.zig.system.darwin.getSdk(b.allocator, io, &opts.target.result) orelse874 const sdk = std.zig.system.darwin.getSdk(b.allocator, io, .{ .map = &b.graph.env_map }, &opts.target.result) orelse
875 @panic("macOS SDK is required to run the test");875 @panic("macOS SDK is required to run the test");
876876
877 const exe = addExecutable(b, opts, .{877 const exe = addExecutable(b, opts, .{
test/standalone/ios/build.zig+1-1
...@@ -25,7 +25,7 @@ pub fn build(b: *std.Build) void {...@@ -25,7 +25,7 @@ pub fn build(b: *std.Build) void {
2525
26 const io = b.graph.io;26 const io = b.graph.io;
2727
28 if (std.zig.system.darwin.getSdk(b.allocator, io, &target.result)) |sdk| {28 if (std.zig.system.darwin.getSdk(b.allocator, io, .{ .map = &b.graph.env_map }, &target.result)) |sdk| {
29 b.sysroot = sdk;29 b.sysroot = sdk;
30 exe.root_module.addSystemIncludePath(.{ .cwd_relative = b.pathJoin(&.{ sdk, "/usr/include" }) });30 exe.root_module.addSystemIncludePath(.{ .cwd_relative = b.pathJoin(&.{ sdk, "/usr/include" }) });
31 exe.root_module.addSystemFrameworkPath(.{ .cwd_relative = b.pathJoin(&.{ sdk, "/System/Library/Frameworks" }) });31 exe.root_module.addSystemFrameworkPath(.{ .cwd_relative = b.pathJoin(&.{ sdk, "/System/Library/Frameworks" }) });
test/standalone/windows_bat_args/fuzz.zig+2-2
...@@ -84,13 +84,13 @@ pub fn main() anyerror!void {...@@ -84,13 +84,13 @@ pub fn main() anyerror!void {
84 }84 }
85}85}
8686
87fn testExec(gpa: Allocator, io: Io, args: []const []const u8, env: ?*std.process.EnvMap) !void {87fn testExec(gpa: Allocator, io: Io, args: []const []const u8, env: ?*std.process.Environ.Map) !void {
88 try testExecBat(gpa, io, "args1.bat", args, env);88 try testExecBat(gpa, io, "args1.bat", args, env);
89 try testExecBat(gpa, io, "args2.bat", args, env);89 try testExecBat(gpa, io, "args2.bat", args, env);
90 try testExecBat(gpa, io, "args3.bat", args, env);90 try testExecBat(gpa, io, "args3.bat", args, env);
91}91}
9292
93fn testExecBat(gpa: Allocator, io: Io, bat: []const u8, args: []const []const u8, env: ?*std.process.EnvMap) !void {93fn 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);94 const argv = try gpa.alloc([]const u8, 1 + args.len);
95 defer gpa.free(argv);95 defer gpa.free(argv);
96 argv[0] = bat;96 argv[0] = bat;
test/standalone/windows_bat_args/test.zig+2-2
...@@ -130,13 +130,13 @@ fn testExecError(err: anyerror, gpa: Allocator, io: Io, args: []const []const u8...@@ -130,13 +130,13 @@ fn testExecError(err: anyerror, gpa: Allocator, io: Io, args: []const []const u8
130 return std.testing.expectError(err, testExec(gpa, io, args, null));130 return std.testing.expectError(err, testExec(gpa, io, args, null));
131}131}
132132
133fn testExec(gpa: Allocator, io: Io, args: []const []const u8, env: ?*std.process.EnvMap) !void {133fn testExec(gpa: Allocator, io: Io, args: []const []const u8, env: ?*std.process.Environ.Map) !void {
134 try testExecBat(gpa, io, "args1.bat", args, env);134 try testExecBat(gpa, io, "args1.bat", args, env);
135 try testExecBat(gpa, io, "args2.bat", args, env);135 try testExecBat(gpa, io, "args2.bat", args, env);
136 try testExecBat(gpa, io, "args3.bat", args, env);136 try testExecBat(gpa, io, "args3.bat", args, env);
137}137}
138138
139fn testExecBat(gpa: Allocator, io: Io, bat: []const u8, args: []const []const u8, env: ?*std.process.EnvMap) !void {139fn 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);140 const argv = try gpa.alloc([]const u8, 1 + args.len);
141 defer gpa.free(argv);141 defer gpa.free(argv);
142 argv[0] = bat;142 argv[0] = bat;
test/standalone/windows_paths/test.zig+3-3
...@@ -25,10 +25,10 @@ pub fn main() anyerror!void {...@@ -25,10 +25,10 @@ pub fn main() anyerror!void {
25 const alt_drive_letter = try getAltDriveLetter(cwd_path);25 const alt_drive_letter = try getAltDriveLetter(cwd_path);
26 const alt_drive_cwd_key = try std.fmt.allocPrint(arena, "={c}:", .{alt_drive_letter});26 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});27 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);28 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);29 try alt_drive_env_map.put(alt_drive_cwd_key, alt_drive_cwd);
3030
31 const empty_env = std.process.EnvMap.init(arena);31 const empty_env = std.process.Environ.Map.init(arena);
3232
33 {33 {
34 const drive_rel = try std.fmt.allocPrint(arena, "{c}:foo", .{alt_drive_letter});34 const drive_rel = try std.fmt.allocPrint(arena, "{c}:foo", .{alt_drive_letter});
...@@ -96,7 +96,7 @@ fn checkRelative(...@@ -96,7 +96,7 @@ fn checkRelative(
96 expected_stdout: []const u8,96 expected_stdout: []const u8,
97 argv: []const []const u8,97 argv: []const []const u8,
98 cwd: ?[]const u8,98 cwd: ?[]const u8,
99 env_map: ?*const std.process.EnvMap,99 env_map: ?*const std.process.Environ.Map,
100) !void {100) !void {
101 const result = try std.process.Child.run(allocator, io, .{101 const result = try std.process.Child.run(allocator, io, .{
102 .argv = argv,102 .argv = argv,
tools/doctest.zig+1-1
...@@ -1128,7 +1128,7 @@ fn in(slice: []const u8, number: u8) bool {...@@ -1128,7 +1128,7 @@ fn in(slice: []const u8, number: u8) bool {
1128fn run(1128fn run(
1129 allocator: Allocator,1129 allocator: Allocator,
1130 io: Io,1130 io: Io,
1131 env_map: *process.EnvMap,1131 env_map: *process.Environ.Map,
1132 cwd: []const u8,1132 cwd: []const u8,
1133 args: []const []const u8,1133 args: []const []const u8,
1134) !process.Child.RunResult {1134) !process.Child.RunResult {