| author | |
| committer | |
| log | d6a1e73142396732be80a7e0757cca1c07551d30 |
| tree | 3b97b816f0bfa9eb1c88c6d202c304b286727dd4 |
| parent | 86a9a9048e28efc8b49d42db12db123822c3450b |
this commit is unfinished. It marks a spot where I wanted to start
moving child process stuff below the std.Io.VTable29 files changed, 2093 insertions(+), 2104 deletions(-)
lib/compiler/build_runner.zig+7-7| ... | ... | @@ -24,7 +24,7 @@ pub const std_options: std.Options = .{ |
| 24 | 24 | .crypto_fork_safety = false, |
| 25 | 25 | }; |
| 26 | 26 | |
| 27 | pub fn main() !void { | |
| 27 | pub fn main(init: process.Init.Minimal) !void { | |
| 28 | 28 | // The build runner is often short-lived, but thanks to `--watch` and `--webui`, that's not |
| 29 | 29 | // always the case. So, we do need a true gpa for some things. |
| 30 | 30 | var debug_gpa_state: std.heap.DebugAllocator(.{}) = .init; |
| ... | ... | @@ -37,7 +37,7 @@ pub fn main() !void { |
| 37 | 37 | var thread_safe_arena: std.heap.ThreadSafeAllocator = .{ .child_allocator = single_threaded_arena.allocator() }; |
| 38 | 38 | const arena = thread_safe_arena.allocator(); |
| 39 | 39 | |
| 40 | const args = try process.argsAlloc(arena); | |
| 40 | const args = try init.args.toSlice(arena); | |
| 41 | 41 | |
| 42 | 42 | var threaded: std.Io.Threaded = .init(gpa, .{}); |
| 43 | 43 | defer threaded.deinit(); |
| ... | ... | @@ -83,7 +83,7 @@ pub fn main() !void { |
| 83 | 83 | .manifest_dir = try local_cache_directory.handle.createDirPathOpen(io, "h", .{}), |
| 84 | 84 | }, |
| 85 | 85 | .zig_exe = zig_exe, |
| 86 | .env_map = try process.getEnvMap(arena), | |
| 86 | .env_map = try init.environ.createMap(arena), | |
| 87 | 87 | .global_cache_root = global_cache_directory, |
| 88 | 88 | .zig_lib_directory = zig_lib_directory, |
| 89 | 89 | .host = .{ |
| ... | ... | @@ -126,13 +126,13 @@ pub fn main() !void { |
| 126 | 126 | var debounce_interval_ms: u16 = 50; |
| 127 | 127 | var webui_listen: ?Io.net.IpAddress = null; |
| 128 | 128 | |
| 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 | 130 | if (std.meta.stringToEnum(ErrorStyle, str)) |style| { |
| 131 | 131 | error_style = style; |
| 132 | 132 | } |
| 133 | 133 | } |
| 134 | 134 | |
| 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 | 136 | if (std.meta.stringToEnum(MultilineErrors, str)) |style| { |
| 137 | 137 | multiline_errors = style; |
| 138 | 138 | } |
| ... | ... | @@ -429,8 +429,8 @@ pub fn main() !void { |
| 429 | 429 | } |
| 430 | 430 | } |
| 431 | 431 | |
| 432 | const NO_COLOR = std.zig.EnvVar.NO_COLOR.isSet(); | |
| 433 | const CLICOLOR_FORCE = std.zig.EnvVar.CLICOLOR_FORCE.isSet(); | |
| 432 | const NO_COLOR = std.zig.EnvVar.NO_COLOR.isSet(&graph.env_map); | |
| 433 | const CLICOLOR_FORCE = std.zig.EnvVar.CLICOLOR_FORCE.isSet(&graph.env_map); | |
| 434 | 434 | |
| 435 | 435 | graph.stderr_mode = switch (color) { |
| 436 | 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 | 12 | const Allocator = std.mem.Allocator; |
| 13 | 13 | const Target = std.Target; |
| 14 | 14 | const process = std.process; |
| 15 | const EnvMap = std.process.EnvMap; | |
| 15 | const EnvMap = std.process.Environ.Map; | |
| 16 | 16 | const File = std.Io.File; |
| 17 | 17 | const Sha256 = std.crypto.hash.sha2.Sha256; |
| 18 | 18 | const ArrayList = std.ArrayList; |
| ... | ... | @@ -1840,13 +1840,12 @@ pub fn runAllowFail( |
| 1840 | 1840 | const io = b.graph.io; |
| 1841 | 1841 | |
| 1842 | 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 | 1844 | child.stdin_behavior = .Ignore; |
| 1845 | 1845 | child.stdout_behavior = .Pipe; |
| 1846 | 1846 | child.stderr_behavior = stderr_behavior; |
| 1847 | child.env_map = &b.graph.env_map; | |
| 1848 | 1847 | |
| 1849 | try Step.handleVerbose2(b, null, child.env_map, argv); | |
| 1848 | try Step.handleVerbose2(b, null, child.environ.map, argv); | |
| 1850 | 1849 | try child.spawn(io); |
| 1851 | 1850 | |
| 1852 | 1851 | var stdout_reader = child.stdout.?.readerStreaming(io, &.{}); |
| ... | ... | @@ -1877,17 +1876,15 @@ pub fn runAllowFail( |
| 1877 | 1876 | pub fn run(b: *Build, argv: []const []const u8) []u8 { |
| 1878 | 1877 | if (!process.can_spawn) { |
| 1879 | 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 | 1881 | process.exit(1); |
| 1883 | 1882 | } |
| 1884 | 1883 | |
| 1885 | 1884 | var code: u8 = undefined; |
| 1886 | 1885 | return b.runAllowFail(argv, &code, .Inherit) catch |err| { |
| 1887 | const printed_cmd = Step.allocPrintCmd(b.allocator, null, argv) catch @panic("OOM"); | |
| 1888 | std.debug.print("unable to spawn the following command: {s}\n{s}\n", .{ | |
| 1889 | @errorName(err), printed_cmd, | |
| 1890 | }); | |
| 1886 | const printed_cmd = Step.allocPrintCmd(b.allocator, null, null, argv) catch @panic("OOM"); | |
| 1887 | std.debug.print("unable to spawn the following command: {t}\n{s}\n", .{ err, printed_cmd }); | |
| 1891 | 1888 | process.exit(1); |
| 1892 | 1889 | }; |
| 1893 | 1890 | } |
lib/std/Build/Step.zig+19-21| ... | ... | @@ -349,18 +349,20 @@ pub fn captureChildProcess( |
| 349 | 349 | progress_node: std.Progress.Node, |
| 350 | 350 | argv: []const []const u8, |
| 351 | 351 | ) !std.process.Child.RunResult { |
| 352 | const arena = s.owner.allocator; | |
| 353 | const io = s.owner.graph.io; | |
| 352 | const graph = s.owner.graph; | |
| 353 | const arena = graph.arena; | |
| 354 | const io = graph.io; | |
| 354 | 355 | |
| 355 | 356 | // If an error occurs, it's happened in this command: |
| 356 | 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); | |
| 358 | 359 | |
| 359 | 360 | try handleChildProcUnsupported(s); |
| 360 | 361 | try handleVerbose(s.owner, null, argv); |
| 361 | 362 | |
| 362 | 363 | const result = std.process.Child.run(arena, io, .{ |
| 363 | 364 | .argv = argv, |
| 365 | .environ = .{ .map = &graph.env_map }, | |
| 364 | 366 | .progress_node = progress_node, |
| 365 | 367 | }) catch |err| return s.fail("failed to run {s}: {t}", .{ argv[0], err }); |
| 366 | 368 | |
| ... | ... | @@ -406,7 +408,7 @@ pub fn evalZigProcess( |
| 406 | 408 | |
| 407 | 409 | // If an error occurs, it's happened in this command: |
| 408 | 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); | |
| 410 | 412 | |
| 411 | 413 | if (s.getZigProcess()) |zp| update: { |
| 412 | 414 | assert(watch); |
| ... | ... | @@ -447,8 +449,7 @@ pub fn evalZigProcess( |
| 447 | 449 | try handleChildProcUnsupported(s); |
| 448 | 450 | try handleVerbose(s.owner, null, argv); |
| 449 | 451 | |
| 450 | var child = std.process.Child.init(argv, arena); | |
| 451 | child.env_map = &b.graph.env_map; | |
| 452 | var child = std.process.Child.init(arena, argv, .{ .map = &b.graph.env_map }); | |
| 452 | 453 | child.stdin_behavior = .Pipe; |
| 453 | 454 | child.stdout_behavior = .Pipe; |
| 454 | 455 | child.stderr_behavior = .Pipe; |
| ... | ... | @@ -692,13 +693,17 @@ pub fn handleVerbose( |
| 692 | 693 | pub fn handleVerbose2( |
| 693 | 694 | b: *Build, |
| 694 | 695 | opt_cwd: ?[]const u8, |
| 695 | opt_env: ?*const std.process.EnvMap, | |
| 696 | opt_env: ?*const std.process.Environ.Map, | |
| 696 | 697 | argv: []const []const u8, |
| 697 | 698 | ) error{OutOfMemory}!void { |
| 698 | 699 | if (b.verbose) { |
| 700 | const graph = b.graph; | |
| 699 | 701 | // Intention of verbose is to print all sub-process command lines to |
| 700 | 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 | 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 | 733 | pub fn allocPrintCmd( |
| 729 | 734 | gpa: Allocator, |
| 730 | 735 | opt_cwd: ?[]const u8, |
| 731 | argv: []const []const u8, | |
| 732 | ) Allocator.Error![]u8 { | |
| 733 | return allocPrintCmd2(gpa, opt_cwd, null, argv); | |
| 734 | } | |
| 735 | ||
| 736 | pub fn allocPrintCmd2( | |
| 737 | gpa: Allocator, | |
| 738 | opt_cwd: ?[]const u8, | |
| 739 | opt_env: ?*const std.process.EnvMap, | |
| 736 | opt_env: ?struct { | |
| 737 | child: *const std.process.Environ.Map, | |
| 738 | parent: *const std.process.Environ.Map, | |
| 739 | }, | |
| 740 | 740 | argv: []const []const u8, |
| 741 | 741 | ) Allocator.Error![]u8 { |
| 742 | 742 | const shell = struct { |
| ... | ... | @@ -779,13 +779,11 @@ pub fn allocPrintCmd2( |
| 779 | 779 | const writer = &aw.writer; |
| 780 | 780 | if (opt_cwd) |cwd| writer.print("cd {s} && ", .{cwd}) catch return error.OutOfMemory; |
| 781 | 781 | if (opt_env) |env| { |
| 782 | var process_env_map = std.process.getEnvMap(gpa) catch std.process.EnvMap.init(gpa); | |
| 783 | defer process_env_map.deinit(); | |
| 784 | var it = env.iterator(); | |
| 782 | var it = env.child.iterator(); | |
| 785 | 783 | while (it.next()) |entry| { |
| 786 | 784 | const key = entry.key_ptr.*; |
| 787 | 785 | const value = entry.value_ptr.*; |
| 788 | if (process_env_map.get(key)) |process_value| { | |
| 786 | if (env.parent.get(key)) |process_value| { | |
| 789 | 787 | if (std.mem.eql(u8, value, process_value)) continue; |
| 790 | 788 | } |
| 791 | 789 | writer.print("{s}=", .{key}) catch return error.OutOfMemory; |
lib/std/Build/Step/Options.zig+1-1| ... | ... | @@ -546,7 +546,7 @@ test Options { |
| 546 | 546 | .manifest_dir = Io.Dir.cwd(), |
| 547 | 547 | }, |
| 548 | 548 | .zig_exe = "test", |
| 549 | .env_map = std.process.EnvMap.init(arena.allocator()), | |
| 549 | .env_map = std.process.Environ.Map.init(arena.allocator()), | |
| 550 | 550 | .global_cache_root = .{ .path = "test", .handle = Io.Dir.cwd() }, |
| 551 | 551 | .host = .{ |
| 552 | 552 | .query = .{}, |
lib/std/Build/Step/Run.zig+17-14| ... | ... | @@ -8,7 +8,7 @@ const Step = std.Build.Step; |
| 8 | 8 | const Dir = std.Io.Dir; |
| 9 | 9 | const mem = std.mem; |
| 10 | 10 | const process = std.process; |
| 11 | const EnvMap = std.process.EnvMap; | |
| 11 | const EnvMap = std.process.Environ.Map; | |
| 12 | 12 | const assert = std.debug.assert; |
| 13 | 13 | const Path = std.Build.Cache.Path; |
| 14 | 14 | |
| ... | ... | @@ -581,23 +581,24 @@ pub fn getEnvMap(run: *Run) *EnvMap { |
| 581 | 581 | } |
| 582 | 582 | |
| 583 | 583 | fn getEnvMapInternal(run: *Run) *EnvMap { |
| 584 | const arena = run.step.owner.allocator; | |
| 584 | const graph = run.step.owner.graph; | |
| 585 | const arena = graph.arena; | |
| 585 | 586 | return run.env_map orelse { |
| 586 | const env_map = arena.create(EnvMap) catch @panic("OOM"); | |
| 587 | env_map.* = process.getEnvMap(arena) catch @panic("unhandled error"); | |
| 588 | run.env_map = env_map; | |
| 589 | return env_map; | |
| 587 | const cloned_map = arena.create(EnvMap) catch @panic("OOM"); | |
| 588 | cloned_map.* = graph.env_map.clone(arena) catch @panic("OOM"); | |
| 589 | run.env_map = cloned_map; | |
| 590 | return cloned_map; | |
| 590 | 591 | }; |
| 591 | 592 | } |
| 592 | 593 | |
| 593 | 594 | pub fn setEnvironmentVariable(run: *Run, key: []const u8, value: []const u8) void { |
| 594 | const b = run.step.owner; | |
| 595 | 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 | } |
| 598 | 599 | |
| 599 | 600 | pub fn removeEnvironmentVariable(run: *Run, key: []const u8) void { |
| 600 | run.getEnvMap().remove(key); | |
| 601 | _ = run.getEnvMap().swapRemove(key); | |
| 601 | 602 | } |
| 602 | 603 | |
| 603 | 604 | /// Adds a check for exact stderr match. Does not add any other checks. |
| ... | ... | @@ -1563,11 +1564,10 @@ fn spawnChildAndCollect( |
| 1563 | 1564 | assert(run.stdio == .zig_test); |
| 1564 | 1565 | } |
| 1565 | 1566 | |
| 1566 | var child = std.process.Child.init(argv, arena); | |
| 1567 | var child = std.process.Child.init(arena, argv, .{ .map = env_map }); | |
| 1567 | 1568 | if (run.cwd) |lazy_cwd| { |
| 1568 | 1569 | child.cwd = lazy_cwd.getPath2(b, &run.step); |
| 1569 | 1570 | } |
| 1570 | child.env_map = env_map; | |
| 1571 | 1571 | child.request_resource_usage_statistics = true; |
| 1572 | 1572 | |
| 1573 | 1573 | child.stdin_behavior = switch (run.stdio) { |
| ... | ... | @@ -1597,7 +1597,10 @@ fn spawnChildAndCollect( |
| 1597 | 1597 | |
| 1598 | 1598 | // If an error occurs, it's caused by this command: |
| 1599 | 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); | |
| 1601 | 1604 | |
| 1602 | 1605 | if (run.stdio == .zig_test) { |
| 1603 | 1606 | var timer = try std.time.Timer.start(); |
| ... | ... | @@ -1627,11 +1630,11 @@ fn setColorEnvironmentVariables(run: *Run, env_map: *EnvMap, terminal_mode: Io.T |
| 1627 | 1630 | .manual => {}, |
| 1628 | 1631 | .enable => { |
| 1629 | 1632 | try env_map.put("CLICOLOR_FORCE", "1"); |
| 1630 | env_map.remove("NO_COLOR"); | |
| 1633 | _ = env_map.swapRemove("NO_COLOR"); | |
| 1631 | 1634 | }, |
| 1632 | 1635 | .disable => { |
| 1633 | 1636 | try env_map.put("NO_COLOR", "1"); |
| 1634 | env_map.remove("CLICOLOR_FORCE"); | |
| 1637 | _ = env_map.swapRemove("CLICOLOR_FORCE"); | |
| 1635 | 1638 | }, |
| 1636 | 1639 | .inherit => switch (terminal_mode) { |
| 1637 | 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 | 528 | } |
| 529 | 529 | |
| 530 | 530 | fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.OptimizeMode) !Cache.Path { |
| 531 | const io = ws.graph.io; | |
| 532 | 531 | const root_name = "build-web"; |
| 533 | 532 | const arch_os_abi = "wasm32-freestanding"; |
| 534 | 533 | const cpu_features = "baseline+atomics+bulk_memory+multivalue+mutable_globals+nontrapping_fptoint+reference_types+sign_ext"; |
| 535 | 534 | |
| 536 | 535 | const gpa = ws.gpa; |
| 537 | 536 | const graph = ws.graph; |
| 537 | const io = graph.io; | |
| 538 | 538 | |
| 539 | 539 | const main_src_path: Cache.Path = .{ |
| 540 | 540 | .root_dir = graph.zig_lib_directory, |
| ... | ... | @@ -572,7 +572,7 @@ fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.Optim |
| 572 | 572 | "--listen=-", |
| 573 | 573 | }); |
| 574 | 574 | |
| 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 | 576 | child.stdin_behavior = .Pipe; |
| 577 | 577 | child.stdout_behavior = .Pipe; |
| 578 | 578 | child.stderr_behavior = .Pipe; |
| ... | ... | @@ -640,7 +640,7 @@ fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.Optim |
| 640 | 640 | if (code != 0) { |
| 641 | 641 | log.err( |
| 642 | 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 | 645 | return error.WasmCompilationFailed; |
| 646 | 646 | } |
| ... | ... | @@ -648,7 +648,7 @@ fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.Optim |
| 648 | 648 | .Signal, .Stopped, .Unknown => { |
| 649 | 649 | log.err( |
| 650 | 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 | 653 | return error.WasmCompilationFailed; |
| 654 | 654 | }, |
| ... | ... | @@ -658,14 +658,14 @@ fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.Optim |
| 658 | 658 | try result_error_bundle.renderToStderr(io, .{}, .auto); |
| 659 | 659 | log.err("the following command failed with {d} compilation errors:\n{s}", .{ |
| 660 | 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 | 663 | return error.WasmCompilationFailed; |
| 664 | 664 | } |
| 665 | 665 | |
| 666 | 666 | const base_path = result orelse { |
| 667 | 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 | 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 | 2232 | pub fn unlockStderr(io: Io) void { |
| 2233 | 2233 | return io.vtable.unlockStderr(io.userdata); |
| 2234 | 2234 | } |
| 2235 | ||
| 2236 | pub 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 | 82 | /// |
| 83 | 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`. | |
| 86 | 86 | pub fn cwd() Dir { |
| 87 | return switch (native_os) { | |
| 87 | const cwdFn = std.Options.cwd orelse return switch (native_os) { | |
| 88 | 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 | 90 | else => .{ .handle = std.posix.AT.FDCWD }, |
| 91 | 91 | }; |
| 92 | return cwdFn(); | |
| 92 | 93 | } |
| 93 | 94 | |
| 94 | 95 | pub const Reader = struct { |
lib/std/Io/Threaded.zig+1-3| ... | ... | @@ -72,7 +72,7 @@ pub const Argv0 = switch (native_os) { |
| 72 | 72 | |
| 73 | 73 | pub const Environ = struct { |
| 74 | 74 | /// Unmodified data directly from the OS. |
| 75 | block: Block = &.{}, | |
| 75 | block: std.process.Environ.Block = &.{}, | |
| 76 | 76 | /// Protected by `mutex`. Determines whether the other fields have been |
| 77 | 77 | /// memoized based on `block`. |
| 78 | 78 | initialized: bool = false, |
| ... | ... | @@ -89,8 +89,6 @@ pub const Environ = struct { |
| 89 | 89 | |
| 90 | 90 | pub const Error = Allocator.Error || Io.UnexpectedError; |
| 91 | 91 | |
| 92 | pub const Block = []const [*:0]const u8; | |
| 93 | ||
| 94 | 92 | pub const Exist = struct { |
| 95 | 93 | NO_COLOR: bool = false, |
| 96 | 94 | CLICOLOR_FORCE: bool = false, |
lib/std/Progress.zig+1-1| ... | ... | @@ -476,7 +476,7 @@ pub fn start(io: Io, options: Options) Node { |
| 476 | 476 | |
| 477 | 477 | global_progress.io = io; |
| 478 | 478 | |
| 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 | 480 | global_progress.update_worker = io.concurrent(ipcThreadRun, .{ |
| 481 | 481 | io, |
| 482 | 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 | 40 | /// pub fn deinit(si: *SelfInfo, gpa: Allocator) void; |
| 41 | 41 | /// |
| 42 | 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 | 44 | /// /// Returns a name for the "module" (e.g. shared library or executable image) containing `address`. |
| 45 | 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 | 66 | .exe_dir = null, |
| 67 | 67 | }; |
| 68 | 68 | |
| 69 | pub fn native(exe_path: []const u8) DebugInfoSearchPaths { | |
| 69 | pub fn native(exe_path: []const u8, io: Io) DebugInfoSearchPaths { | |
| 70 | 70 | return .{ |
| 71 | 71 | .debuginfod_client = p: { |
| 72 | if (std.posix.getenv("DEBUGINFOD_CACHE_PATH")) |p| { | |
| 72 | if (io.environ("DEBUGINFOD_CACHE_PATH")) |p| { | |
| 73 | 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 | 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 | 79 | break :p .{ home_path, "/.cache/debuginfod_client" }; |
| 80 | 80 | } |
| 81 | 81 | break :p null; |
lib/std/debug/SelfInfo/Elf.zig+3-2| ... | ... | @@ -322,11 +322,12 @@ const Module = struct { |
| 322 | 322 | if (mod.loaded_elf == null) mod.loaded_elf = loadElf(mod, gpa, io); |
| 323 | 323 | return if (mod.loaded_elf.?) |*elf| elf else |err| err; |
| 324 | 324 | } |
| 325 | ||
| 325 | 326 | fn loadElf(mod: *Module, gpa: Allocator, io: Io) Error!LoadedElf { |
| 326 | 327 | const load_result = if (mod.name.len > 0) res: { |
| 327 | 328 | var file = Io.Dir.cwd().openFile(io, mod.name, .{}) catch return error.MissingDebugInfo; |
| 328 | 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 | 331 | } else res: { |
| 331 | 332 | const path = std.process.executablePathAlloc(io, gpa) catch |err| switch (err) { |
| 332 | 333 | error.OutOfMemory => |e| return e, |
| ... | ... | @@ -335,7 +336,7 @@ const Module = struct { |
| 335 | 336 | defer gpa.free(path); |
| 336 | 337 | var file = Io.Dir.cwd().openFile(io, path, .{}) catch return error.MissingDebugInfo; |
| 337 | 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 | }; |
| 340 | 341 | |
| 341 | 342 | var elf_file = load_result catch |err| switch (err) { |
lib/std/os.zig+1-64| ... | ... | @@ -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 | ||
| 15 | const root = @import("root"); | |
| 16 | const std = @import("std.zig"); | |
| 17 | 1 | const builtin = @import("builtin"); |
| 18 | const assert = std.debug.assert; | |
| 19 | const math = std.math; | |
| 20 | const mem = std.mem; | |
| 21 | const elf = std.elf; | |
| 22 | const fs = std.fs; | |
| 23 | const dl = @import("dynamic_library.zig"); | |
| 24 | const posix = std.posix; | |
| 25 | 2 | const native_os = builtin.os.tag; |
| 26 | 3 | |
| 27 | 4 | pub const linux = @import("os/linux.zig"); |
| ... | ... | @@ -33,47 +10,7 @@ pub const windows = @import("os/windows.zig"); |
| 33 | 10 | |
| 34 | 11 | test { |
| 35 | 12 | _ = linux; |
| 36 | if (native_os == .uefi) { | |
| 37 | _ = uefi; | |
| 38 | } | |
| 13 | if (native_os == .uefi) _ = uefi; | |
| 39 | 14 | _ = wasi; |
| 40 | 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 | |
| 46 | pub 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. | |
| 51 | pub 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 | ||
| 57 | pub const FstatError = error{ | |
| 58 | SystemResources, | |
| 59 | AccessDenied, | |
| 60 | Unexpected, | |
| 61 | }; | |
| 62 | ||
| 63 | pub 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 | ||
| 76 | pub 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 | 857 | } |
| 858 | 858 | } |
| 859 | 859 | |
| 860 | pub 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. | |
| 868 | pub 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 | 860 | /// This function also uses the PATH environment variable to get the full path to the executable. |
| 919 | 861 | /// If `file` is an absolute path, this is the same as `execveZ`. |
| 920 | 862 | pub fn execvpeZ( |
| 921 | 863 | file: [*:0]const u8, |
| 922 | 864 | argv_ptr: [*:null]const ?[*:0]const u8, |
| 923 | 865 | envp: [*:null]const ?[*:0]const u8, |
| 866 | optional_PATH: ?[]const u8, | |
| 924 | 867 | ) ExecveError { |
| 925 | return execvpeZ_expandArg0(.no_expand, file, argv_ptr, envp); | |
| 926 | } | |
| 927 | ||
| 928 | /// Get an environment variable. | |
| 929 | /// See also `getenvZ`. | |
| 930 | pub 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`. | |
| 972 | pub 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)); | |
| 868 | return execvpeZ_expandArg0(.no_expand, file, argv_ptr, envp, optional_PATH); | |
| 981 | 869 | } |
| 982 | 870 | |
| 983 | 871 | pub const GetCwdError = error{ |
| ... | ... | @@ -1945,7 +1833,7 @@ pub const FStatError = std.Io.File.StatError; |
| 1945 | 1833 | /// Return information about a file descriptor. |
| 1946 | 1834 | pub fn fstat(fd: fd_t) FStatError!Stat { |
| 1947 | 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 | } |
| 1950 | 1838 | |
| 1951 | 1839 | var stat = mem.zeroes(Stat); |
lib/std/process.zig+154-1740| ... | ... | @@ -16,10 +16,8 @@ const unicode = std.unicode; |
| 16 | 16 | const max_path_bytes = std.fs.max_path_bytes; |
| 17 | 17 | |
| 18 | 18 | pub const Child = @import("process/Child.zig"); |
| 19 | pub const changeCurDir = posix.chdir; | |
| 20 | pub const changeCurDirZ = posix.chdirZ; | |
| 21 | ||
| 22 | pub const GetCwdError = posix.GetCwdError; | |
| 19 | pub const Args = @import("process/Args.zig"); | |
| 20 | pub const Environ = @import("process/Environ.zig"); | |
| 23 | 21 | |
| 24 | 22 | /// This is the global, process-wide protection to coordinate stderr writes. |
| 25 | 23 | /// |
| ... | ... | @@ -28,6 +26,39 @@ pub const GetCwdError = posix.GetCwdError; |
| 28 | 26 | /// information. |
| 29 | 27 | pub var stderr_thread_mutex: std.Thread.Mutex.Recursive = .init; |
| 30 | 28 | |
| 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. | |
| 36 | pub 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 | ||
| 60 | pub const GetCwdError = posix.GetCwdError; | |
| 61 | ||
| 31 | 62 | /// The result is a slice of `out_buffer`, from index `0`. |
| 32 | 63 | /// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/). |
| 33 | 64 | /// On other platforms, the result is an opaque sequence of bytes with no particular encoding. |
| ... | ... | @@ -73,1484 +104,6 @@ test getCwdAlloc { |
| 73 | 104 | testing.allocator.free(cwd); |
| 74 | 105 | } |
| 75 | 106 | |
| 76 | pub 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 | ||
| 244 | test 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 | ||
| 299 | pub 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. | |
| 310 | pub 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 | ||
| 406 | test getEnvMap { | |
| 407 | var env = try getEnvMap(testing.allocator); | |
| 408 | defer env.deinit(); | |
| 409 | } | |
| 410 | ||
| 411 | pub 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. | |
| 425 | pub 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. | |
| 449 | pub 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. | |
| 461 | pub 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 | ||
| 474 | pub 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. | |
| 481 | pub 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 | ||
| 494 | pub 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. | |
| 504 | pub 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. | |
| 522 | pub 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` | |
| 552 | pub 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 | ||
| 588 | test getEnvVarOwned { | |
| 589 | try testing.expectError( | |
| 590 | error.EnvironmentVariableNotFound, | |
| 591 | getEnvVarOwned(std.testing.allocator, "BADENV"), | |
| 592 | ); | |
| 593 | } | |
| 594 | ||
| 595 | test hasEnvVarConstant { | |
| 596 | if (native_os == .wasi and !builtin.link_libc) return error.SkipZigTest; | |
| 597 | ||
| 598 | try testing.expect(!hasEnvVarConstant("BADENV")); | |
| 599 | } | |
| 600 | ||
| 601 | test hasEnvVar { | |
| 602 | const has_env = try hasEnvVar(std.testing.allocator, "BADENV"); | |
| 603 | try testing.expect(!has_env); | |
| 604 | } | |
| 605 | ||
| 606 | pub 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 | ||
| 635 | pub 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 | |
| 731 | pub 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` | |
| 985 | pub 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 | |
| 991 | pub 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. | |
| 1189 | pub 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. | |
| 1257 | pub fn args() ArgIterator { | |
| 1258 | return ArgIterator.init(); | |
| 1259 | } | |
| 1260 | ||
| 1261 | /// You must deinitialize iterator's internal buffers by calling `deinit` when done. | |
| 1262 | pub 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. | |
| 1269 | pub 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 | ||
| 1306 | pub 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 | ||
| 1316 | test 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 | ||
| 1449 | fn 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 | ||
| 1480 | test "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 | ||
| 1503 | fn 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 | ||
| 1513 | test "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 | ||
| 1543 | fn 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 | ||
| 1554 | 107 | pub const UserInfo = struct { |
| 1555 | 108 | uid: posix.uid_t, |
| 1556 | 109 | gid: posix.gid_t, |
| ... | ... | @@ -1706,73 +259,143 @@ pub fn getBaseAddress() usize { |
| 1706 | 259 | } |
| 1707 | 260 | } |
| 1708 | 261 | |
| 1709 | /// Tells whether calling the `execv` or `execve` functions will be a compile error. | |
| 1710 | pub const can_execv = switch (native_os) { | |
| 262 | /// Deprecated in favor of `Child.can_spawn`. | |
| 263 | pub const can_spawn = Child.can_spawn; | |
| 264 | /// Deprecated in favor of `can_replace`. | |
| 265 | pub 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. | |
| 270 | pub const can_replace = switch (native_os) { | |
| 1711 | 271 | .windows, .haiku, .wasi => false, |
| 1712 | 272 | else => true, |
| 1713 | 273 | }; |
| 1714 | 274 | |
| 1715 | /// Tells whether spawning child processes is supported (e.g. via Child) | |
| 1716 | pub const can_spawn = switch (native_os) { | |
| 1717 | .wasi, .ios, .tvos, .visionos, .watchos => false, | |
| 1718 | else => true, | |
| 1719 | }; | |
| 275 | pub const ReplaceError = std.posix.ExecveError || error{OutOfMemory}; | |
| 1720 | 276 | |
| 1721 | pub 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. | |
| 292 | pub fn replace(io: Io, gpa: Allocator, argv: []const []const u8, env: Environ.Block) ReplaceError { | |
| 293 | if (!can_replace) @compileError("unsupported operation: replace"); | |
| 1722 | 294 | |
| 1723 | /// Replaces the current process image with the executed process. | |
| 1724 | /// This function must allocate memory to add a null terminating bytes on path and each arg. | |
| 1725 | /// It must also convert to KEY=VALUE\0 format for environment variables, and include null | |
| 1726 | /// pointers after the args and after the environment variables. | |
| 1727 | /// `argv[0]` is the executable path. | |
| 1728 | /// This function also uses the PATH environment variable to get the full path to the executable. | |
| 1729 | /// Due to the heap-allocation, it is illegal to call this function in a fork() child. | |
| 1730 | /// For that use case, use the `std.posix` functions directly. | |
| 1731 | pub fn execv(allocator: Allocator, argv: []const []const u8) ExecvError { | |
| 1732 | return execve(allocator, argv, null); | |
| 295 | var arena_allocator = std.heap.ArenaAllocator.init(gpa); | |
| 296 | defer arena_allocator.deinit(); | |
| 297 | const arena = arena_allocator.allocator(); | |
| 298 | ||
| 299 | const argv_buf = try arena.allocSentinel(?[*:0]const u8, argv.len, null); | |
| 300 | for (argv, 0..) |arg, i| argv_buf[i] = (try arena.dupeZ(u8, arg)).ptr; | |
| 301 | ||
| 302 | return posix.execvpeZ_expandArg0(.no_expand, argv_buf.ptr[0].?, argv_buf.ptr, env); | |
| 1733 | 303 | } |
| 1734 | 304 | |
| 1735 | /// Replaces the current process image with the executed process. | |
| 1736 | /// This function must allocate memory to add a null terminating bytes on path and each arg. | |
| 1737 | /// It must also convert to KEY=VALUE\0 format for environment variables, and include null | |
| 1738 | /// pointers after the args and after the environment variables. | |
| 1739 | /// `argv[0]` is the executable path. | |
| 1740 | /// This function also uses the PATH environment variable to get the full path to the executable. | |
| 1741 | /// Due to the heap-allocation, it is illegal to call this function in a fork() child. | |
| 1742 | /// For that use case, use the `std.posix` functions directly. | |
| 1743 | pub fn execve( | |
| 1744 | allocator: Allocator, | |
| 1745 | argv: []const []const u8, | |
| 1746 | env_map: ?*const EnvMap, | |
| 1747 | ) ExecvError { | |
| 1748 | if (!can_execv) @compileError("The target OS does not support execv"); | |
| 1749 | ||
| 1750 | var arena_allocator = std.heap.ArenaAllocator.init(allocator); | |
| 305 | /// Replaces the current process image with the executed process. If this | |
| 306 | /// function succeeds, it does not return. | |
| 307 | /// | |
| 308 | /// `argv[0]` is the file path of the process to replace the current one with, | |
| 309 | /// relative to `dir`. It is *always* treated as a file path, even if it does | |
| 310 | /// not contain '/'. | |
| 311 | /// | |
| 312 | /// This operation is not available on targets for which `can_replace` is | |
| 313 | /// `false`. | |
| 314 | /// | |
| 315 | /// This function must allocate memory to add a null terminating bytes on path | |
| 316 | /// and each arg. | |
| 317 | /// | |
| 318 | /// Due to the heap allocation, it is illegal to call this | |
| 319 | /// function in a fork() child. For that use case, use the `std.posix` | |
| 320 | /// functions directly. | |
| 321 | pub 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 | 325 | defer arena_allocator.deinit(); |
| 1752 | 326 | const arena = arena_allocator.allocator(); |
| 1753 | 327 | |
| 1754 | 328 | const argv_buf = try arena.allocSentinel(?[*:0]const u8, argv.len, null); |
| 1755 | 329 | for (argv, 0..) |arg, i| argv_buf[i] = (try arena.dupeZ(u8, arg)).ptr; |
| 1756 | 330 | |
| 1757 | const envp = m: { | |
| 1758 | if (env_map) |m| { | |
| 1759 | const envp_buf = try createNullDelimitedEnvMap(arena, m); | |
| 1760 | break :m envp_buf.ptr; | |
| 1761 | } else if (builtin.link_libc) { | |
| 1762 | break :m std.c.environ; | |
| 1763 | } else if (builtin.output_mode == .Exe) { | |
| 1764 | // Then we have Zig start code and this works. | |
| 1765 | // TODO type-safety for null-termination of `os.environ`. | |
| 1766 | break :m @as([*:null]const ?[*:0]const u8, @ptrCast(std.os.environ.ptr)); | |
| 1767 | } else { | |
| 1768 | // TODO come up with a solution for this. | |
| 1769 | @compileError("missing std lib enhancement: std.process.execv implementation has no way to collect the environment variables to forward to the child process"); | |
| 1770 | } | |
| 331 | return posix.execvpeZ_expandArg0(.no_expand, argv_buf.ptr[0].?, argv_buf.ptr, env); | |
| 332 | } | |
| 333 | ||
| 334 | pub const Arg0Expand = enum { expand, no_expand }; | |
| 335 | ||
| 336 | /// Replaces the current process image with the executed process. If this | |
| 337 | /// function succeeds, it does not return. | |
| 338 | /// | |
| 339 | /// This operation is not available on all targets. `can_execv` | |
| 340 | /// | |
| 341 | /// This function also uses the PATH environment variable to get the full path to the executable. | |
| 342 | /// If `file` is an absolute path, this is the same as `execveZ`. | |
| 343 | /// | |
| 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. | |
| 347 | pub 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 | }; |
| 1772 | 374 | |
| 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 | } |
| 1775 | 397 | |
| 398 | ||
| 1776 | 399 | pub const TotalSystemMemoryError = error{ |
| 1777 | 400 | UnknownTotalSystemMemory, |
| 1778 | 401 | }; |
| ... | ... | @@ -1903,215 +526,6 @@ test raiseFileDescriptorLimit { |
| 1903 | 526 | raiseFileDescriptorLimit(); |
| 1904 | 527 | } |
| 1905 | 528 | |
| 1906 | pub 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. | |
| 1915 | pub 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. | |
| 1977 | pub 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 | ||
| 2039 | pub fn createNullDelimitedEnvMap(arena: mem.Allocator, env_map: *const EnvMap) Allocator.Error![:null]?[*:0]u8 { | |
| 2040 | return createEnvironFromMap(arena, env_map, .{}); | |
| 2041 | } | |
| 2042 | ||
| 2043 | test 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. | |
| 2076 | pub 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 | 529 | /// Logs an error and then terminates the process with exit code 1. |
| 2116 | 530 | pub fn fatal(comptime format: []const u8, format_arguments: anytype) noreturn { |
| 2117 | 531 | std.log.err(format, format_arguments); |
lib/std/process/Args.zig created+958| ... | ... | @@ -0,0 +1,958 @@ |
| 1 | const Args = @This(); | |
| 2 | ||
| 3 | const builtin = @import("builtin"); | |
| 4 | const native_os = builtin.os.tag; | |
| 5 | ||
| 6 | const std = @import("../std.zig"); | |
| 7 | const Allocator = std.mem.Allocator; | |
| 8 | const assert = std.debug.assert; | |
| 9 | const testing = std.debug.testing; | |
| 10 | ||
| 11 | vector: Vector, | |
| 12 | ||
| 13 | pub 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. | |
| 19 | pub 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. | |
| 454 | pub 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. | |
| 460 | pub 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. | |
| 471 | pub 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`. | |
| 508 | pub 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 | ||
| 518 | test "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 | ||
| 651 | fn 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 | ||
| 682 | test "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 | ||
| 705 | fn 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` | |
| 716 | pub 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 | |
| 722 | pub 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 | ||
| 919 | test "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 | ||
| 949 | fn 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 | 13 | const linux = std.os.linux; |
| 14 | 14 | const posix = std.posix; |
| 15 | 15 | const mem = std.mem; |
| 16 | const EnvMap = std.process.EnvMap; | |
| 17 | 16 | const maxInt = std.math.maxInt; |
| 18 | 17 | const assert = std.debug.assert; |
| 19 | 18 | const Allocator = std.mem.Allocator; |
| 20 | 19 | const ArrayList = std.ArrayList; |
| 21 | 20 | |
| 21 | /// Tells whether spawning child processes is supported. | |
| 22 | pub const can_spawn = switch (native_os) { | |
| 23 | .wasi, .ios, .tvos, .visionos, .watchos => false, | |
| 24 | else => true, | |
| 25 | }; | |
| 26 | ||
| 22 | 27 | pub const Id = switch (native_os) { |
| 23 | 28 | .windows => windows.HANDLE, |
| 24 | 29 | .wasi => void, |
| ... | ... | @@ -54,9 +59,8 @@ term: ?(SpawnError!Term), |
| 54 | 59 | |
| 55 | 60 | argv: []const []const u8, |
| 56 | 61 | |
| 57 | /// Leave as null to use the current env map using the supplied allocator. | |
| 58 | /// Required if unable to access the current env map (e.g. building a library on | |
| 59 | /// some platforms). | |
| 62 | parent_environ: process.Environ, | |
| 63 | /// `null` means to use `parent_environ` also for the spawned process. | |
| 60 | 64 | env_map: ?*const EnvMap, |
| 61 | 65 | |
| 62 | 66 | stdin_behavior: StdIo, |
| ... | ... | @@ -229,15 +233,15 @@ pub const StdIo = enum { |
| 229 | 233 | }; |
| 230 | 234 | |
| 231 | 235 | /// First argument in argv is the executable. |
| 232 | pub fn init(argv: []const []const u8, allocator: Allocator) Child { | |
| 236 | pub fn init(gpa: Allocator, argv: []const []const u8, environ: Environ) Child { | |
| 233 | 237 | return .{ |
| 234 | .allocator = allocator, | |
| 238 | .allocator = gpa, | |
| 235 | 239 | .argv = argv, |
| 240 | .environ = environ, | |
| 236 | 241 | .id = undefined, |
| 237 | 242 | .thread_handle = undefined, |
| 238 | 243 | .err_pipe = if (native_os == .windows) {} else null, |
| 239 | 244 | .term = null, |
| 240 | .env_map = null, | |
| 241 | 245 | .cwd = null, |
| 242 | 246 | .uid = if (native_os == .windows or native_os == .wasi) {} else null, |
| 243 | 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 | 439 | |
| 436 | 440 | /// Spawns a child process, waits for it, collecting stdout and stderr, and then returns. |
| 437 | 441 | /// If it succeeds, the caller owns result.stdout and result.stderr memory. |
| 438 | pub fn run(allocator: Allocator, io: Io, args: struct { | |
| 442 | pub fn run(gpa: Allocator, io: Io, args: struct { | |
| 439 | 443 | argv: []const []const u8, |
| 444 | environ: Environ, | |
| 440 | 445 | cwd: ?[]const u8 = null, |
| 441 | 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 | 447 | max_output_bytes: usize = 50 * 1024, |
| 446 | 448 | expand_arg0: Arg0Expand = .no_expand, |
| 447 | 449 | progress_node: std.Progress.Node = std.Progress.Node.none, |
| 448 | 450 | }) RunError!RunResult { |
| 449 | var child = Child.init(args.argv, allocator); | |
| 451 | var child = Child.init(gpa, args.argv, args.environ); | |
| 450 | 452 | child.stdin_behavior = .Ignore; |
| 451 | 453 | child.stdout_behavior = .Pipe; |
| 452 | 454 | child.stderr_behavior = .Pipe; |
| 453 | 455 | child.cwd = args.cwd; |
| 454 | 456 | child.cwd_dir = args.cwd_dir; |
| 455 | child.env_map = args.env_map; | |
| 456 | 457 | child.expand_arg0 = args.expand_arg0; |
| 457 | 458 | child.progress_node = args.progress_node; |
| 458 | 459 | |
| 459 | 460 | var stdout: ArrayList(u8) = .empty; |
| 460 | defer stdout.deinit(allocator); | |
| 461 | defer stdout.deinit(gpa); | |
| 461 | 462 | var stderr: ArrayList(u8) = .empty; |
| 462 | defer stderr.deinit(allocator); | |
| 463 | defer stderr.deinit(gpa); | |
| 463 | 464 | |
| 464 | 465 | try child.spawn(io); |
| 465 | 466 | errdefer { |
| 466 | 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); | |
| 469 | 470 | |
| 470 | 471 | return .{ |
| 471 | .stdout = try stdout.toOwnedSlice(allocator), | |
| 472 | .stderr = try stderr.toOwnedSlice(allocator), | |
| 472 | .stdout = try stdout.toOwnedSlice(gpa), | |
| 473 | .stderr = try stderr.toOwnedSlice(gpa), | |
| 473 | 474 | .term = try child.wait(io), |
| 474 | 475 | }; |
| 475 | 476 | } |
| ... | ... | @@ -643,23 +644,16 @@ fn spawnPosix(self: *Child, io: Io) SpawnError!void { |
| 643 | 644 | |
| 644 | 645 | const envp: [*:null]const ?[*:0]const u8 = m: { |
| 645 | 646 | const prog_fd: i32 = if (prog_pipe[1] == -1) -1 else prog_fileno; |
| 646 | if (self.env_map) |env_map| { | |
| 647 | break :m (try process.createEnvironFromMap(arena, env_map, .{ | |
| 647 | switch (self.environ) { | |
| 648 | .empty => break :m (try process.Environ.createBlock(.{ .block = &.{} }, arena, .{ | |
| 648 | 649 | .zig_progress_fd = prog_fd, |
| 649 | })).ptr; | |
| 650 | } else if (builtin.link_libc) { | |
| 651 | break :m (try process.createEnvironFromExisting(arena, std.c.environ, .{ | |
| 650 | })).ptr, | |
| 651 | .inherit => |b| break :m (try b.createBlock(arena, .{ | |
| 652 | 652 | .zig_progress_fd = prog_fd, |
| 653 | })).ptr; | |
| 654 | } else if (builtin.output_mode == .Exe) { | |
| 655 | // Then we have Zig start code and this works. | |
| 656 | // TODO type-safety for null-termination of `os.environ`. | |
| 657 | break :m (try process.createEnvironFromExisting(arena, @ptrCast(std.os.environ.ptr), .{ | |
| 653 | })).ptr, | |
| 654 | .map => |m| break :m (try m.createBlock(arena, .{ | |
| 658 | 655 | .zig_progress_fd = prog_fd, |
| 659 | })).ptr; | |
| 660 | } else { | |
| 661 | // TODO come up with a solution for this. | |
| 662 | @panic("missing std lib enhancement: std.process.Child implementation has no way to collect the environment variables to forward to the child process"); | |
| 656 | })).ptr, | |
| 663 | 657 | } |
| 664 | 658 | }; |
| 665 | 659 | |
| ... | ... | @@ -701,9 +695,15 @@ fn spawnPosix(self: *Child, io: Io) SpawnError!void { |
| 701 | 695 | posix.kill(posix.getpid(), .STOP) catch |err| forkChildErrReport(io, err_pipe[1], err); |
| 702 | 696 | } |
| 703 | 697 | |
| 698 | const parent_PATH: ?[]const u8 = switch(self.environ) { | |
| 699 | .empty => null, | |
| 700 | .inherit => | |
| 701 | .map => |m| m.get("PATH"), | |
| 702 | }; | |
| 703 | ||
| 704 | 704 | const err = switch (self.expand_arg0) { |
| 705 | .expand => posix.execvpeZ_expandArg0(.expand, argv_buf.ptr[0].?, argv_buf.ptr, envp), | |
| 706 | .no_expand => posix.execvpeZ_expandArg0(.no_expand, argv_buf.ptr[0].?, argv_buf.ptr, envp), | |
| 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, parent_PATH), | |
| 707 | 707 | }; |
| 708 | 708 | forkChildErrReport(io, err_pipe[1], err); |
| 709 | 709 | } |
lib/std/process/Environ.zig created+764| ... | ... | @@ -0,0 +1,764 @@ |
| 1 | const Environ = @This(); | |
| 2 | ||
| 3 | const builtin = @import("builtin"); | |
| 4 | const native_os = builtin.os.tag; | |
| 5 | ||
| 6 | const std = @import("../std.zig"); | |
| 7 | const Allocator = std.mem.Allocator; | |
| 8 | const assert = std.debug.assert; | |
| 9 | const testing = std.debug.testing; | |
| 10 | const unicode = std.unicode; | |
| 11 | const posix = std.posix; | |
| 12 | const mem = std.mem; | |
| 13 | ||
| 14 | block: Block, | |
| 15 | ||
| 16 | pub const Block = switch (native_os) { | |
| 17 | .windows => []const u16, | |
| 18 | else => []const [*:0]const u8, | |
| 19 | }; | |
| 20 | ||
| 21 | pub 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 | ||
| 278 | pub 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. | |
| 286 | pub 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 | ||
| 382 | test createMap { | |
| 383 | var env = try createMap(testing.allocator); | |
| 384 | defer env.deinit(); | |
| 385 | } | |
| 386 | ||
| 387 | pub 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. | |
| 401 | pub 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. | |
| 425 | pub 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. | |
| 437 | pub 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 | ||
| 450 | pub 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. | |
| 455 | pub 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 | ||
| 460 | pub 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. | |
| 470 | pub 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. | |
| 488 | pub 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` | |
| 518 | pub 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 | ||
| 554 | test getEnvVarOwned { | |
| 555 | try testing.expectError( | |
| 556 | error.EnvironmentVariableNotFound, | |
| 557 | getEnvVarOwned(std.testing.allocator, "BADENV"), | |
| 558 | ); | |
| 559 | } | |
| 560 | ||
| 561 | test hasEnvVarConstant { | |
| 562 | if (native_os == .wasi and !builtin.link_libc) return error.SkipZigTest; | |
| 563 | ||
| 564 | try testing.expect(!hasEnvVarConstant("BADENV")); | |
| 565 | } | |
| 566 | ||
| 567 | test hasEnvVar { | |
| 568 | const has_env = try hasEnvVar(std.testing.allocator, "BADENV"); | |
| 569 | try testing.expect(!has_env); | |
| 570 | } | |
| 571 | ||
| 572 | pub 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. | |
| 581 | pub 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 | ||
| 639 | test "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. | |
| 672 | pub 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 | ||
| 711 | test 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 | 524 | |
| 525 | 525 | std.debug.maybeEnableSegfaultHandler(); |
| 526 | 526 | |
| 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 | } |
| 529 | 532 | |
| 530 | 533 | fn wWinMainCRTStartup() callconv(.withStackAlign(.c, 1)) noreturn { |
| ... | ... | @@ -666,17 +669,12 @@ fn expandStackSize(phdrs: []elf.Phdr) void { |
| 666 | 669 | } |
| 667 | 670 | |
| 668 | 671 | inline 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 | 672 | if (std.Options.debug_threaded_io) |t| { |
| 673 | 673 | if (@sizeOf(std.Io.Threaded.Argv0) != 0) t.argv0.value = argv[0]; |
| 674 | 674 | t.environ = .{ .block = envp }; |
| 675 | 675 | } |
| 676 | ||
| 677 | 676 | std.debug.maybeEnableSegfaultHandler(); |
| 678 | ||
| 679 | return callMain(); | |
| 677 | return callMain(argv[0..argc], envp); | |
| 680 | 678 | } |
| 681 | 679 | |
| 682 | 680 | fn main(c_argc: c_int, c_argv: [*][*:0]c_char, c_envp: [*:null]?[*:0]c_char) callconv(.c) c_int { |
| ... | ... | @@ -695,62 +693,94 @@ fn main(c_argc: c_int, c_argv: [*][*:0]c_char, c_envp: [*:null]?[*:0]c_char) cal |
| 695 | 693 | } |
| 696 | 694 | |
| 697 | 695 | fn 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)]; | |
| 699 | ||
| 696 | const argv = @as([*][*:0]u8, @ptrCast(c_argv))[0..@intCast(c_argc)]; | |
| 700 | 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 | } |
| 703 | ||
| 704 | return callMain(); | |
| 700 | return callMain(argv, &.{}); | |
| 705 | 701 | } |
| 706 | 702 | |
| 707 | // General error message for a malformed return type | |
| 703 | /// General error message for a malformed return type | |
| 708 | 704 | const bad_main_ret = "expected return type of main to be 'void', '!void', 'noreturn', 'u8', or '!u8'"; |
| 709 | 705 | |
| 710 | pub inline fn callMain() u8 { | |
| 711 | const ReturnType = @typeInfo(@TypeOf(root.main)).@"fn".return_type.?; | |
| 706 | const use_debug_allocator = !builtin.link_libc and !native_arch.isWasm() and builtin.mode == .Debug; | |
| 707 | var debug_allocator: std.heap.DebugAllocator(.{}) = .init; | |
| 708 | ||
| 709 | inline 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 | }; | |
| 712 | 730 | |
| 713 | switch (ReturnType) { | |
| 714 | void => { | |
| 715 | root.main(); | |
| 716 | return 0; | |
| 717 | }, | |
| 718 | noreturn, u8 => { | |
| 719 | return root.main(); | |
| 720 | }, | |
| 721 | else => { | |
| 722 | if (@typeInfo(ReturnType) != .error_union) @compileError(bad_main_ret); | |
| 723 | ||
| 724 | const result = root.main() catch |err| { | |
| 725 | switch (builtin.zig_backend) { | |
| 726 | .stage2_powerpc, | |
| 727 | .stage2_riscv64, | |
| 728 | => { | |
| 729 | _ = std.posix.write(std.posix.STDERR_FILENO, "error: failed with error\n") catch {}; | |
| 730 | return 1; | |
| 731 | }, | |
| 732 | else => {}, | |
| 733 | } | |
| 734 | std.log.err("{s}", .{@errorName(err)}); | |
| 735 | switch (native_os) { | |
| 736 | .freestanding, .other => {}, | |
| 737 | else => if (@errorReturnTrace()) |trace| { | |
| 738 | std.debug.dumpStackTrace(trace); | |
| 739 | }, | |
| 740 | } | |
| 741 | return 1; | |
| 742 | }; | |
| 731 | var arena_allocator = std.heap.ArenaAllocator.init(std.heap.page_allocator); | |
| 732 | defer arena_allocator.deinit(); | |
| 743 | 733 | |
| 744 | return switch (@TypeOf(result)) { | |
| 745 | void => 0, | |
| 746 | u8 => result, | |
| 747 | else => @compileError(bad_main_ret), | |
| 748 | }; | |
| 734 | var threaded: std.Io.Threaded = .init(gpa, .{ | |
| 735 | .argv0 = if (@sizeOf(std.Io.Threaded.Argv0) != 0) .{ .value = args[0] } else .{}, | |
| 736 | .environ = environ, | |
| 737 | }); | |
| 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 | ||
| 756 | inline 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 | } |
| 752 | 782 | |
| 753 | pub fn call_wWinMain() std.os.windows.INT { | |
| 783 | fn call_wWinMain() std.os.windows.INT { | |
| 754 | 784 | const peb = std.os.windows.peb(); |
| 755 | 785 | const MAIN_HINSTANCE = @typeInfo(@TypeOf(root.wWinMain)).@"fn".params[0].type.?; |
| 756 | 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 | 114 | pub const Options = struct { |
| 115 | 115 | enable_segfault_handler: bool = debug.default_enable_segfault_handler, |
| 116 | 116 | |
| 117 | /// Function used to implement `std.Io.Dir.cwd` for WASI. | |
| 118 | wasiCwd: fn () os.wasi.fd_t = os.defaultWasiCwd, | |
| 119 | ||
| 120 | 117 | /// The current log level. |
| 121 | 118 | log_level: log.Level = log.default_level, |
| 122 | 119 | |
| ... | ... | @@ -193,6 +190,9 @@ pub const Options = struct { |
| 193 | 190 | |
| 194 | 191 | /// Overrides `std.Io.File.Permissions`. |
| 195 | 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 | }; |
| 197 | 197 | |
| 198 | 198 | // 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 | 746 | LOCALAPPDATA, |
| 747 | 747 | HOME, |
| 748 | 748 | |
| 749 | pub fn isSet(comptime ev: EnvVar) bool { | |
| 750 | return std.process.hasNonEmptyEnvVarConstant(@tagName(ev)); | |
| 749 | pub fn isSet(ev: EnvVar, map: *const std.process.Environ.Map) bool { | |
| 750 | return map.contains(@tagName(ev)); | |
| 751 | 751 | } |
| 752 | 752 | |
| 753 | pub fn get(ev: EnvVar, arena: std.mem.Allocator) !?[]u8 { | |
| 754 | if (std.process.getEnvVarOwned(arena, @tagName(ev))) |value| { | |
| 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)); | |
| 753 | pub fn get(ev: EnvVar, map: *const std.process.Environ.Map) ?[]const u8 { | |
| 754 | return map.get(@tagName(ev)); | |
| 764 | 755 | } |
| 765 | 756 | }; |
| 766 | 757 |
lib/std/zig/system/darwin.zig+5-2| ... | ... | @@ -35,7 +35,7 @@ pub fn isSdkInstalled(gpa: Allocator, io: Io) bool { |
| 35 | 35 | /// Caller owns the memory. |
| 36 | 36 | /// stderr from xcrun is ignored. |
| 37 | 37 | /// If error.OutOfMemory occurs in Allocator, this function returns null. |
| 38 | pub fn getSdk(gpa: Allocator, io: Io, target: *const Target) ?[]const u8 { | |
| 38 | pub fn getSdk(gpa: Allocator, io: Io, environ: std.process.Child.Environ, target: *const Target) ?[]const u8 { | |
| 39 | 39 | const is_simulator_abi = target.abi == .simulator; |
| 40 | 40 | const sdk = switch (target.os.tag) { |
| 41 | 41 | .driverkit => "driverkit", |
| ... | ... | @@ -47,7 +47,10 @@ pub fn getSdk(gpa: Allocator, io: Io, target: *const Target) ?[]const u8 { |
| 47 | 47 | else => return null, |
| 48 | 48 | }; |
| 49 | 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 | 54 | defer { |
| 52 | 55 | gpa.free(result.stderr); |
| 53 | 56 | gpa.free(result.stdout); |
test/link/macho.zig+1-1| ... | ... | @@ -871,7 +871,7 @@ fn testLinkDirectlyCppTbd(b: *Build, opts: Options) *Step { |
| 871 | 871 | const io = b.graph.io; |
| 872 | 872 | const test_step = addTestStep(b, "link-directly-cpp-tbd", opts); |
| 873 | 873 | |
| 874 | const sdk = std.zig.system.darwin.getSdk(b.allocator, io, &opts.target.result) orelse | |
| 874 | const sdk = std.zig.system.darwin.getSdk(b.allocator, io, .{ .map = &b.graph.env_map }, &opts.target.result) orelse | |
| 875 | 875 | @panic("macOS SDK is required to run the test"); |
| 876 | 876 | |
| 877 | 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 | 25 | |
| 26 | 26 | const io = b.graph.io; |
| 27 | 27 | |
| 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 | 29 | b.sysroot = sdk; |
| 30 | 30 | exe.root_module.addSystemIncludePath(.{ .cwd_relative = b.pathJoin(&.{ sdk, "/usr/include" }) }); |
| 31 | 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 | 84 | } |
| 85 | 85 | } |
| 86 | 86 | |
| 87 | fn testExec(gpa: Allocator, io: Io, args: []const []const u8, env: ?*std.process.EnvMap) !void { | |
| 87 | fn testExec(gpa: Allocator, io: Io, args: []const []const u8, env: ?*std.process.Environ.Map) !void { | |
| 88 | 88 | try testExecBat(gpa, io, "args1.bat", args, env); |
| 89 | 89 | try testExecBat(gpa, io, "args2.bat", args, env); |
| 90 | 90 | try testExecBat(gpa, io, "args3.bat", args, env); |
| 91 | 91 | } |
| 92 | 92 | |
| 93 | fn testExecBat(gpa: Allocator, io: Io, bat: []const u8, args: []const []const u8, env: ?*std.process.EnvMap) !void { | |
| 93 | fn testExecBat(gpa: Allocator, io: Io, bat: []const u8, args: []const []const u8, env: ?*std.process.Environ.Map) !void { | |
| 94 | 94 | const argv = try gpa.alloc([]const u8, 1 + args.len); |
| 95 | 95 | defer gpa.free(argv); |
| 96 | 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 | 130 | return std.testing.expectError(err, testExec(gpa, io, args, null)); |
| 131 | 131 | } |
| 132 | 132 | |
| 133 | fn testExec(gpa: Allocator, io: Io, args: []const []const u8, env: ?*std.process.EnvMap) !void { | |
| 133 | fn testExec(gpa: Allocator, io: Io, args: []const []const u8, env: ?*std.process.Environ.Map) !void { | |
| 134 | 134 | try testExecBat(gpa, io, "args1.bat", args, env); |
| 135 | 135 | try testExecBat(gpa, io, "args2.bat", args, env); |
| 136 | 136 | try testExecBat(gpa, io, "args3.bat", args, env); |
| 137 | 137 | } |
| 138 | 138 | |
| 139 | fn testExecBat(gpa: Allocator, io: Io, bat: []const u8, args: []const []const u8, env: ?*std.process.EnvMap) !void { | |
| 139 | fn testExecBat(gpa: Allocator, io: Io, bat: []const u8, args: []const []const u8, env: ?*std.process.Environ.Map) !void { | |
| 140 | 140 | const argv = try gpa.alloc([]const u8, 1 + args.len); |
| 141 | 141 | defer gpa.free(argv); |
| 142 | 142 | argv[0] = bat; |
test/standalone/windows_paths/test.zig+3-3| ... | ... | @@ -25,10 +25,10 @@ pub fn main() anyerror!void { |
| 25 | 25 | const alt_drive_letter = try getAltDriveLetter(cwd_path); |
| 26 | 26 | const alt_drive_cwd_key = try std.fmt.allocPrint(arena, "={c}:", .{alt_drive_letter}); |
| 27 | 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 | 29 | try alt_drive_env_map.put(alt_drive_cwd_key, alt_drive_cwd); |
| 30 | 30 | |
| 31 | const empty_env = std.process.EnvMap.init(arena); | |
| 31 | const empty_env = std.process.Environ.Map.init(arena); | |
| 32 | 32 | |
| 33 | 33 | { |
| 34 | 34 | const drive_rel = try std.fmt.allocPrint(arena, "{c}:foo", .{alt_drive_letter}); |
| ... | ... | @@ -96,7 +96,7 @@ fn checkRelative( |
| 96 | 96 | expected_stdout: []const u8, |
| 97 | 97 | argv: []const []const u8, |
| 98 | 98 | cwd: ?[]const u8, |
| 99 | env_map: ?*const std.process.EnvMap, | |
| 99 | env_map: ?*const std.process.Environ.Map, | |
| 100 | 100 | ) !void { |
| 101 | 101 | const result = try std.process.Child.run(allocator, io, .{ |
| 102 | 102 | .argv = argv, |
tools/doctest.zig+1-1| ... | ... | @@ -1128,7 +1128,7 @@ fn in(slice: []const u8, number: u8) bool { |
| 1128 | 1128 | fn run( |
| 1129 | 1129 | allocator: Allocator, |
| 1130 | 1130 | io: Io, |
| 1131 | env_map: *process.EnvMap, | |
| 1131 | env_map: *process.Environ.Map, | |
| 1132 | 1132 | cwd: []const u8, |
| 1133 | 1133 | args: []const []const u8, |
| 1134 | 1134 | ) !process.Child.RunResult { |