authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-05-23 11:25:41-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-05-26 09:31:55-07:00
logf47824f24d1b20c276a797df8cf99066c08203b6
treed988ba1a65733978fd93b85009d2088dc9a3076e
parent793f820b393e0a79842b99afb008d13eb1343ed8

std: restructure child process namespace


24 files changed, 2048 insertions(+), 2048 deletions(-)

CMakeLists.txt+104-104
...@@ -193,100 +193,6 @@ set(ZIG_CPP_SOURCES...@@ -193,100 +193,6 @@ set(ZIG_CPP_SOURCES
193# Needed because we use cmake, not the zig build system, to build zig2.o.193# Needed because we use cmake, not the zig build system, to build zig2.o.
194set(ZIG_STAGE2_SOURCES194set(ZIG_STAGE2_SOURCES
195 "${ZIG_CONFIG_ZIG_OUT}"195 "${ZIG_CONFIG_ZIG_OUT}"
196 lib/std/array_hash_map.zig
197 lib/std/array_list.zig
198 lib/std/ascii.zig
199 lib/std/atomic.zig
200 lib/std/base64.zig
201 lib/std/BitStack.zig
202 lib/std/buf_map.zig
203 lib/std/Build.zig
204 lib/std/Build/Cache.zig
205 lib/std/Build/Cache/DepTokenizer.zig
206 lib/std/builtin.zig
207 lib/std/c.zig
208 lib/std/c/linux.zig
209 lib/std/child_process.zig
210 lib/std/coff.zig
211 lib/std/static_string_map.zig
212 lib/std/crypto.zig
213 lib/std/crypto/blake3.zig
214 lib/std/crypto/siphash.zig
215 lib/std/debug.zig
216 lib/std/dwarf.zig
217 lib/std/dwarf/AT.zig
218 lib/std/dwarf/ATE.zig
219 lib/std/dwarf/FORM.zig
220 lib/std/dwarf/LANG.zig
221 lib/std/dwarf/OP.zig
222 lib/std/dwarf/TAG.zig
223 lib/std/elf.zig
224 lib/std/fifo.zig
225 lib/std/fmt.zig
226 lib/std/fmt/format_float.zig
227 lib/std/fmt/parse_float.zig
228 lib/std/fs.zig
229 lib/std/fs/AtomicFile.zig
230 lib/std/fs/Dir.zig
231 lib/std/fs/File.zig
232 lib/std/fs/get_app_data_dir.zig
233 lib/std/fs/path.zig
234 lib/std/hash.zig
235 lib/std/hash/auto_hash.zig
236 lib/std/hash/wyhash.zig
237 lib/std/hash_map.zig
238 lib/std/heap.zig
239 lib/std/heap/arena_allocator.zig
240 lib/std/io.zig
241 lib/std/io/buffered_atomic_file.zig
242 lib/std/io/buffered_writer.zig
243 lib/std/io/change_detection_stream.zig
244 lib/std/io/counting_reader.zig
245 lib/std/io/counting_writer.zig
246 lib/std/io/find_byte_writer.zig
247 lib/std/io/fixed_buffer_stream.zig
248 lib/std/io/limited_reader.zig
249 lib/std/io/Reader.zig
250 lib/std/io/seekable_stream.zig
251 lib/std/io/Writer.zig
252 lib/std/json.zig
253 lib/std/json/stringify.zig
254 lib/std/leb128.zig
255 lib/std/linked_list.zig
256 lib/std/log.zig
257 lib/std/macho.zig
258 lib/std/math.zig
259 lib/std/math/big.zig
260 lib/std/math/big/int.zig
261 lib/std/math/float.zig
262 lib/std/math/frexp.zig
263 lib/std/math/isinf.zig
264 lib/std/math/isnan.zig
265 lib/std/math/log.zig
266 lib/std/math/log10.zig
267 lib/std/math/log2.zig
268 lib/std/math/signbit.zig
269 lib/std/math/sqrt.zig
270 lib/std/mem.zig
271 lib/std/mem/Allocator.zig
272 lib/std/meta.zig
273 lib/std/meta/trailer_flags.zig
274 lib/std/multi_array_list.zig
275 lib/std/os.zig
276 lib/std/os/linux.zig
277 lib/std/os/linux/x86_64.zig
278 lib/std/os/linux.zig
279 lib/std/os/linux/IoUring.zig
280 lib/std/os/linux/io_uring_sqe.zig
281 lib/std/os/linux/x86_64.zig
282 lib/std/os/windows.zig
283 lib/std/os/windows/ntstatus.zig
284 lib/std/os/windows/win32error.zig
285 lib/std/Progress.zig
286 lib/std/pdb.zig
287 lib/std/process.zig
288 lib/std/Random.zig
289 lib/std/sort.zig
290 lib/compiler_rt.zig196 lib/compiler_rt.zig
291 lib/compiler_rt/absv.zig197 lib/compiler_rt/absv.zig
292 lib/compiler_rt/absvdi2.zig198 lib/compiler_rt/absvdi2.zig
...@@ -363,7 +269,7 @@ set(ZIG_STAGE2_SOURCES...@@ -363,7 +269,7 @@ set(ZIG_STAGE2_SOURCES
363 lib/compiler_rt/fixxfdi.zig269 lib/compiler_rt/fixxfdi.zig
364 lib/compiler_rt/fixxfsi.zig270 lib/compiler_rt/fixxfsi.zig
365 lib/compiler_rt/fixxfti.zig271 lib/compiler_rt/fixxfti.zig
366 lib/compiler_rt/int_from_float.zig272 lib/compiler_rt/float_from_int.zig
367 lib/compiler_rt/floatdidf.zig273 lib/compiler_rt/floatdidf.zig
368 lib/compiler_rt/floatdihf.zig274 lib/compiler_rt/floatdihf.zig
369 lib/compiler_rt/floatdisf.zig275 lib/compiler_rt/floatdisf.zig
...@@ -404,7 +310,7 @@ set(ZIG_STAGE2_SOURCES...@@ -404,7 +310,7 @@ set(ZIG_STAGE2_SOURCES
404 lib/compiler_rt/getf2.zig310 lib/compiler_rt/getf2.zig
405 lib/compiler_rt/gexf2.zig311 lib/compiler_rt/gexf2.zig
406 lib/compiler_rt/int.zig312 lib/compiler_rt/int.zig
407 lib/compiler_rt/float_from_int.zig313 lib/compiler_rt/int_from_float.zig
408 lib/compiler_rt/log.zig314 lib/compiler_rt/log.zig
409 lib/compiler_rt/log10.zig315 lib/compiler_rt/log10.zig
410 lib/compiler_rt/log2.zig316 lib/compiler_rt/log2.zig
...@@ -417,7 +323,11 @@ set(ZIG_STAGE2_SOURCES...@@ -417,7 +323,11 @@ set(ZIG_STAGE2_SOURCES
417 lib/compiler_rt/multf3.zig323 lib/compiler_rt/multf3.zig
418 lib/compiler_rt/mulxf3.zig324 lib/compiler_rt/mulxf3.zig
419 lib/compiler_rt/negXi2.zig325 lib/compiler_rt/negXi2.zig
326 lib/compiler_rt/negdf2.zig
327 lib/compiler_rt/negsf2.zig
328 lib/compiler_rt/negtf2.zig
420 lib/compiler_rt/negv.zig329 lib/compiler_rt/negv.zig
330 lib/compiler_rt/negxf2.zig
421 lib/compiler_rt/os_version_check.zig331 lib/compiler_rt/os_version_check.zig
422 lib/compiler_rt/parity.zig332 lib/compiler_rt/parity.zig
423 lib/compiler_rt/popcount.zig333 lib/compiler_rt/popcount.zig
...@@ -430,15 +340,11 @@ set(ZIG_STAGE2_SOURCES...@@ -430,15 +340,11 @@ set(ZIG_STAGE2_SOURCES
430 lib/compiler_rt/sincos.zig340 lib/compiler_rt/sincos.zig
431 lib/compiler_rt/sqrt.zig341 lib/compiler_rt/sqrt.zig
432 lib/compiler_rt/stack_probe.zig342 lib/compiler_rt/stack_probe.zig
343 lib/compiler_rt/subdf3.zig
433 lib/compiler_rt/subo.zig344 lib/compiler_rt/subo.zig
434 lib/compiler_rt/subsf3.zig345 lib/compiler_rt/subsf3.zig
435 lib/compiler_rt/subdf3.zig
436 lib/compiler_rt/subtf3.zig346 lib/compiler_rt/subtf3.zig
437 lib/compiler_rt/subxf3.zig347 lib/compiler_rt/subxf3.zig
438 lib/compiler_rt/negsf2.zig
439 lib/compiler_rt/negdf2.zig
440 lib/compiler_rt/negtf2.zig
441 lib/compiler_rt/negxf2.zig
442 lib/compiler_rt/tan.zig348 lib/compiler_rt/tan.zig
443 lib/compiler_rt/trig.zig349 lib/compiler_rt/trig.zig
444 lib/compiler_rt/trunc.zig350 lib/compiler_rt/trunc.zig
...@@ -461,8 +367,12 @@ set(ZIG_STAGE2_SOURCES...@@ -461,8 +367,12 @@ set(ZIG_STAGE2_SOURCES
461 lib/compiler_rt/unorddf2.zig367 lib/compiler_rt/unorddf2.zig
462 lib/compiler_rt/unordsf2.zig368 lib/compiler_rt/unordsf2.zig
463 lib/compiler_rt/unordtf2.zig369 lib/compiler_rt/unordtf2.zig
464 lib/std/start.zig370 lib/std/BitStack.zig
465 lib/std/std.zig371 lib/std/Build.zig
372 lib/std/Build/Cache.zig
373 lib/std/Build/Cache/DepTokenizer.zig
374 lib/std/Progress.zig
375 lib/std/Random.zig
466 lib/std/Target.zig376 lib/std/Target.zig
467 lib/std/Target/Query.zig377 lib/std/Target/Query.zig
468 lib/std/Target/aarch64.zig378 lib/std/Target/aarch64.zig
...@@ -476,8 +386,8 @@ set(ZIG_STAGE2_SOURCES...@@ -476,8 +386,8 @@ set(ZIG_STAGE2_SOURCES
476 lib/std/Target/nvptx.zig386 lib/std/Target/nvptx.zig
477 lib/std/Target/powerpc.zig387 lib/std/Target/powerpc.zig
478 lib/std/Target/riscv.zig388 lib/std/Target/riscv.zig
479 lib/std/Target/sparc.zig
480 lib/std/Target/s390x.zig389 lib/std/Target/s390x.zig
390 lib/std/Target/sparc.zig
481 lib/std/Target/wasm.zig391 lib/std/Target/wasm.zig
482 lib/std/Target/x86.zig392 lib/std/Target/x86.zig
483 lib/std/Thread.zig393 lib/std/Thread.zig
...@@ -486,6 +396,96 @@ set(ZIG_STAGE2_SOURCES...@@ -486,6 +396,96 @@ set(ZIG_STAGE2_SOURCES
486 lib/std/Thread/Pool.zig396 lib/std/Thread/Pool.zig
487 lib/std/Thread/ResetEvent.zig397 lib/std/Thread/ResetEvent.zig
488 lib/std/Thread/WaitGroup.zig398 lib/std/Thread/WaitGroup.zig
399 lib/std/array_hash_map.zig
400 lib/std/array_list.zig
401 lib/std/ascii.zig
402 lib/std/atomic.zig
403 lib/std/base64.zig
404 lib/std/buf_map.zig
405 lib/std/builtin.zig
406 lib/std/c.zig
407 lib/std/c/linux.zig
408 lib/std/coff.zig
409 lib/std/crypto.zig
410 lib/std/crypto/blake3.zig
411 lib/std/crypto/siphash.zig
412 lib/std/debug.zig
413 lib/std/dwarf.zig
414 lib/std/dwarf/AT.zig
415 lib/std/dwarf/ATE.zig
416 lib/std/dwarf/FORM.zig
417 lib/std/dwarf/LANG.zig
418 lib/std/dwarf/OP.zig
419 lib/std/dwarf/TAG.zig
420 lib/std/elf.zig
421 lib/std/fifo.zig
422 lib/std/fmt.zig
423 lib/std/fmt/format_float.zig
424 lib/std/fmt/parse_float.zig
425 lib/std/fs.zig
426 lib/std/fs/AtomicFile.zig
427 lib/std/fs/Dir.zig
428 lib/std/fs/File.zig
429 lib/std/fs/get_app_data_dir.zig
430 lib/std/fs/path.zig
431 lib/std/hash.zig
432 lib/std/hash/auto_hash.zig
433 lib/std/hash/wyhash.zig
434 lib/std/hash_map.zig
435 lib/std/heap.zig
436 lib/std/heap/arena_allocator.zig
437 lib/std/io.zig
438 lib/std/io/Reader.zig
439 lib/std/io/Writer.zig
440 lib/std/io/buffered_atomic_file.zig
441 lib/std/io/buffered_writer.zig
442 lib/std/io/change_detection_stream.zig
443 lib/std/io/counting_reader.zig
444 lib/std/io/counting_writer.zig
445 lib/std/io/find_byte_writer.zig
446 lib/std/io/fixed_buffer_stream.zig
447 lib/std/io/limited_reader.zig
448 lib/std/io/seekable_stream.zig
449 lib/std/json.zig
450 lib/std/json/stringify.zig
451 lib/std/leb128.zig
452 lib/std/linked_list.zig
453 lib/std/log.zig
454 lib/std/macho.zig
455 lib/std/math.zig
456 lib/std/math/big.zig
457 lib/std/math/big/int.zig
458 lib/std/math/float.zig
459 lib/std/math/frexp.zig
460 lib/std/math/isinf.zig
461 lib/std/math/isnan.zig
462 lib/std/math/log.zig
463 lib/std/math/log10.zig
464 lib/std/math/log2.zig
465 lib/std/math/signbit.zig
466 lib/std/math/sqrt.zig
467 lib/std/mem.zig
468 lib/std/mem/Allocator.zig
469 lib/std/meta.zig
470 lib/std/meta/trailer_flags.zig
471 lib/std/multi_array_list.zig
472 lib/std/os.zig
473 lib/std/os/linux.zig
474 lib/std/os/linux.zig
475 lib/std/os/linux/IoUring.zig
476 lib/std/os/linux/io_uring_sqe.zig
477 lib/std/os/linux/x86_64.zig
478 lib/std/os/linux/x86_64.zig
479 lib/std/os/windows.zig
480 lib/std/os/windows/ntstatus.zig
481 lib/std/os/windows/win32error.zig
482 lib/std/pdb.zig
483 lib/std/process.zig
484 lib/std/process/Child.zig
485 lib/std/sort.zig
486 lib/std/start.zig
487 lib/std/static_string_map.zig
488 lib/std/std.zig
489 lib/std/time.zig489 lib/std/time.zig
490 lib/std/treap.zig490 lib/std/treap.zig
491 lib/std/unicode.zig491 lib/std/unicode.zig
lib/compiler/aro/aro/Driver.zig+1-1
...@@ -803,7 +803,7 @@ pub fn invokeLinker(d: *Driver, tc: *Toolchain, comptime fast_exit: bool) !void...@@ -803,7 +803,7 @@ pub fn invokeLinker(d: *Driver, tc: *Toolchain, comptime fast_exit: bool) !void
803 return d.fatal("unable to dump linker args: {s}", .{errorDescription(er)});803 return d.fatal("unable to dump linker args: {s}", .{errorDescription(er)});
804 };804 };
805 }805 }
806 var child = std.ChildProcess.init(argv.items, d.comp.gpa);806 var child = std.process.Child.init(argv.items, d.comp.gpa);
807 // TODO handle better807 // TODO handle better
808 child.stdin_behavior = .Inherit;808 child.stdin_behavior = .Inherit;
809 child.stdout_behavior = .Inherit;809 child.stdout_behavior = .Inherit;
lib/compiler/std-docs.zig+2-2
...@@ -302,7 +302,7 @@ fn buildWasmBinary(...@@ -302,7 +302,7 @@ fn buildWasmBinary(
302 "--listen=-",302 "--listen=-",
303 });303 });
304304
305 var child = std.ChildProcess.init(argv.items, gpa);305 var child = std.process.Child.init(argv.items, gpa);
306 child.stdin_behavior = .Pipe;306 child.stdin_behavior = .Pipe;
307 child.stdout_behavior = .Pipe;307 child.stdout_behavior = .Pipe;
308 child.stderr_behavior = .Pipe;308 child.stderr_behavior = .Pipe;
...@@ -437,7 +437,7 @@ fn openBrowserTabThread(gpa: Allocator, url: []const u8) !void {...@@ -437,7 +437,7 @@ fn openBrowserTabThread(gpa: Allocator, url: []const u8) !void {
437 .macos => "open",437 .macos => "open",
438 else => "xdg-open",438 else => "xdg-open",
439 };439 };
440 var child = std.ChildProcess.init(&.{ main_exe, url }, gpa);440 var child = std.process.Child.init(&.{ main_exe, url }, gpa);
441 child.stdin_behavior = .Ignore;441 child.stdin_behavior = .Ignore;
442 child.stdout_behavior = .Ignore;442 child.stdout_behavior = .Ignore;
443 child.stderr_behavior = .Ignore;443 child.stderr_behavior = .Ignore;
lib/std/Build.zig+3-3
...@@ -178,7 +178,7 @@ pub const RunError = error{...@@ -178,7 +178,7 @@ pub const RunError = error{
178 ExitCodeFailure,178 ExitCodeFailure,
179 ProcessTerminated,179 ProcessTerminated,
180 ExecNotSupported,180 ExecNotSupported,
181} || std.ChildProcess.SpawnError;181} || std.process.Child.SpawnError;
182182
183pub const PkgConfigError = error{183pub const PkgConfigError = error{
184 PkgConfigCrashed,184 PkgConfigCrashed,
...@@ -1719,7 +1719,7 @@ pub fn runAllowFail(...@@ -1719,7 +1719,7 @@ pub fn runAllowFail(
1719 b: *Build,1719 b: *Build,
1720 argv: []const []const u8,1720 argv: []const []const u8,
1721 out_code: *u8,1721 out_code: *u8,
1722 stderr_behavior: std.ChildProcess.StdIo,1722 stderr_behavior: std.process.Child.StdIo,
1723) RunError![]u8 {1723) RunError![]u8 {
1724 assert(argv.len != 0);1724 assert(argv.len != 0);
17251725
...@@ -1727,7 +1727,7 @@ pub fn runAllowFail(...@@ -1727,7 +1727,7 @@ pub fn runAllowFail(
1727 return error.ExecNotSupported;1727 return error.ExecNotSupported;
17281728
1729 const max_output_size = 400 * 1024;1729 const max_output_size = 400 * 1024;
1730 var child = std.ChildProcess.init(argv, b.allocator);1730 var child = std.process.Child.init(argv, b.allocator);
1731 child.stdin_behavior = .Ignore;1731 child.stdin_behavior = .Ignore;
1732 child.stdout_behavior = .Pipe;1732 child.stdout_behavior = .Pipe;
1733 child.stderr_behavior = stderr_behavior;1733 child.stderr_behavior = stderr_behavior;
lib/std/Build/Step.zig+3-3
...@@ -275,7 +275,7 @@ pub fn evalChildProcess(s: *Step, argv: []const []const u8) !void {...@@ -275,7 +275,7 @@ pub fn evalChildProcess(s: *Step, argv: []const []const u8) !void {
275 try handleChildProcUnsupported(s, null, argv);275 try handleChildProcUnsupported(s, null, argv);
276 try handleVerbose(s.owner, null, argv);276 try handleVerbose(s.owner, null, argv);
277277
278 const result = std.ChildProcess.run(.{278 const result = std.process.Child.run(.{
279 .allocator = arena,279 .allocator = arena,
280 .argv = argv,280 .argv = argv,
281 }) catch |err| return s.fail("unable to spawn {s}: {s}", .{ argv[0], @errorName(err) });281 }) catch |err| return s.fail("unable to spawn {s}: {s}", .{ argv[0], @errorName(err) });
...@@ -313,7 +313,7 @@ pub fn evalZigProcess(...@@ -313,7 +313,7 @@ pub fn evalZigProcess(
313 try handleChildProcUnsupported(s, null, argv);313 try handleChildProcUnsupported(s, null, argv);
314 try handleVerbose(s.owner, null, argv);314 try handleVerbose(s.owner, null, argv);
315315
316 var child = std.ChildProcess.init(argv, arena);316 var child = std.process.Child.init(argv, arena);
317 child.env_map = &b.graph.env_map;317 child.env_map = &b.graph.env_map;
318 child.stdin_behavior = .Pipe;318 child.stdin_behavior = .Pipe;
319 child.stdout_behavior = .Pipe;319 child.stdout_behavior = .Pipe;
...@@ -480,7 +480,7 @@ pub inline fn handleChildProcUnsupported(...@@ -480,7 +480,7 @@ pub inline fn handleChildProcUnsupported(
480480
481pub fn handleChildProcessTerm(481pub fn handleChildProcessTerm(
482 s: *Step,482 s: *Step,
483 term: std.ChildProcess.Term,483 term: std.process.Child.Term,
484 opt_cwd: ?[]const u8,484 opt_cwd: ?[]const u8,
485 argv: []const []const u8,485 argv: []const []const u8,
486) error{ MakeFailed, OutOfMemory }!void {486) error{ MakeFailed, OutOfMemory }!void {
lib/std/child_process.zig deleted-1880
...@@ -1,1880 +0,0 @@
1const std = @import("std.zig");
2const builtin = @import("builtin");
3const unicode = std.unicode;
4const io = std.io;
5const fs = std.fs;
6const process = std.process;
7const File = std.fs.File;
8const windows = std.os.windows;
9const linux = std.os.linux;
10const posix = std.posix;
11const mem = std.mem;
12const math = std.math;
13const debug = std.debug;
14const EnvMap = process.EnvMap;
15const maxInt = std.math.maxInt;
16const assert = std.debug.assert;
17const native_os = builtin.os.tag;
18
19pub const ChildProcess = struct {
20 pub const Id = switch (native_os) {
21 .windows => windows.HANDLE,
22 .wasi => void,
23 else => posix.pid_t,
24 };
25
26 /// Available after calling `spawn()`. This becomes `undefined` after calling `wait()`.
27 /// On Windows this is the hProcess.
28 /// On POSIX this is the pid.
29 id: Id,
30 thread_handle: if (native_os == .windows) windows.HANDLE else void,
31
32 allocator: mem.Allocator,
33
34 /// The writing end of the child process's standard input pipe.
35 /// Usage requires `stdin_behavior == StdIo.Pipe`.
36 /// Available after calling `spawn()`.
37 stdin: ?File,
38
39 /// The reading end of the child process's standard output pipe.
40 /// Usage requires `stdout_behavior == StdIo.Pipe`.
41 /// Available after calling `spawn()`.
42 stdout: ?File,
43
44 /// The reading end of the child process's standard error pipe.
45 /// Usage requires `stderr_behavior == StdIo.Pipe`.
46 /// Available after calling `spawn()`.
47 stderr: ?File,
48
49 /// Terminated state of the child process.
50 /// Available after calling `wait()`.
51 term: ?(SpawnError!Term),
52
53 argv: []const []const u8,
54
55 /// Leave as null to use the current env map using the supplied allocator.
56 env_map: ?*const EnvMap,
57
58 stdin_behavior: StdIo,
59 stdout_behavior: StdIo,
60 stderr_behavior: StdIo,
61
62 /// Set to change the user id when spawning the child process.
63 uid: if (native_os == .windows or native_os == .wasi) void else ?posix.uid_t,
64
65 /// Set to change the group id when spawning the child process.
66 gid: if (native_os == .windows or native_os == .wasi) void else ?posix.gid_t,
67
68 /// Set to change the current working directory when spawning the child process.
69 cwd: ?[]const u8,
70 /// Set to change the current working directory when spawning the child process.
71 /// This is not yet implemented for Windows. See https://github.com/ziglang/zig/issues/5190
72 /// Once that is done, `cwd` will be deprecated in favor of this field.
73 cwd_dir: ?fs.Dir = null,
74
75 err_pipe: ?if (native_os == .windows) void else [2]posix.fd_t,
76
77 expand_arg0: Arg0Expand,
78
79 /// Darwin-only. Disable ASLR for the child process.
80 disable_aslr: bool = false,
81
82 /// Darwin-only. Start child process in suspended state as if SIGSTOP was sent.
83 start_suspended: bool = false,
84
85 /// Set to true to obtain rusage information for the child process.
86 /// Depending on the target platform and implementation status, the
87 /// requested statistics may or may not be available. If they are
88 /// available, then the `resource_usage_statistics` field will be populated
89 /// after calling `wait`.
90 /// On Linux and Darwin, this obtains rusage statistics from wait4().
91 request_resource_usage_statistics: bool = false,
92
93 /// This is available after calling wait if
94 /// `request_resource_usage_statistics` was set to `true` before calling
95 /// `spawn`.
96 resource_usage_statistics: ResourceUsageStatistics = .{},
97
98 pub const ResourceUsageStatistics = struct {
99 rusage: @TypeOf(rusage_init) = rusage_init,
100
101 /// Returns the peak resident set size of the child process, in bytes,
102 /// if available.
103 pub inline fn getMaxRss(rus: ResourceUsageStatistics) ?usize {
104 switch (native_os) {
105 .linux => {
106 if (rus.rusage) |ru| {
107 return @as(usize, @intCast(ru.maxrss)) * 1024;
108 } else {
109 return null;
110 }
111 },
112 .windows => {
113 if (rus.rusage) |ru| {
114 return ru.PeakWorkingSetSize;
115 } else {
116 return null;
117 }
118 },
119 .macos, .ios => {
120 if (rus.rusage) |ru| {
121 // Darwin oddly reports in bytes instead of kilobytes.
122 return @as(usize, @intCast(ru.maxrss));
123 } else {
124 return null;
125 }
126 },
127 else => return null,
128 }
129 }
130
131 const rusage_init = switch (native_os) {
132 .linux, .macos, .ios => @as(?posix.rusage, null),
133 .windows => @as(?windows.VM_COUNTERS, null),
134 else => {},
135 };
136 };
137
138 pub const Arg0Expand = posix.Arg0Expand;
139
140 pub const SpawnError = error{
141 OutOfMemory,
142
143 /// POSIX-only. `StdIo.Ignore` was selected and opening `/dev/null` returned ENODEV.
144 NoDevice,
145
146 /// Windows-only. `cwd` or `argv` was provided and it was invalid WTF-8.
147 /// https://simonsapin.github.io/wtf-8/
148 InvalidWtf8,
149
150 /// Windows-only. `cwd` was provided, but the path did not exist when spawning the child process.
151 CurrentWorkingDirectoryUnlinked,
152
153 /// Windows-only. NUL (U+0000), LF (U+000A), CR (U+000D) are not allowed
154 /// within arguments when executing a `.bat`/`.cmd` script.
155 /// - NUL/LF signifiies end of arguments, so anything afterwards
156 /// would be lost after execution.
157 /// - CR is stripped by `cmd.exe`, so any CR codepoints
158 /// would be lost after execution.
159 InvalidBatchScriptArg,
160 } ||
161 posix.ExecveError ||
162 posix.SetIdError ||
163 posix.ChangeCurDirError ||
164 windows.CreateProcessError ||
165 windows.GetProcessMemoryInfoError ||
166 windows.WaitForSingleObjectError;
167
168 pub const Term = union(enum) {
169 Exited: u8,
170 Signal: u32,
171 Stopped: u32,
172 Unknown: u32,
173 };
174
175 /// Behavior of the child process's standard input, output, and error
176 /// streams.
177 pub const StdIo = enum {
178 /// Inherit the stream from the parent process.
179 Inherit,
180
181 /// Pass a null stream to the child process.
182 /// This is /dev/null on POSIX and NUL on Windows.
183 Ignore,
184
185 /// Create a pipe for the stream.
186 /// The corresponding field (`stdout`, `stderr`, or `stdin`)
187 /// will be assigned a `File` object that can be used
188 /// to read from or write to the pipe.
189 Pipe,
190
191 /// Close the stream after the child process spawns.
192 Close,
193 };
194
195 /// First argument in argv is the executable.
196 pub fn init(argv: []const []const u8, allocator: mem.Allocator) ChildProcess {
197 return .{
198 .allocator = allocator,
199 .argv = argv,
200 .id = undefined,
201 .thread_handle = undefined,
202 .err_pipe = null,
203 .term = null,
204 .env_map = null,
205 .cwd = null,
206 .uid = if (native_os == .windows or native_os == .wasi) {} else null,
207 .gid = if (native_os == .windows or native_os == .wasi) {} else null,
208 .stdin = null,
209 .stdout = null,
210 .stderr = null,
211 .stdin_behavior = StdIo.Inherit,
212 .stdout_behavior = StdIo.Inherit,
213 .stderr_behavior = StdIo.Inherit,
214 .expand_arg0 = .no_expand,
215 };
216 }
217
218 pub fn setUserName(self: *ChildProcess, name: []const u8) !void {
219 const user_info = try std.process.getUserInfo(name);
220 self.uid = user_info.uid;
221 self.gid = user_info.gid;
222 }
223
224 /// On success must call `kill` or `wait`.
225 /// After spawning the `id` is available.
226 pub fn spawn(self: *ChildProcess) SpawnError!void {
227 if (!std.process.can_spawn) {
228 @compileError("the target operating system cannot spawn processes");
229 }
230
231 if (native_os == .windows) {
232 return self.spawnWindows();
233 } else {
234 return self.spawnPosix();
235 }
236 }
237
238 pub fn spawnAndWait(self: *ChildProcess) SpawnError!Term {
239 try self.spawn();
240 return self.wait();
241 }
242
243 /// Forcibly terminates child process and then cleans up all resources.
244 pub fn kill(self: *ChildProcess) !Term {
245 if (native_os == .windows) {
246 return self.killWindows(1);
247 } else {
248 return self.killPosix();
249 }
250 }
251
252 pub fn killWindows(self: *ChildProcess, exit_code: windows.UINT) !Term {
253 if (self.term) |term| {
254 self.cleanupStreams();
255 return term;
256 }
257
258 windows.TerminateProcess(self.id, exit_code) catch |err| switch (err) {
259 error.PermissionDenied => {
260 // Usually when TerminateProcess triggers a ACCESS_DENIED error, it
261 // indicates that the process has already exited, but there may be
262 // some rare edge cases where our process handle no longer has the
263 // PROCESS_TERMINATE access right, so let's do another check to make
264 // sure the process is really no longer running:
265 windows.WaitForSingleObjectEx(self.id, 0, false) catch return err;
266 return error.AlreadyTerminated;
267 },
268 else => return err,
269 };
270 try self.waitUnwrappedWindows();
271 return self.term.?;
272 }
273
274 pub fn killPosix(self: *ChildProcess) !Term {
275 if (self.term) |term| {
276 self.cleanupStreams();
277 return term;
278 }
279 posix.kill(self.id, posix.SIG.TERM) catch |err| switch (err) {
280 error.ProcessNotFound => return error.AlreadyTerminated,
281 else => return err,
282 };
283 try self.waitUnwrapped();
284 return self.term.?;
285 }
286
287 /// Blocks until child process terminates and then cleans up all resources.
288 pub fn wait(self: *ChildProcess) !Term {
289 const term = if (native_os == .windows)
290 try self.waitWindows()
291 else
292 try self.waitPosix();
293
294 self.id = undefined;
295
296 return term;
297 }
298
299 pub const RunResult = struct {
300 term: Term,
301 stdout: []u8,
302 stderr: []u8,
303 };
304
305 fn fifoToOwnedArrayList(fifo: *std.io.PollFifo) std.ArrayList(u8) {
306 if (fifo.head > 0) {
307 @memcpy(fifo.buf[0..fifo.count], fifo.buf[fifo.head..][0..fifo.count]);
308 }
309 const result = std.ArrayList(u8){
310 .items = fifo.buf[0..fifo.count],
311 .capacity = fifo.buf.len,
312 .allocator = fifo.allocator,
313 };
314 fifo.* = std.io.PollFifo.init(fifo.allocator);
315 return result;
316 }
317
318 /// Collect the output from the process's stdout and stderr. Will return once all output
319 /// has been collected. This does not mean that the process has ended. `wait` should still
320 /// be called to wait for and clean up the process.
321 ///
322 /// The process must be started with stdout_behavior and stderr_behavior == .Pipe
323 pub fn collectOutput(
324 child: ChildProcess,
325 stdout: *std.ArrayList(u8),
326 stderr: *std.ArrayList(u8),
327 max_output_bytes: usize,
328 ) !void {
329 debug.assert(child.stdout_behavior == .Pipe);
330 debug.assert(child.stderr_behavior == .Pipe);
331
332 // we could make this work with multiple allocators but YAGNI
333 if (stdout.allocator.ptr != stderr.allocator.ptr or
334 stdout.allocator.vtable != stderr.allocator.vtable)
335 {
336 unreachable; // ChildProcess.collectOutput only supports 1 allocator
337 }
338
339 var poller = std.io.poll(stdout.allocator, enum { stdout, stderr }, .{
340 .stdout = child.stdout.?,
341 .stderr = child.stderr.?,
342 });
343 defer poller.deinit();
344
345 while (try poller.poll()) {
346 if (poller.fifo(.stdout).count > max_output_bytes)
347 return error.StdoutStreamTooLong;
348 if (poller.fifo(.stderr).count > max_output_bytes)
349 return error.StderrStreamTooLong;
350 }
351
352 stdout.* = fifoToOwnedArrayList(poller.fifo(.stdout));
353 stderr.* = fifoToOwnedArrayList(poller.fifo(.stderr));
354 }
355
356 pub const RunError = posix.GetCwdError || posix.ReadError || SpawnError || posix.PollError || error{
357 StdoutStreamTooLong,
358 StderrStreamTooLong,
359 };
360
361 /// Spawns a child process, waits for it, collecting stdout and stderr, and then returns.
362 /// If it succeeds, the caller owns result.stdout and result.stderr memory.
363 pub fn run(args: struct {
364 allocator: mem.Allocator,
365 argv: []const []const u8,
366 cwd: ?[]const u8 = null,
367 cwd_dir: ?fs.Dir = null,
368 env_map: ?*const EnvMap = null,
369 max_output_bytes: usize = 50 * 1024,
370 expand_arg0: Arg0Expand = .no_expand,
371 }) RunError!RunResult {
372 var child = ChildProcess.init(args.argv, args.allocator);
373 child.stdin_behavior = .Ignore;
374 child.stdout_behavior = .Pipe;
375 child.stderr_behavior = .Pipe;
376 child.cwd = args.cwd;
377 child.cwd_dir = args.cwd_dir;
378 child.env_map = args.env_map;
379 child.expand_arg0 = args.expand_arg0;
380
381 var stdout = std.ArrayList(u8).init(args.allocator);
382 var stderr = std.ArrayList(u8).init(args.allocator);
383 errdefer {
384 stdout.deinit();
385 stderr.deinit();
386 }
387
388 try child.spawn();
389 try child.collectOutput(&stdout, &stderr, args.max_output_bytes);
390
391 return RunResult{
392 .term = try child.wait(),
393 .stdout = try stdout.toOwnedSlice(),
394 .stderr = try stderr.toOwnedSlice(),
395 };
396 }
397
398 fn waitWindows(self: *ChildProcess) !Term {
399 if (self.term) |term| {
400 self.cleanupStreams();
401 return term;
402 }
403
404 try self.waitUnwrappedWindows();
405 return self.term.?;
406 }
407
408 fn waitPosix(self: *ChildProcess) !Term {
409 if (self.term) |term| {
410 self.cleanupStreams();
411 return term;
412 }
413
414 try self.waitUnwrapped();
415 return self.term.?;
416 }
417
418 fn waitUnwrappedWindows(self: *ChildProcess) !void {
419 const result = windows.WaitForSingleObjectEx(self.id, windows.INFINITE, false);
420
421 self.term = @as(SpawnError!Term, x: {
422 var exit_code: windows.DWORD = undefined;
423 if (windows.kernel32.GetExitCodeProcess(self.id, &exit_code) == 0) {
424 break :x Term{ .Unknown = 0 };
425 } else {
426 break :x Term{ .Exited = @as(u8, @truncate(exit_code)) };
427 }
428 });
429
430 if (self.request_resource_usage_statistics) {
431 self.resource_usage_statistics.rusage = try windows.GetProcessMemoryInfo(self.id);
432 }
433
434 posix.close(self.id);
435 posix.close(self.thread_handle);
436 self.cleanupStreams();
437 return result;
438 }
439
440 fn waitUnwrapped(self: *ChildProcess) !void {
441 const res: posix.WaitPidResult = res: {
442 if (self.request_resource_usage_statistics) {
443 switch (native_os) {
444 .linux, .macos, .ios => {
445 var ru: posix.rusage = undefined;
446 const res = posix.wait4(self.id, 0, &ru);
447 self.resource_usage_statistics.rusage = ru;
448 break :res res;
449 },
450 else => {},
451 }
452 }
453
454 break :res posix.waitpid(self.id, 0);
455 };
456 const status = res.status;
457 self.cleanupStreams();
458 self.handleWaitResult(status);
459 }
460
461 fn handleWaitResult(self: *ChildProcess, status: u32) void {
462 self.term = self.cleanupAfterWait(status);
463 }
464
465 fn cleanupStreams(self: *ChildProcess) void {
466 if (self.stdin) |*stdin| {
467 stdin.close();
468 self.stdin = null;
469 }
470 if (self.stdout) |*stdout| {
471 stdout.close();
472 self.stdout = null;
473 }
474 if (self.stderr) |*stderr| {
475 stderr.close();
476 self.stderr = null;
477 }
478 }
479
480 fn cleanupAfterWait(self: *ChildProcess, status: u32) !Term {
481 if (self.err_pipe) |err_pipe| {
482 defer destroyPipe(err_pipe);
483
484 if (native_os == .linux) {
485 var fd = [1]posix.pollfd{posix.pollfd{
486 .fd = err_pipe[0],
487 .events = posix.POLL.IN,
488 .revents = undefined,
489 }};
490
491 // Check if the eventfd buffer stores a non-zero value by polling
492 // it, that's the error code returned by the child process.
493 _ = posix.poll(&fd, 0) catch unreachable;
494
495 // According to eventfd(2) the descriptor is readable if the counter
496 // has a value greater than 0
497 if ((fd[0].revents & posix.POLL.IN) != 0) {
498 const err_int = try readIntFd(err_pipe[0]);
499 return @as(SpawnError, @errorCast(@errorFromInt(err_int)));
500 }
501 } else {
502 // Write maxInt(ErrInt) to the write end of the err_pipe. This is after
503 // waitpid, so this write is guaranteed to be after the child
504 // pid potentially wrote an error. This way we can do a blocking
505 // read on the error pipe and either get maxInt(ErrInt) (no error) or
506 // an error code.
507 try writeIntFd(err_pipe[1], maxInt(ErrInt));
508 const err_int = try readIntFd(err_pipe[0]);
509 // Here we potentially return the fork child's error from the parent
510 // pid.
511 if (err_int != maxInt(ErrInt)) {
512 return @as(SpawnError, @errorCast(@errorFromInt(err_int)));
513 }
514 }
515 }
516
517 return statusToTerm(status);
518 }
519
520 fn statusToTerm(status: u32) Term {
521 return if (posix.W.IFEXITED(status))
522 Term{ .Exited = posix.W.EXITSTATUS(status) }
523 else if (posix.W.IFSIGNALED(status))
524 Term{ .Signal = posix.W.TERMSIG(status) }
525 else if (posix.W.IFSTOPPED(status))
526 Term{ .Stopped = posix.W.STOPSIG(status) }
527 else
528 Term{ .Unknown = status };
529 }
530
531 fn spawnPosix(self: *ChildProcess) SpawnError!void {
532 const pipe_flags: posix.O = .{};
533 const stdin_pipe = if (self.stdin_behavior == StdIo.Pipe) try posix.pipe2(pipe_flags) else undefined;
534 errdefer if (self.stdin_behavior == StdIo.Pipe) {
535 destroyPipe(stdin_pipe);
536 };
537
538 const stdout_pipe = if (self.stdout_behavior == StdIo.Pipe) try posix.pipe2(pipe_flags) else undefined;
539 errdefer if (self.stdout_behavior == StdIo.Pipe) {
540 destroyPipe(stdout_pipe);
541 };
542
543 const stderr_pipe = if (self.stderr_behavior == StdIo.Pipe) try posix.pipe2(pipe_flags) else undefined;
544 errdefer if (self.stderr_behavior == StdIo.Pipe) {
545 destroyPipe(stderr_pipe);
546 };
547
548 const any_ignore = (self.stdin_behavior == StdIo.Ignore or self.stdout_behavior == StdIo.Ignore or self.stderr_behavior == StdIo.Ignore);
549 const dev_null_fd = if (any_ignore)
550 posix.openZ("/dev/null", .{ .ACCMODE = .RDWR }, 0) catch |err| switch (err) {
551 error.PathAlreadyExists => unreachable,
552 error.NoSpaceLeft => unreachable,
553 error.FileTooBig => unreachable,
554 error.DeviceBusy => unreachable,
555 error.FileLocksNotSupported => unreachable,
556 error.BadPathName => unreachable, // Windows-only
557 error.WouldBlock => unreachable,
558 error.NetworkNotFound => unreachable, // Windows-only
559 else => |e| return e,
560 }
561 else
562 undefined;
563 defer {
564 if (any_ignore) posix.close(dev_null_fd);
565 }
566
567 var arena_allocator = std.heap.ArenaAllocator.init(self.allocator);
568 defer arena_allocator.deinit();
569 const arena = arena_allocator.allocator();
570
571 // The POSIX standard does not allow malloc() between fork() and execve(),
572 // and `self.allocator` may be a libc allocator.
573 // I have personally observed the child process deadlocking when it tries
574 // to call malloc() due to a heap allocation between fork() and execve(),
575 // in musl v1.1.24.
576 // Additionally, we want to reduce the number of possible ways things
577 // can fail between fork() and execve().
578 // Therefore, we do all the allocation for the execve() before the fork().
579 // This means we must do the null-termination of argv and env vars here.
580 const argv_buf = try arena.allocSentinel(?[*:0]const u8, self.argv.len, null);
581 for (self.argv, 0..) |arg, i| argv_buf[i] = (try arena.dupeZ(u8, arg)).ptr;
582
583 const envp = m: {
584 if (self.env_map) |env_map| {
585 const envp_buf = try createNullDelimitedEnvMap(arena, env_map);
586 break :m envp_buf.ptr;
587 } else if (builtin.link_libc) {
588 break :m std.c.environ;
589 } else if (builtin.output_mode == .Exe) {
590 // Then we have Zig start code and this works.
591 // TODO type-safety for null-termination of `os.environ`.
592 break :m @as([*:null]const ?[*:0]const u8, @ptrCast(std.os.environ.ptr));
593 } else {
594 // TODO come up with a solution for this.
595 @compileError("missing std lib enhancement: ChildProcess implementation has no way to collect the environment variables to forward to the child process");
596 }
597 };
598
599 // This pipe is used to communicate errors between the time of fork
600 // and execve from the child process to the parent process.
601 const err_pipe = blk: {
602 if (native_os == .linux) {
603 const fd = try posix.eventfd(0, linux.EFD.CLOEXEC);
604 // There's no distinction between the readable and the writeable
605 // end with eventfd
606 break :blk [2]posix.fd_t{ fd, fd };
607 } else {
608 break :blk try posix.pipe2(.{ .CLOEXEC = true });
609 }
610 };
611 errdefer destroyPipe(err_pipe);
612
613 const pid_result = try posix.fork();
614 if (pid_result == 0) {
615 // we are the child
616 setUpChildIo(self.stdin_behavior, stdin_pipe[0], posix.STDIN_FILENO, dev_null_fd) catch |err| forkChildErrReport(err_pipe[1], err);
617 setUpChildIo(self.stdout_behavior, stdout_pipe[1], posix.STDOUT_FILENO, dev_null_fd) catch |err| forkChildErrReport(err_pipe[1], err);
618 setUpChildIo(self.stderr_behavior, stderr_pipe[1], posix.STDERR_FILENO, dev_null_fd) catch |err| forkChildErrReport(err_pipe[1], err);
619
620 if (self.stdin_behavior == .Pipe) {
621 posix.close(stdin_pipe[0]);
622 posix.close(stdin_pipe[1]);
623 }
624 if (self.stdout_behavior == .Pipe) {
625 posix.close(stdout_pipe[0]);
626 posix.close(stdout_pipe[1]);
627 }
628 if (self.stderr_behavior == .Pipe) {
629 posix.close(stderr_pipe[0]);
630 posix.close(stderr_pipe[1]);
631 }
632
633 if (self.cwd_dir) |cwd| {
634 posix.fchdir(cwd.fd) catch |err| forkChildErrReport(err_pipe[1], err);
635 } else if (self.cwd) |cwd| {
636 posix.chdir(cwd) catch |err| forkChildErrReport(err_pipe[1], err);
637 }
638
639 if (self.gid) |gid| {
640 posix.setregid(gid, gid) catch |err| forkChildErrReport(err_pipe[1], err);
641 }
642
643 if (self.uid) |uid| {
644 posix.setreuid(uid, uid) catch |err| forkChildErrReport(err_pipe[1], err);
645 }
646
647 const err = switch (self.expand_arg0) {
648 .expand => posix.execvpeZ_expandArg0(.expand, argv_buf.ptr[0].?, argv_buf.ptr, envp),
649 .no_expand => posix.execvpeZ_expandArg0(.no_expand, argv_buf.ptr[0].?, argv_buf.ptr, envp),
650 };
651 forkChildErrReport(err_pipe[1], err);
652 }
653
654 // we are the parent
655 const pid: i32 = @intCast(pid_result);
656 if (self.stdin_behavior == StdIo.Pipe) {
657 self.stdin = File{ .handle = stdin_pipe[1] };
658 } else {
659 self.stdin = null;
660 }
661 if (self.stdout_behavior == StdIo.Pipe) {
662 self.stdout = File{ .handle = stdout_pipe[0] };
663 } else {
664 self.stdout = null;
665 }
666 if (self.stderr_behavior == StdIo.Pipe) {
667 self.stderr = File{ .handle = stderr_pipe[0] };
668 } else {
669 self.stderr = null;
670 }
671
672 self.id = pid;
673 self.err_pipe = err_pipe;
674 self.term = null;
675
676 if (self.stdin_behavior == StdIo.Pipe) {
677 posix.close(stdin_pipe[0]);
678 }
679 if (self.stdout_behavior == StdIo.Pipe) {
680 posix.close(stdout_pipe[1]);
681 }
682 if (self.stderr_behavior == StdIo.Pipe) {
683 posix.close(stderr_pipe[1]);
684 }
685 }
686
687 fn spawnWindows(self: *ChildProcess) SpawnError!void {
688 var saAttr = windows.SECURITY_ATTRIBUTES{
689 .nLength = @sizeOf(windows.SECURITY_ATTRIBUTES),
690 .bInheritHandle = windows.TRUE,
691 .lpSecurityDescriptor = null,
692 };
693
694 const any_ignore = (self.stdin_behavior == StdIo.Ignore or self.stdout_behavior == StdIo.Ignore or self.stderr_behavior == StdIo.Ignore);
695
696 const nul_handle = if (any_ignore)
697 // "\Device\Null" or "\??\NUL"
698 windows.OpenFile(&[_]u16{ '\\', 'D', 'e', 'v', 'i', 'c', 'e', '\\', 'N', 'u', 'l', 'l' }, .{
699 .access_mask = windows.GENERIC_READ | windows.GENERIC_WRITE | windows.SYNCHRONIZE,
700 .share_access = windows.FILE_SHARE_READ | windows.FILE_SHARE_WRITE | windows.FILE_SHARE_DELETE,
701 .sa = &saAttr,
702 .creation = windows.OPEN_EXISTING,
703 }) catch |err| switch (err) {
704 error.PathAlreadyExists => return error.Unexpected, // not possible for "NUL"
705 error.PipeBusy => return error.Unexpected, // not possible for "NUL"
706 error.FileNotFound => return error.Unexpected, // not possible for "NUL"
707 error.AccessDenied => return error.Unexpected, // not possible for "NUL"
708 error.NameTooLong => return error.Unexpected, // not possible for "NUL"
709 error.WouldBlock => return error.Unexpected, // not possible for "NUL"
710 error.NetworkNotFound => return error.Unexpected, // not possible for "NUL"
711 error.AntivirusInterference => return error.Unexpected, // not possible for "NUL"
712 else => |e| return e,
713 }
714 else
715 undefined;
716 defer {
717 if (any_ignore) posix.close(nul_handle);
718 }
719
720 var g_hChildStd_IN_Rd: ?windows.HANDLE = null;
721 var g_hChildStd_IN_Wr: ?windows.HANDLE = null;
722 switch (self.stdin_behavior) {
723 StdIo.Pipe => {
724 try windowsMakePipeIn(&g_hChildStd_IN_Rd, &g_hChildStd_IN_Wr, &saAttr);
725 },
726 StdIo.Ignore => {
727 g_hChildStd_IN_Rd = nul_handle;
728 },
729 StdIo.Inherit => {
730 g_hChildStd_IN_Rd = windows.GetStdHandle(windows.STD_INPUT_HANDLE) catch null;
731 },
732 StdIo.Close => {
733 g_hChildStd_IN_Rd = null;
734 },
735 }
736 errdefer if (self.stdin_behavior == StdIo.Pipe) {
737 windowsDestroyPipe(g_hChildStd_IN_Rd, g_hChildStd_IN_Wr);
738 };
739
740 var g_hChildStd_OUT_Rd: ?windows.HANDLE = null;
741 var g_hChildStd_OUT_Wr: ?windows.HANDLE = null;
742 switch (self.stdout_behavior) {
743 StdIo.Pipe => {
744 try windowsMakeAsyncPipe(&g_hChildStd_OUT_Rd, &g_hChildStd_OUT_Wr, &saAttr);
745 },
746 StdIo.Ignore => {
747 g_hChildStd_OUT_Wr = nul_handle;
748 },
749 StdIo.Inherit => {
750 g_hChildStd_OUT_Wr = windows.GetStdHandle(windows.STD_OUTPUT_HANDLE) catch null;
751 },
752 StdIo.Close => {
753 g_hChildStd_OUT_Wr = null;
754 },
755 }
756 errdefer if (self.stdout_behavior == StdIo.Pipe) {
757 windowsDestroyPipe(g_hChildStd_OUT_Rd, g_hChildStd_OUT_Wr);
758 };
759
760 var g_hChildStd_ERR_Rd: ?windows.HANDLE = null;
761 var g_hChildStd_ERR_Wr: ?windows.HANDLE = null;
762 switch (self.stderr_behavior) {
763 StdIo.Pipe => {
764 try windowsMakeAsyncPipe(&g_hChildStd_ERR_Rd, &g_hChildStd_ERR_Wr, &saAttr);
765 },
766 StdIo.Ignore => {
767 g_hChildStd_ERR_Wr = nul_handle;
768 },
769 StdIo.Inherit => {
770 g_hChildStd_ERR_Wr = windows.GetStdHandle(windows.STD_ERROR_HANDLE) catch null;
771 },
772 StdIo.Close => {
773 g_hChildStd_ERR_Wr = null;
774 },
775 }
776 errdefer if (self.stderr_behavior == StdIo.Pipe) {
777 windowsDestroyPipe(g_hChildStd_ERR_Rd, g_hChildStd_ERR_Wr);
778 };
779
780 var siStartInfo = windows.STARTUPINFOW{
781 .cb = @sizeOf(windows.STARTUPINFOW),
782 .hStdError = g_hChildStd_ERR_Wr,
783 .hStdOutput = g_hChildStd_OUT_Wr,
784 .hStdInput = g_hChildStd_IN_Rd,
785 .dwFlags = windows.STARTF_USESTDHANDLES,
786
787 .lpReserved = null,
788 .lpDesktop = null,
789 .lpTitle = null,
790 .dwX = 0,
791 .dwY = 0,
792 .dwXSize = 0,
793 .dwYSize = 0,
794 .dwXCountChars = 0,
795 .dwYCountChars = 0,
796 .dwFillAttribute = 0,
797 .wShowWindow = 0,
798 .cbReserved2 = 0,
799 .lpReserved2 = null,
800 };
801 var piProcInfo: windows.PROCESS_INFORMATION = undefined;
802
803 const cwd_w = if (self.cwd) |cwd| try unicode.wtf8ToWtf16LeAllocZ(self.allocator, cwd) else null;
804 defer if (cwd_w) |cwd| self.allocator.free(cwd);
805 const cwd_w_ptr = if (cwd_w) |cwd| cwd.ptr else null;
806
807 const maybe_envp_buf = if (self.env_map) |env_map| try createWindowsEnvBlock(self.allocator, env_map) else null;
808 defer if (maybe_envp_buf) |envp_buf| self.allocator.free(envp_buf);
809 const envp_ptr = if (maybe_envp_buf) |envp_buf| envp_buf.ptr else null;
810
811 const app_name_wtf8 = self.argv[0];
812 const app_name_is_absolute = fs.path.isAbsolute(app_name_wtf8);
813
814 // the cwd set in ChildProcess is in effect when choosing the executable path
815 // to match posix semantics
816 var cwd_path_w_needs_free = false;
817 const cwd_path_w = x: {
818 // If the app name is absolute, then we need to use its dirname as the cwd
819 if (app_name_is_absolute) {
820 cwd_path_w_needs_free = true;
821 const dir = fs.path.dirname(app_name_wtf8).?;
822 break :x try unicode.wtf8ToWtf16LeAllocZ(self.allocator, dir);
823 } else if (self.cwd) |cwd| {
824 cwd_path_w_needs_free = true;
825 break :x try unicode.wtf8ToWtf16LeAllocZ(self.allocator, cwd);
826 } else {
827 break :x &[_:0]u16{}; // empty for cwd
828 }
829 };
830 defer if (cwd_path_w_needs_free) self.allocator.free(cwd_path_w);
831
832 // If the app name has more than just a filename, then we need to separate that
833 // into the basename and dirname and use the dirname as an addition to the cwd
834 // path. This is because NtQueryDirectoryFile cannot accept FileName params with
835 // path separators.
836 const app_basename_wtf8 = fs.path.basename(app_name_wtf8);
837 // If the app name is absolute, then the cwd will already have the app's dirname in it,
838 // so only populate app_dirname if app name is a relative path with > 0 path separators.
839 const maybe_app_dirname_wtf8 = if (!app_name_is_absolute) fs.path.dirname(app_name_wtf8) else null;
840 const app_dirname_w: ?[:0]u16 = x: {
841 if (maybe_app_dirname_wtf8) |app_dirname_wtf8| {
842 break :x try unicode.wtf8ToWtf16LeAllocZ(self.allocator, app_dirname_wtf8);
843 }
844 break :x null;
845 };
846 defer if (app_dirname_w != null) self.allocator.free(app_dirname_w.?);
847
848 const app_name_w = try unicode.wtf8ToWtf16LeAllocZ(self.allocator, app_basename_wtf8);
849 defer self.allocator.free(app_name_w);
850
851 run: {
852 const PATH: [:0]const u16 = std.process.getenvW(unicode.utf8ToUtf16LeStringLiteral("PATH")) orelse &[_:0]u16{};
853 const PATHEXT: [:0]const u16 = std.process.getenvW(unicode.utf8ToUtf16LeStringLiteral("PATHEXT")) orelse &[_:0]u16{};
854
855 // In case the command ends up being a .bat/.cmd script, we need to escape things using the cmd.exe rules
856 // and invoke cmd.exe ourselves in order to mitigate arbitrary command execution from maliciously
857 // constructed arguments.
858 //
859 // We'll need to wait until we're actually trying to run the command to know for sure
860 // if the resolved command has the `.bat` or `.cmd` extension, so we defer actually
861 // serializing the command line until we determine how it should be serialized.
862 var cmd_line_cache = WindowsCommandLineCache.init(self.allocator, self.argv);
863 defer cmd_line_cache.deinit();
864
865 var app_buf = std.ArrayListUnmanaged(u16){};
866 defer app_buf.deinit(self.allocator);
867
868 try app_buf.appendSlice(self.allocator, app_name_w);
869
870 var dir_buf = std.ArrayListUnmanaged(u16){};
871 defer dir_buf.deinit(self.allocator);
872
873 if (cwd_path_w.len > 0) {
874 try dir_buf.appendSlice(self.allocator, cwd_path_w);
875 }
876 if (app_dirname_w) |app_dir| {
877 if (dir_buf.items.len > 0) try dir_buf.append(self.allocator, fs.path.sep);
878 try dir_buf.appendSlice(self.allocator, app_dir);
879 }
880 if (dir_buf.items.len > 0) {
881 // Need to normalize the path, openDirW can't handle things like double backslashes
882 const normalized_len = windows.normalizePath(u16, dir_buf.items) catch return error.BadPathName;
883 dir_buf.shrinkRetainingCapacity(normalized_len);
884 }
885
886 windowsCreateProcessPathExt(self.allocator, &dir_buf, &app_buf, PATHEXT, &cmd_line_cache, envp_ptr, cwd_w_ptr, &siStartInfo, &piProcInfo) catch |no_path_err| {
887 const original_err = switch (no_path_err) {
888 // argv[0] contains unsupported characters that will never resolve to a valid exe.
889 error.InvalidArg0 => return error.FileNotFound,
890 error.FileNotFound, error.InvalidExe, error.AccessDenied => |e| e,
891 error.UnrecoverableInvalidExe => return error.InvalidExe,
892 else => |e| return e,
893 };
894
895 // If the app name had path separators, that disallows PATH searching,
896 // and there's no need to search the PATH if the app name is absolute.
897 // We still search the path if the cwd is absolute because of the
898 // "cwd set in ChildProcess is in effect when choosing the executable path
899 // to match posix semantics" behavior--we don't want to skip searching
900 // the PATH just because we were trying to set the cwd of the child process.
901 if (app_dirname_w != null or app_name_is_absolute) {
902 return original_err;
903 }
904
905 var it = mem.tokenizeScalar(u16, PATH, ';');
906 while (it.next()) |search_path| {
907 dir_buf.clearRetainingCapacity();
908 try dir_buf.appendSlice(self.allocator, search_path);
909 // Need to normalize the path, some PATH values can contain things like double
910 // backslashes which openDirW can't handle
911 const normalized_len = windows.normalizePath(u16, dir_buf.items) catch continue;
912 dir_buf.shrinkRetainingCapacity(normalized_len);
913
914 if (windowsCreateProcessPathExt(self.allocator, &dir_buf, &app_buf, PATHEXT, &cmd_line_cache, envp_ptr, cwd_w_ptr, &siStartInfo, &piProcInfo)) {
915 break :run;
916 } else |err| switch (err) {
917 // argv[0] contains unsupported characters that will never resolve to a valid exe.
918 error.InvalidArg0 => return error.FileNotFound,
919 error.FileNotFound, error.AccessDenied, error.InvalidExe => continue,
920 error.UnrecoverableInvalidExe => return error.InvalidExe,
921 else => |e| return e,
922 }
923 } else {
924 return original_err;
925 }
926 };
927 }
928
929 if (g_hChildStd_IN_Wr) |h| {
930 self.stdin = File{ .handle = h };
931 } else {
932 self.stdin = null;
933 }
934 if (g_hChildStd_OUT_Rd) |h| {
935 self.stdout = File{ .handle = h };
936 } else {
937 self.stdout = null;
938 }
939 if (g_hChildStd_ERR_Rd) |h| {
940 self.stderr = File{ .handle = h };
941 } else {
942 self.stderr = null;
943 }
944
945 self.id = piProcInfo.hProcess;
946 self.thread_handle = piProcInfo.hThread;
947 self.term = null;
948
949 if (self.stdin_behavior == StdIo.Pipe) {
950 posix.close(g_hChildStd_IN_Rd.?);
951 }
952 if (self.stderr_behavior == StdIo.Pipe) {
953 posix.close(g_hChildStd_ERR_Wr.?);
954 }
955 if (self.stdout_behavior == StdIo.Pipe) {
956 posix.close(g_hChildStd_OUT_Wr.?);
957 }
958 }
959
960 fn setUpChildIo(stdio: StdIo, pipe_fd: i32, std_fileno: i32, dev_null_fd: i32) !void {
961 switch (stdio) {
962 .Pipe => try posix.dup2(pipe_fd, std_fileno),
963 .Close => posix.close(std_fileno),
964 .Inherit => {},
965 .Ignore => try posix.dup2(dev_null_fd, std_fileno),
966 }
967 }
968};
969
970/// Expects `app_buf` to contain exactly the app name, and `dir_buf` to contain exactly the dir path.
971/// After return, `app_buf` will always contain exactly the app name and `dir_buf` will always contain exactly the dir path.
972/// Note: `app_buf` should not contain any leading path separators.
973/// Note: If the dir is the cwd, dir_buf should be empty (len = 0).
974fn windowsCreateProcessPathExt(
975 allocator: mem.Allocator,
976 dir_buf: *std.ArrayListUnmanaged(u16),
977 app_buf: *std.ArrayListUnmanaged(u16),
978 pathext: [:0]const u16,
979 cmd_line_cache: *WindowsCommandLineCache,
980 envp_ptr: ?[*]u16,
981 cwd_ptr: ?[*:0]u16,
982 lpStartupInfo: *windows.STARTUPINFOW,
983 lpProcessInformation: *windows.PROCESS_INFORMATION,
984) !void {
985 const app_name_len = app_buf.items.len;
986 const dir_path_len = dir_buf.items.len;
987
988 if (app_name_len == 0) return error.FileNotFound;
989
990 defer app_buf.shrinkRetainingCapacity(app_name_len);
991 defer dir_buf.shrinkRetainingCapacity(dir_path_len);
992
993 // The name of the game here is to avoid CreateProcessW calls at all costs,
994 // and only ever try calling it when we have a real candidate for execution.
995 // Secondarily, we want to minimize the number of syscalls used when checking
996 // for each PATHEXT-appended version of the app name.
997 //
998 // An overview of the technique used:
999 // - Open the search directory for iteration (either cwd or a path from PATH)
1000 // - Use NtQueryDirectoryFile with a wildcard filename of `<app name>*` to
1001 // check if anything that could possibly match either the unappended version
1002 // of the app name or any of the versions with a PATHEXT value appended exists.
1003 // - If the wildcard NtQueryDirectoryFile call found nothing, we can exit early
1004 // without needing to use PATHEXT at all.
1005 //
1006 // This allows us to use a <open dir, NtQueryDirectoryFile, close dir> sequence
1007 // for any directory that doesn't contain any possible matches, instead of having
1008 // to use a separate look up for each individual filename combination (unappended +
1009 // each PATHEXT appended). For directories where the wildcard *does* match something,
1010 // we iterate the matches and take note of any that are either the unappended version,
1011 // or a version with a supported PATHEXT appended. We then try calling CreateProcessW
1012 // with the found versions in the appropriate order.
1013
1014 var dir = dir: {
1015 // needs to be null-terminated
1016 try dir_buf.append(allocator, 0);
1017 defer dir_buf.shrinkRetainingCapacity(dir_path_len);
1018 const dir_path_z = dir_buf.items[0 .. dir_buf.items.len - 1 :0];
1019 const prefixed_path = try windows.wToPrefixedFileW(null, dir_path_z);
1020 break :dir fs.cwd().openDirW(prefixed_path.span().ptr, .{ .iterate = true }) catch
1021 return error.FileNotFound;
1022 };
1023 defer dir.close();
1024
1025 // Add wildcard and null-terminator
1026 try app_buf.append(allocator, '*');
1027 try app_buf.append(allocator, 0);
1028 const app_name_wildcard = app_buf.items[0 .. app_buf.items.len - 1 :0];
1029
1030 // This 2048 is arbitrary, we just want it to be large enough to get multiple FILE_DIRECTORY_INFORMATION entries
1031 // returned per NtQueryDirectoryFile call.
1032 var file_information_buf: [2048]u8 align(@alignOf(windows.FILE_DIRECTORY_INFORMATION)) = undefined;
1033 const file_info_maximum_single_entry_size = @sizeOf(windows.FILE_DIRECTORY_INFORMATION) + (windows.NAME_MAX * 2);
1034 if (file_information_buf.len < file_info_maximum_single_entry_size) {
1035 @compileError("file_information_buf must be large enough to contain at least one maximum size FILE_DIRECTORY_INFORMATION entry");
1036 }
1037 var io_status: windows.IO_STATUS_BLOCK = undefined;
1038
1039 const num_supported_pathext = @typeInfo(CreateProcessSupportedExtension).Enum.fields.len;
1040 var pathext_seen = [_]bool{false} ** num_supported_pathext;
1041 var any_pathext_seen = false;
1042 var unappended_exists = false;
1043
1044 // Fully iterate the wildcard matches via NtQueryDirectoryFile and take note of all versions
1045 // of the app_name we should try to spawn.
1046 // Note: This is necessary because the order of the files returned is filesystem-dependent:
1047 // On NTFS, `blah.exe*` will always return `blah.exe` first if it exists.
1048 // On FAT32, it's possible for something like `blah.exe.obj` to be returned first.
1049 while (true) {
1050 const app_name_len_bytes = math.cast(u16, app_name_wildcard.len * 2) orelse return error.NameTooLong;
1051 var app_name_unicode_string = windows.UNICODE_STRING{
1052 .Length = app_name_len_bytes,
1053 .MaximumLength = app_name_len_bytes,
1054 .Buffer = @constCast(app_name_wildcard.ptr),
1055 };
1056 const rc = windows.ntdll.NtQueryDirectoryFile(
1057 dir.fd,
1058 null,
1059 null,
1060 null,
1061 &io_status,
1062 &file_information_buf,
1063 file_information_buf.len,
1064 .FileDirectoryInformation,
1065 windows.FALSE, // single result
1066 &app_name_unicode_string,
1067 windows.FALSE, // restart iteration
1068 );
1069
1070 // If we get nothing with the wildcard, then we can just bail out
1071 // as we know appending PATHEXT will not yield anything.
1072 switch (rc) {
1073 .SUCCESS => {},
1074 .NO_SUCH_FILE => return error.FileNotFound,
1075 .NO_MORE_FILES => break,
1076 .ACCESS_DENIED => return error.AccessDenied,
1077 else => return windows.unexpectedStatus(rc),
1078 }
1079
1080 // According to the docs, this can only happen if there is not enough room in the
1081 // buffer to write at least one complete FILE_DIRECTORY_INFORMATION entry.
1082 // Therefore, this condition should not be possible to hit with the buffer size we use.
1083 std.debug.assert(io_status.Information != 0);
1084
1085 var it = windows.FileInformationIterator(windows.FILE_DIRECTORY_INFORMATION){ .buf = &file_information_buf };
1086 while (it.next()) |info| {
1087 // Skip directories
1088 if (info.FileAttributes & windows.FILE_ATTRIBUTE_DIRECTORY != 0) continue;
1089 const filename = @as([*]u16, @ptrCast(&info.FileName))[0 .. info.FileNameLength / 2];
1090 // Because all results start with the app_name since we're using the wildcard `app_name*`,
1091 // if the length is equal to app_name then this is an exact match
1092 if (filename.len == app_name_len) {
1093 // Note: We can't break early here because it's possible that the unappended version
1094 // fails to spawn, in which case we still want to try the PATHEXT appended versions.
1095 unappended_exists = true;
1096 } else if (windowsCreateProcessSupportsExtension(filename[app_name_len..])) |pathext_ext| {
1097 pathext_seen[@intFromEnum(pathext_ext)] = true;
1098 any_pathext_seen = true;
1099 }
1100 }
1101 }
1102
1103 const unappended_err = unappended: {
1104 if (unappended_exists) {
1105 if (dir_path_len != 0) switch (dir_buf.items[dir_buf.items.len - 1]) {
1106 '/', '\\' => {},
1107 else => try dir_buf.append(allocator, fs.path.sep),
1108 };
1109 try dir_buf.appendSlice(allocator, app_buf.items[0..app_name_len]);
1110 try dir_buf.append(allocator, 0);
1111 const full_app_name = dir_buf.items[0 .. dir_buf.items.len - 1 :0];
1112
1113 const is_bat_or_cmd = bat_or_cmd: {
1114 const app_name = app_buf.items[0..app_name_len];
1115 const ext_start = std.mem.lastIndexOfScalar(u16, app_name, '.') orelse break :bat_or_cmd false;
1116 const ext = app_name[ext_start..];
1117 const ext_enum = windowsCreateProcessSupportsExtension(ext) orelse break :bat_or_cmd false;
1118 switch (ext_enum) {
1119 .cmd, .bat => break :bat_or_cmd true,
1120 else => break :bat_or_cmd false,
1121 }
1122 };
1123 const cmd_line_w = if (is_bat_or_cmd)
1124 try cmd_line_cache.scriptCommandLine(full_app_name)
1125 else
1126 try cmd_line_cache.commandLine();
1127 const app_name_w = if (is_bat_or_cmd)
1128 try cmd_line_cache.cmdExePath()
1129 else
1130 full_app_name;
1131
1132 if (windowsCreateProcess(app_name_w.ptr, cmd_line_w.ptr, envp_ptr, cwd_ptr, lpStartupInfo, lpProcessInformation)) |_| {
1133 return;
1134 } else |err| switch (err) {
1135 error.FileNotFound,
1136 error.AccessDenied,
1137 => break :unappended err,
1138 error.InvalidExe => {
1139 // On InvalidExe, if the extension of the app name is .exe then
1140 // it's treated as an unrecoverable error. Otherwise, it'll be
1141 // skipped as normal.
1142 const app_name = app_buf.items[0..app_name_len];
1143 const ext_start = std.mem.lastIndexOfScalar(u16, app_name, '.') orelse break :unappended err;
1144 const ext = app_name[ext_start..];
1145 if (windows.eqlIgnoreCaseWTF16(ext, unicode.utf8ToUtf16LeStringLiteral(".EXE"))) {
1146 return error.UnrecoverableInvalidExe;
1147 }
1148 break :unappended err;
1149 },
1150 else => return err,
1151 }
1152 }
1153 break :unappended error.FileNotFound;
1154 };
1155
1156 if (!any_pathext_seen) return unappended_err;
1157
1158 // Now try any PATHEXT appended versions that we've seen
1159 var ext_it = mem.tokenizeScalar(u16, pathext, ';');
1160 while (ext_it.next()) |ext| {
1161 const ext_enum = windowsCreateProcessSupportsExtension(ext) orelse continue;
1162 if (!pathext_seen[@intFromEnum(ext_enum)]) continue;
1163
1164 dir_buf.shrinkRetainingCapacity(dir_path_len);
1165 if (dir_path_len != 0) switch (dir_buf.items[dir_buf.items.len - 1]) {
1166 '/', '\\' => {},
1167 else => try dir_buf.append(allocator, fs.path.sep),
1168 };
1169 try dir_buf.appendSlice(allocator, app_buf.items[0..app_name_len]);
1170 try dir_buf.appendSlice(allocator, ext);
1171 try dir_buf.append(allocator, 0);
1172 const full_app_name = dir_buf.items[0 .. dir_buf.items.len - 1 :0];
1173
1174 const is_bat_or_cmd = switch (ext_enum) {
1175 .cmd, .bat => true,
1176 else => false,
1177 };
1178 const cmd_line_w = if (is_bat_or_cmd)
1179 try cmd_line_cache.scriptCommandLine(full_app_name)
1180 else
1181 try cmd_line_cache.commandLine();
1182 const app_name_w = if (is_bat_or_cmd)
1183 try cmd_line_cache.cmdExePath()
1184 else
1185 full_app_name;
1186
1187 if (windowsCreateProcess(app_name_w.ptr, cmd_line_w.ptr, envp_ptr, cwd_ptr, lpStartupInfo, lpProcessInformation)) |_| {
1188 return;
1189 } else |err| switch (err) {
1190 error.FileNotFound => continue,
1191 error.AccessDenied => continue,
1192 error.InvalidExe => {
1193 // On InvalidExe, if the extension of the app name is .exe then
1194 // it's treated as an unrecoverable error. Otherwise, it'll be
1195 // skipped as normal.
1196 if (windows.eqlIgnoreCaseWTF16(ext, unicode.utf8ToUtf16LeStringLiteral(".EXE"))) {
1197 return error.UnrecoverableInvalidExe;
1198 }
1199 continue;
1200 },
1201 else => return err,
1202 }
1203 }
1204
1205 return unappended_err;
1206}
1207
1208fn windowsCreateProcess(app_name: [*:0]u16, cmd_line: [*:0]u16, envp_ptr: ?[*]u16, cwd_ptr: ?[*:0]u16, lpStartupInfo: *windows.STARTUPINFOW, lpProcessInformation: *windows.PROCESS_INFORMATION) !void {
1209 // TODO the docs for environment pointer say:
1210 // > A pointer to the environment block for the new process. If this parameter
1211 // > is NULL, the new process uses the environment of the calling process.
1212 // > ...
1213 // > An environment block can contain either Unicode or ANSI characters. If
1214 // > the environment block pointed to by lpEnvironment contains Unicode
1215 // > characters, be sure that dwCreationFlags includes CREATE_UNICODE_ENVIRONMENT.
1216 // > If this parameter is NULL and the environment block of the parent process
1217 // > contains Unicode characters, you must also ensure that dwCreationFlags
1218 // > includes CREATE_UNICODE_ENVIRONMENT.
1219 // This seems to imply that we have to somehow know whether our process parent passed
1220 // CREATE_UNICODE_ENVIRONMENT if we want to pass NULL for the environment parameter.
1221 // Since we do not know this information that would imply that we must not pass NULL
1222 // for the parameter.
1223 // However this would imply that programs compiled with -DUNICODE could not pass
1224 // environment variables to programs that were not, which seems unlikely.
1225 // More investigation is needed.
1226 return windows.CreateProcessW(
1227 app_name,
1228 cmd_line,
1229 null,
1230 null,
1231 windows.TRUE,
1232 windows.CREATE_UNICODE_ENVIRONMENT,
1233 @as(?*anyopaque, @ptrCast(envp_ptr)),
1234 cwd_ptr,
1235 lpStartupInfo,
1236 lpProcessInformation,
1237 );
1238}
1239
1240// Should be kept in sync with `windowsCreateProcessSupportsExtension`
1241const CreateProcessSupportedExtension = enum {
1242 bat,
1243 cmd,
1244 com,
1245 exe,
1246};
1247
1248/// Case-insensitive WTF-16 lookup
1249fn windowsCreateProcessSupportsExtension(ext: []const u16) ?CreateProcessSupportedExtension {
1250 if (ext.len != 4) return null;
1251 const State = enum {
1252 start,
1253 dot,
1254 b,
1255 ba,
1256 c,
1257 cm,
1258 co,
1259 e,
1260 ex,
1261 };
1262 var state: State = .start;
1263 for (ext) |c| switch (state) {
1264 .start => switch (c) {
1265 '.' => state = .dot,
1266 else => return null,
1267 },
1268 .dot => switch (c) {
1269 'b', 'B' => state = .b,
1270 'c', 'C' => state = .c,
1271 'e', 'E' => state = .e,
1272 else => return null,
1273 },
1274 .b => switch (c) {
1275 'a', 'A' => state = .ba,
1276 else => return null,
1277 },
1278 .c => switch (c) {
1279 'm', 'M' => state = .cm,
1280 'o', 'O' => state = .co,
1281 else => return null,
1282 },
1283 .e => switch (c) {
1284 'x', 'X' => state = .ex,
1285 else => return null,
1286 },
1287 .ba => switch (c) {
1288 't', 'T' => return .bat,
1289 else => return null,
1290 },
1291 .cm => switch (c) {
1292 'd', 'D' => return .cmd,
1293 else => return null,
1294 },
1295 .co => switch (c) {
1296 'm', 'M' => return .com,
1297 else => return null,
1298 },
1299 .ex => switch (c) {
1300 'e', 'E' => return .exe,
1301 else => return null,
1302 },
1303 };
1304 return null;
1305}
1306
1307test windowsCreateProcessSupportsExtension {
1308 try std.testing.expectEqual(CreateProcessSupportedExtension.exe, windowsCreateProcessSupportsExtension(&[_]u16{ '.', 'e', 'X', 'e' }).?);
1309 try std.testing.expect(windowsCreateProcessSupportsExtension(&[_]u16{ '.', 'e', 'X', 'e', 'c' }) == null);
1310}
1311
1312/// Serializes argv into a WTF-16 encoded command-line string for use with CreateProcessW.
1313///
1314/// Serialization is done on-demand and the result is cached in order to allow for:
1315/// - Only serializing the particular type of command line needed (`.bat`/`.cmd`
1316/// command line serialization is different from `.exe`/etc)
1317/// - Reusing the serialized command lines if necessary (i.e. if the execution
1318/// of a command fails and the PATH is going to be continued to be searched
1319/// for more candidates)
1320pub const WindowsCommandLineCache = struct {
1321 cmd_line: ?[:0]u16 = null,
1322 script_cmd_line: ?[:0]u16 = null,
1323 cmd_exe_path: ?[:0]u16 = null,
1324 argv: []const []const u8,
1325 allocator: mem.Allocator,
1326
1327 pub fn init(allocator: mem.Allocator, argv: []const []const u8) WindowsCommandLineCache {
1328 return .{
1329 .allocator = allocator,
1330 .argv = argv,
1331 };
1332 }
1333
1334 pub fn deinit(self: *WindowsCommandLineCache) void {
1335 if (self.cmd_line) |cmd_line| self.allocator.free(cmd_line);
1336 if (self.script_cmd_line) |script_cmd_line| self.allocator.free(script_cmd_line);
1337 if (self.cmd_exe_path) |cmd_exe_path| self.allocator.free(cmd_exe_path);
1338 }
1339
1340 pub fn commandLine(self: *WindowsCommandLineCache) ![:0]u16 {
1341 if (self.cmd_line == null) {
1342 self.cmd_line = try argvToCommandLineWindows(self.allocator, self.argv);
1343 }
1344 return self.cmd_line.?;
1345 }
1346
1347 /// Not cached, since the path to the batch script will change during PATH searching.
1348 /// `script_path` should be as qualified as possible, e.g. if the PATH is being searched,
1349 /// then script_path should include both the search path and the script filename
1350 /// (this allows avoiding cmd.exe having to search the PATH again).
1351 pub fn scriptCommandLine(self: *WindowsCommandLineCache, script_path: []const u16) ![:0]u16 {
1352 if (self.script_cmd_line) |v| self.allocator.free(v);
1353 self.script_cmd_line = try argvToScriptCommandLineWindows(
1354 self.allocator,
1355 script_path,
1356 self.argv[1..],
1357 );
1358 return self.script_cmd_line.?;
1359 }
1360
1361 pub fn cmdExePath(self: *WindowsCommandLineCache) ![:0]u16 {
1362 if (self.cmd_exe_path == null) {
1363 self.cmd_exe_path = try windowsCmdExePath(self.allocator);
1364 }
1365 return self.cmd_exe_path.?;
1366 }
1367};
1368
1369pub fn windowsCmdExePath(allocator: mem.Allocator) error{ OutOfMemory, Unexpected }![:0]u16 {
1370 var buf = try std.ArrayListUnmanaged(u16).initCapacity(allocator, 128);
1371 errdefer buf.deinit(allocator);
1372 while (true) {
1373 const unused_slice = buf.unusedCapacitySlice();
1374 // TODO: Get the system directory from PEB.ReadOnlyStaticServerData
1375 const len = windows.kernel32.GetSystemDirectoryW(@ptrCast(unused_slice), @intCast(unused_slice.len));
1376 if (len == 0) {
1377 switch (windows.kernel32.GetLastError()) {
1378 else => |err| return windows.unexpectedError(err),
1379 }
1380 }
1381 if (len > unused_slice.len) {
1382 try buf.ensureUnusedCapacity(allocator, len);
1383 } else {
1384 buf.items.len = len;
1385 break;
1386 }
1387 }
1388 switch (buf.items[buf.items.len - 1]) {
1389 '/', '\\' => {},
1390 else => try buf.append(allocator, fs.path.sep),
1391 }
1392 try buf.appendSlice(allocator, std.unicode.utf8ToUtf16LeStringLiteral("cmd.exe"));
1393 return try buf.toOwnedSliceSentinel(allocator, 0);
1394}
1395
1396pub const ArgvToScriptCommandLineError = error{
1397 OutOfMemory,
1398 InvalidWtf8,
1399 /// NUL (U+0000), LF (U+000A), CR (U+000D) are not allowed
1400 /// within arguments when executing a `.bat`/`.cmd` script.
1401 /// - NUL/LF signifiies end of arguments, so anything afterwards
1402 /// would be lost after execution.
1403 /// - CR is stripped by `cmd.exe`, so any CR codepoints
1404 /// would be lost after execution.
1405 InvalidBatchScriptArg,
1406};
1407
1408/// Serializes `argv` to a Windows command-line string that uses `cmd.exe /c` and `cmd.exe`-specific
1409/// escaping rules. The caller owns the returned slice.
1410///
1411/// Escapes `argv` using the suggested mitigation against arbitrary command execution from:
1412/// https://flatt.tech/research/posts/batbadbut-you-cant-securely-execute-commands-on-windows/
1413pub fn argvToScriptCommandLineWindows(
1414 allocator: mem.Allocator,
1415 /// Path to the `.bat`/`.cmd` script. If this path is relative, it is assumed to be relative to the CWD.
1416 /// The script must have been verified to exist at this path before calling this function.
1417 script_path: []const u16,
1418 /// Arguments, not including the script name itself. Expected to be encoded as WTF-8.
1419 script_args: []const []const u8,
1420) ArgvToScriptCommandLineError![:0]u16 {
1421 var buf = try std.ArrayList(u8).initCapacity(allocator, 64);
1422 defer buf.deinit();
1423
1424 // `/d` disables execution of AutoRun commands.
1425 // `/e:ON` and `/v:OFF` are needed for BatBadBut mitigation:
1426 // > If delayed expansion is enabled via the registry value DelayedExpansion,
1427 // > it must be disabled by explicitly calling cmd.exe with the /V:OFF option.
1428 // > Escaping for % requires the command extension to be enabled.
1429 // > If it’s disabled via the registry value EnableExtensions, it must be enabled with the /E:ON option.
1430 // https://flatt.tech/research/posts/batbadbut-you-cant-securely-execute-commands-on-windows/
1431 buf.appendSliceAssumeCapacity("cmd.exe /d /e:ON /v:OFF /c \"");
1432
1433 // Always quote the path to the script arg
1434 buf.appendAssumeCapacity('"');
1435 // We always want the path to the batch script to include a path separator in order to
1436 // avoid cmd.exe searching the PATH for the script. This is not part of the arbitrary
1437 // command execution mitigation, we just know exactly what script we want to execute
1438 // at this point, and potentially making cmd.exe re-find it is unnecessary.
1439 //
1440 // If the script path does not have a path separator, then we know its relative to CWD and
1441 // we can just put `.\` in the front.
1442 if (mem.indexOfAny(u16, script_path, &[_]u16{ mem.nativeToLittle(u16, '\\'), mem.nativeToLittle(u16, '/') }) == null) {
1443 try buf.appendSlice(".\\");
1444 }
1445 // Note that we don't do any escaping/mitigations for this argument, since the relevant
1446 // characters (", %, etc) are illegal in file paths and this function should only be called
1447 // with script paths that have been verified to exist.
1448 try std.unicode.wtf16LeToWtf8ArrayList(&buf, script_path);
1449 buf.appendAssumeCapacity('"');
1450
1451 for (script_args) |arg| {
1452 // Literal carriage returns get stripped when run through cmd.exe
1453 // and NUL/newlines act as 'end of command.' Because of this, it's basically
1454 // always a mistake to include these characters in argv, so it's
1455 // an error condition in order to ensure that the return of this
1456 // function can always roundtrip through cmd.exe.
1457 if (std.mem.indexOfAny(u8, arg, "\x00\r\n") != null) {
1458 return error.InvalidBatchScriptArg;
1459 }
1460
1461 // Separate args with a space.
1462 try buf.append(' ');
1463
1464 // Need to quote if the argument is empty (otherwise the arg would just be lost)
1465 // or if the last character is a `\`, since then something like "%~2" in a .bat
1466 // script would cause the closing " to be escaped which we don't want.
1467 var needs_quotes = arg.len == 0 or arg[arg.len - 1] == '\\';
1468 if (!needs_quotes) {
1469 for (arg) |c| {
1470 switch (c) {
1471 // Known good characters that don't need to be quoted
1472 'A'...'Z', 'a'...'z', '0'...'9', '#', '$', '*', '+', '-', '.', '/', ':', '?', '@', '\\', '_' => {},
1473 // When in doubt, quote
1474 else => {
1475 needs_quotes = true;
1476 break;
1477 },
1478 }
1479 }
1480 }
1481 if (needs_quotes) {
1482 try buf.append('"');
1483 }
1484 var backslashes: usize = 0;
1485 for (arg) |c| {
1486 switch (c) {
1487 '\\' => {
1488 backslashes += 1;
1489 },
1490 '"' => {
1491 try buf.appendNTimes('\\', backslashes);
1492 try buf.append('"');
1493 backslashes = 0;
1494 },
1495 // Replace `%` with `%%cd:~,%`.
1496 //
1497 // cmd.exe allows extracting a substring from an environment
1498 // variable with the syntax: `%foo:~<start_index>,<end_index>%`.
1499 // Therefore, `%cd:~,%` will always expand to an empty string
1500 // since both the start and end index are blank, and it is assumed
1501 // that `%cd%` is always available since it is a built-in variable
1502 // that corresponds to the current directory.
1503 //
1504 // This means that replacing `%foo%` with `%%cd:~,%foo%%cd:~,%`
1505 // will stop `%foo%` from being expanded and *after* expansion
1506 // we'll still be left with `%foo%` (the literal string).
1507 '%' => {
1508 // the trailing `%` is appended outside the switch
1509 try buf.appendSlice("%%cd:~,");
1510 backslashes = 0;
1511 },
1512 else => {
1513 backslashes = 0;
1514 },
1515 }
1516 try buf.append(c);
1517 }
1518 if (needs_quotes) {
1519 try buf.appendNTimes('\\', backslashes);
1520 try buf.append('"');
1521 }
1522 }
1523
1524 try buf.append('"');
1525
1526 return try unicode.wtf8ToWtf16LeAllocZ(allocator, buf.items);
1527}
1528
1529pub const ArgvToCommandLineError = error{ OutOfMemory, InvalidWtf8, InvalidArg0 };
1530
1531/// Serializes `argv` to a Windows command-line string suitable for passing to a child process and
1532/// parsing by the `CommandLineToArgvW` algorithm. The caller owns the returned slice.
1533pub fn argvToCommandLineWindows(
1534 allocator: mem.Allocator,
1535 argv: []const []const u8,
1536) ArgvToCommandLineError![:0]u16 {
1537 var buf = std.ArrayList(u8).init(allocator);
1538 defer buf.deinit();
1539
1540 if (argv.len != 0) {
1541 const arg0 = argv[0];
1542
1543 // The first argument must be quoted if it contains spaces or ASCII control characters
1544 // (excluding DEL). It also follows special quoting rules where backslashes have no special
1545 // interpretation, which makes it impossible to pass certain first arguments containing
1546 // double quotes to a child process without characters from the first argument leaking into
1547 // subsequent ones (which could have security implications).
1548 //
1549 // Empty arguments technically don't need quotes, but we quote them anyway for maximum
1550 // compatibility with different implementations of the 'CommandLineToArgvW' algorithm.
1551 //
1552 // Double quotes are illegal in paths on Windows, so for the sake of simplicity we reject
1553 // all first arguments containing double quotes, even ones that we could theoretically
1554 // serialize in unquoted form.
1555 var needs_quotes = arg0.len == 0;
1556 for (arg0) |c| {
1557 if (c <= ' ') {
1558 needs_quotes = true;
1559 } else if (c == '"') {
1560 return error.InvalidArg0;
1561 }
1562 }
1563 if (needs_quotes) {
1564 try buf.append('"');
1565 try buf.appendSlice(arg0);
1566 try buf.append('"');
1567 } else {
1568 try buf.appendSlice(arg0);
1569 }
1570
1571 for (argv[1..]) |arg| {
1572 try buf.append(' ');
1573
1574 // Subsequent arguments must be quoted if they contain spaces, tabs or double quotes,
1575 // or if they are empty. For simplicity and for maximum compatibility with different
1576 // implementations of the 'CommandLineToArgvW' algorithm, we also quote all ASCII
1577 // control characters (again, excluding DEL).
1578 needs_quotes = for (arg) |c| {
1579 if (c <= ' ' or c == '"') {
1580 break true;
1581 }
1582 } else arg.len == 0;
1583 if (!needs_quotes) {
1584 try buf.appendSlice(arg);
1585 continue;
1586 }
1587
1588 try buf.append('"');
1589 var backslash_count: usize = 0;
1590 for (arg) |byte| {
1591 switch (byte) {
1592 '\\' => {
1593 backslash_count += 1;
1594 },
1595 '"' => {
1596 try buf.appendNTimes('\\', backslash_count * 2 + 1);
1597 try buf.append('"');
1598 backslash_count = 0;
1599 },
1600 else => {
1601 try buf.appendNTimes('\\', backslash_count);
1602 try buf.append(byte);
1603 backslash_count = 0;
1604 },
1605 }
1606 }
1607 try buf.appendNTimes('\\', backslash_count * 2);
1608 try buf.append('"');
1609 }
1610 }
1611
1612 return try unicode.wtf8ToWtf16LeAllocZ(allocator, buf.items);
1613}
1614
1615test argvToCommandLineWindows {
1616 const t = testArgvToCommandLineWindows;
1617
1618 try t(&.{
1619 \\C:\Program Files\zig\zig.exe
1620 ,
1621 \\run
1622 ,
1623 \\.\src\main.zig
1624 ,
1625 \\-target
1626 ,
1627 \\x86_64-windows-gnu
1628 ,
1629 \\-O
1630 ,
1631 \\ReleaseSafe
1632 ,
1633 \\--
1634 ,
1635 \\--emoji=🗿
1636 ,
1637 \\--eval=new Regex("Dwayne \"The Rock\" Johnson")
1638 ,
1639 },
1640 \\"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\")"
1641 );
1642
1643 try t(&.{}, "");
1644 try t(&.{""}, "\"\"");
1645 try t(&.{" "}, "\" \"");
1646 try t(&.{"\t"}, "\"\t\"");
1647 try t(&.{"\x07"}, "\"\x07\"");
1648 try t(&.{"🦎"}, "🦎");
1649
1650 try t(
1651 &.{ "zig", "aa aa", "bb\tbb", "cc\ncc", "dd\r\ndd", "ee\x7Fee" },
1652 "zig \"aa aa\" \"bb\tbb\" \"cc\ncc\" \"dd\r\ndd\" ee\x7Fee",
1653 );
1654
1655 try t(
1656 &.{ "\\\\foo bar\\foo bar\\", "\\\\zig zag\\zig zag\\" },
1657 "\"\\\\foo bar\\foo bar\\\" \"\\\\zig zag\\zig zag\\\\\"",
1658 );
1659
1660 try std.testing.expectError(
1661 error.InvalidArg0,
1662 argvToCommandLineWindows(std.testing.allocator, &.{"\"quotes\"quotes\""}),
1663 );
1664 try std.testing.expectError(
1665 error.InvalidArg0,
1666 argvToCommandLineWindows(std.testing.allocator, &.{"quotes\"quotes"}),
1667 );
1668 try std.testing.expectError(
1669 error.InvalidArg0,
1670 argvToCommandLineWindows(std.testing.allocator, &.{"q u o t e s \" q u o t e s"}),
1671 );
1672}
1673
1674fn testArgvToCommandLineWindows(argv: []const []const u8, expected_cmd_line: []const u8) !void {
1675 const cmd_line_w = try argvToCommandLineWindows(std.testing.allocator, argv);
1676 defer std.testing.allocator.free(cmd_line_w);
1677
1678 const cmd_line = try unicode.wtf16LeToWtf8Alloc(std.testing.allocator, cmd_line_w);
1679 defer std.testing.allocator.free(cmd_line);
1680
1681 try std.testing.expectEqualStrings(expected_cmd_line, cmd_line);
1682}
1683
1684fn windowsDestroyPipe(rd: ?windows.HANDLE, wr: ?windows.HANDLE) void {
1685 if (rd) |h| posix.close(h);
1686 if (wr) |h| posix.close(h);
1687}
1688
1689fn windowsMakePipeIn(rd: *?windows.HANDLE, wr: *?windows.HANDLE, sattr: *const windows.SECURITY_ATTRIBUTES) !void {
1690 var rd_h: windows.HANDLE = undefined;
1691 var wr_h: windows.HANDLE = undefined;
1692 try windows.CreatePipe(&rd_h, &wr_h, sattr);
1693 errdefer windowsDestroyPipe(rd_h, wr_h);
1694 try windows.SetHandleInformation(wr_h, windows.HANDLE_FLAG_INHERIT, 0);
1695 rd.* = rd_h;
1696 wr.* = wr_h;
1697}
1698
1699var pipe_name_counter = std.atomic.Value(u32).init(1);
1700
1701fn windowsMakeAsyncPipe(rd: *?windows.HANDLE, wr: *?windows.HANDLE, sattr: *const windows.SECURITY_ATTRIBUTES) !void {
1702 var tmp_bufw: [128]u16 = undefined;
1703
1704 // Anonymous pipes are built upon Named pipes.
1705 // https://docs.microsoft.com/en-us/windows/win32/api/namedpipeapi/nf-namedpipeapi-createpipe
1706 // Asynchronous (overlapped) read and write operations are not supported by anonymous pipes.
1707 // https://docs.microsoft.com/en-us/windows/win32/ipc/anonymous-pipe-operations
1708 const pipe_path = blk: {
1709 var tmp_buf: [128]u8 = undefined;
1710 // Forge a random path for the pipe.
1711 const pipe_path = std.fmt.bufPrintZ(
1712 &tmp_buf,
1713 "\\\\.\\pipe\\zig-childprocess-{d}-{d}",
1714 .{ windows.GetCurrentProcessId(), pipe_name_counter.fetchAdd(1, .monotonic) },
1715 ) catch unreachable;
1716 const len = std.unicode.wtf8ToWtf16Le(&tmp_bufw, pipe_path) catch unreachable;
1717 tmp_bufw[len] = 0;
1718 break :blk tmp_bufw[0..len :0];
1719 };
1720
1721 // Create the read handle that can be used with overlapped IO ops.
1722 const read_handle = windows.kernel32.CreateNamedPipeW(
1723 pipe_path.ptr,
1724 windows.PIPE_ACCESS_INBOUND | windows.FILE_FLAG_OVERLAPPED,
1725 windows.PIPE_TYPE_BYTE,
1726 1,
1727 4096,
1728 4096,
1729 0,
1730 sattr,
1731 );
1732 if (read_handle == windows.INVALID_HANDLE_VALUE) {
1733 switch (windows.kernel32.GetLastError()) {
1734 else => |err| return windows.unexpectedError(err),
1735 }
1736 }
1737 errdefer posix.close(read_handle);
1738
1739 var sattr_copy = sattr.*;
1740 const write_handle = windows.kernel32.CreateFileW(
1741 pipe_path.ptr,
1742 windows.GENERIC_WRITE,
1743 0,
1744 &sattr_copy,
1745 windows.OPEN_EXISTING,
1746 windows.FILE_ATTRIBUTE_NORMAL,
1747 null,
1748 );
1749 if (write_handle == windows.INVALID_HANDLE_VALUE) {
1750 switch (windows.kernel32.GetLastError()) {
1751 else => |err| return windows.unexpectedError(err),
1752 }
1753 }
1754 errdefer posix.close(write_handle);
1755
1756 try windows.SetHandleInformation(read_handle, windows.HANDLE_FLAG_INHERIT, 0);
1757
1758 rd.* = read_handle;
1759 wr.* = write_handle;
1760}
1761
1762fn destroyPipe(pipe: [2]posix.fd_t) void {
1763 posix.close(pipe[0]);
1764 if (pipe[0] != pipe[1]) posix.close(pipe[1]);
1765}
1766
1767// Child of fork calls this to report an error to the fork parent.
1768// Then the child exits.
1769fn forkChildErrReport(fd: i32, err: ChildProcess.SpawnError) noreturn {
1770 writeIntFd(fd, @as(ErrInt, @intFromError(err))) catch {};
1771 // If we're linking libc, some naughty applications may have registered atexit handlers
1772 // which we really do not want to run in the fork child. I caught LLVM doing this and
1773 // it caused a deadlock instead of doing an exit syscall. In the words of Avril Lavigne,
1774 // "Why'd you have to go and make things so complicated?"
1775 if (builtin.link_libc) {
1776 // The _exit(2) function does nothing but make the exit syscall, unlike exit(3)
1777 std.c._exit(1);
1778 }
1779 posix.exit(1);
1780}
1781
1782const ErrInt = std.meta.Int(.unsigned, @sizeOf(anyerror) * 8);
1783
1784fn writeIntFd(fd: i32, value: ErrInt) !void {
1785 const file = File{ .handle = fd };
1786 file.writer().writeInt(u64, @intCast(value), .little) catch return error.SystemResources;
1787}
1788
1789fn readIntFd(fd: i32) !ErrInt {
1790 const file = File{ .handle = fd };
1791 return @as(ErrInt, @intCast(file.reader().readInt(u64, .little) catch return error.SystemResources));
1792}
1793
1794/// Caller must free result.
1795pub fn createWindowsEnvBlock(allocator: mem.Allocator, env_map: *const EnvMap) ![]u16 {
1796 // count bytes needed
1797 const max_chars_needed = x: {
1798 var max_chars_needed: usize = 4; // 4 for the final 4 null bytes
1799 var it = env_map.iterator();
1800 while (it.next()) |pair| {
1801 // +1 for '='
1802 // +1 for null byte
1803 max_chars_needed += pair.key_ptr.len + pair.value_ptr.len + 2;
1804 }
1805 break :x max_chars_needed;
1806 };
1807 const result = try allocator.alloc(u16, max_chars_needed);
1808 errdefer allocator.free(result);
1809
1810 var it = env_map.iterator();
1811 var i: usize = 0;
1812 while (it.next()) |pair| {
1813 i += try unicode.wtf8ToWtf16Le(result[i..], pair.key_ptr.*);
1814 result[i] = '=';
1815 i += 1;
1816 i += try unicode.wtf8ToWtf16Le(result[i..], pair.value_ptr.*);
1817 result[i] = 0;
1818 i += 1;
1819 }
1820 result[i] = 0;
1821 i += 1;
1822 result[i] = 0;
1823 i += 1;
1824 result[i] = 0;
1825 i += 1;
1826 result[i] = 0;
1827 i += 1;
1828 return try allocator.realloc(result, i);
1829}
1830
1831pub fn createNullDelimitedEnvMap(arena: mem.Allocator, env_map: *const EnvMap) ![:null]?[*:0]u8 {
1832 const envp_count = env_map.count();
1833 const envp_buf = try arena.allocSentinel(?[*:0]u8, envp_count, null);
1834 {
1835 var it = env_map.iterator();
1836 var i: usize = 0;
1837 while (it.next()) |pair| : (i += 1) {
1838 const env_buf = try arena.allocSentinel(u8, pair.key_ptr.len + pair.value_ptr.len + 1, 0);
1839 @memcpy(env_buf[0..pair.key_ptr.len], pair.key_ptr.*);
1840 env_buf[pair.key_ptr.len] = '=';
1841 @memcpy(env_buf[pair.key_ptr.len + 1 ..][0..pair.value_ptr.len], pair.value_ptr.*);
1842 envp_buf[i] = env_buf.ptr;
1843 }
1844 assert(i == envp_count);
1845 }
1846 return envp_buf;
1847}
1848
1849test createNullDelimitedEnvMap {
1850 const testing = std.testing;
1851 const allocator = testing.allocator;
1852 var envmap = EnvMap.init(allocator);
1853 defer envmap.deinit();
1854
1855 try envmap.put("HOME", "/home/ifreund");
1856 try envmap.put("WAYLAND_DISPLAY", "wayland-1");
1857 try envmap.put("DISPLAY", ":1");
1858 try envmap.put("DEBUGINFOD_URLS", " ");
1859 try envmap.put("XCURSOR_SIZE", "24");
1860
1861 var arena = std.heap.ArenaAllocator.init(allocator);
1862 defer arena.deinit();
1863 const environ = try createNullDelimitedEnvMap(arena.allocator(), &envmap);
1864
1865 try testing.expectEqual(@as(usize, 5), environ.len);
1866
1867 inline for (.{
1868 "HOME=/home/ifreund",
1869 "WAYLAND_DISPLAY=wayland-1",
1870 "DISPLAY=:1",
1871 "DEBUGINFOD_URLS= ",
1872 "XCURSOR_SIZE=24",
1873 }) |target| {
1874 for (environ) |variable| {
1875 if (mem.eql(u8, mem.span(variable orelse continue), target)) break;
1876 } else {
1877 try testing.expect(false); // Environment variable not found
1878 }
1879 }
1880}
lib/std/process.zig+111-24
...@@ -6,12 +6,12 @@ const math = std.math;...@@ -6,12 +6,12 @@ const math = std.math;
6const Allocator = mem.Allocator;6const Allocator = mem.Allocator;
7const assert = std.debug.assert;7const assert = std.debug.assert;
8const testing = std.testing;8const testing = std.testing;
9const child_process = @import("child_process.zig");
10const native_os = builtin.os.tag;9const native_os = builtin.os.tag;
11const posix = std.posix;10const posix = std.posix;
12const windows = std.os.windows;11const windows = std.os.windows;
12const unicode = std.unicode;
1313
14pub const Child = child_process.ChildProcess;14pub const Child = @import("process/Child.zig");
15pub const abort = posix.abort;15pub const abort = posix.abort;
16pub const exit = posix.exit;16pub const exit = posix.exit;
17pub const changeCurDir = posix.chdir;17pub const changeCurDir = posix.chdir;
...@@ -86,7 +86,7 @@ pub const EnvMap = struct {...@@ -86,7 +86,7 @@ pub const EnvMap = struct {
86 _ = self;86 _ = self;
87 if (native_os == .windows) {87 if (native_os == .windows) {
88 var h = std.hash.Wyhash.init(0);88 var h = std.hash.Wyhash.init(0);
89 var it = std.unicode.Wtf8View.initUnchecked(s).iterator();89 var it = unicode.Wtf8View.initUnchecked(s).iterator();
90 while (it.nextCodepoint()) |cp| {90 while (it.nextCodepoint()) |cp| {
91 const cp_upper = upcase(cp);91 const cp_upper = upcase(cp);
92 h.update(&[_]u8{92 h.update(&[_]u8{
...@@ -103,8 +103,8 @@ pub const EnvMap = struct {...@@ -103,8 +103,8 @@ pub const EnvMap = struct {
103 pub fn eql(self: @This(), a: []const u8, b: []const u8) bool {103 pub fn eql(self: @This(), a: []const u8, b: []const u8) bool {
104 _ = self;104 _ = self;
105 if (native_os == .windows) {105 if (native_os == .windows) {
106 var it_a = std.unicode.Wtf8View.initUnchecked(a).iterator();106 var it_a = unicode.Wtf8View.initUnchecked(a).iterator();
107 var it_b = std.unicode.Wtf8View.initUnchecked(b).iterator();107 var it_b = unicode.Wtf8View.initUnchecked(b).iterator();
108 while (true) {108 while (true) {
109 const c_a = it_a.nextCodepoint() orelse break;109 const c_a = it_a.nextCodepoint() orelse break;
110 const c_b = it_b.nextCodepoint() orelse return false;110 const c_b = it_b.nextCodepoint() orelse return false;
...@@ -141,7 +141,7 @@ pub const EnvMap = struct {...@@ -141,7 +141,7 @@ pub const EnvMap = struct {
141 /// If `putMove` fails, the ownership of key and value does not transfer.141 /// If `putMove` fails, the ownership of key and value does not transfer.
142 /// On Windows `key` must be a valid [WTF-8](https://simonsapin.github.io/wtf-8/) string.142 /// On Windows `key` must be a valid [WTF-8](https://simonsapin.github.io/wtf-8/) string.
143 pub fn putMove(self: *EnvMap, key: []u8, value: []u8) !void {143 pub fn putMove(self: *EnvMap, key: []u8, value: []u8) !void {
144 assert(std.unicode.wtf8ValidateSlice(key));144 assert(unicode.wtf8ValidateSlice(key));
145 const get_or_put = try self.hash_map.getOrPut(key);145 const get_or_put = try self.hash_map.getOrPut(key);
146 if (get_or_put.found_existing) {146 if (get_or_put.found_existing) {
147 self.free(get_or_put.key_ptr.*);147 self.free(get_or_put.key_ptr.*);
...@@ -154,7 +154,7 @@ pub const EnvMap = struct {...@@ -154,7 +154,7 @@ pub const EnvMap = struct {
154 /// `key` and `value` are copied into the EnvMap.154 /// `key` and `value` are copied into the EnvMap.
155 /// On Windows `key` must be a valid [WTF-8](https://simonsapin.github.io/wtf-8/) string.155 /// On Windows `key` must be a valid [WTF-8](https://simonsapin.github.io/wtf-8/) string.
156 pub fn put(self: *EnvMap, key: []const u8, value: []const u8) !void {156 pub fn put(self: *EnvMap, key: []const u8, value: []const u8) !void {
157 assert(std.unicode.wtf8ValidateSlice(key));157 assert(unicode.wtf8ValidateSlice(key));
158 const value_copy = try self.copy(value);158 const value_copy = try self.copy(value);
159 errdefer self.free(value_copy);159 errdefer self.free(value_copy);
160 const get_or_put = try self.hash_map.getOrPut(key);160 const get_or_put = try self.hash_map.getOrPut(key);
...@@ -173,7 +173,7 @@ pub const EnvMap = struct {...@@ -173,7 +173,7 @@ pub const EnvMap = struct {
173 /// The returned pointer is invalidated if the map resizes.173 /// The returned pointer is invalidated if the map resizes.
174 /// On Windows `key` must be a valid [WTF-8](https://simonsapin.github.io/wtf-8/) string.174 /// On Windows `key` must be a valid [WTF-8](https://simonsapin.github.io/wtf-8/) string.
175 pub fn getPtr(self: EnvMap, key: []const u8) ?*[]const u8 {175 pub fn getPtr(self: EnvMap, key: []const u8) ?*[]const u8 {
176 assert(std.unicode.wtf8ValidateSlice(key));176 assert(unicode.wtf8ValidateSlice(key));
177 return self.hash_map.getPtr(key);177 return self.hash_map.getPtr(key);
178 }178 }
179179
...@@ -182,7 +182,7 @@ pub const EnvMap = struct {...@@ -182,7 +182,7 @@ pub const EnvMap = struct {
182 /// key is removed from the map.182 /// key is removed from the map.
183 /// On Windows `key` must be a valid [WTF-8](https://simonsapin.github.io/wtf-8/) string.183 /// On Windows `key` must be a valid [WTF-8](https://simonsapin.github.io/wtf-8/) string.
184 pub fn get(self: EnvMap, key: []const u8) ?[]const u8 {184 pub fn get(self: EnvMap, key: []const u8) ?[]const u8 {
185 assert(std.unicode.wtf8ValidateSlice(key));185 assert(unicode.wtf8ValidateSlice(key));
186 return self.hash_map.get(key);186 return self.hash_map.get(key);
187 }187 }
188188
...@@ -190,7 +190,7 @@ pub const EnvMap = struct {...@@ -190,7 +190,7 @@ pub const EnvMap = struct {
190 /// This invalidates the value returned by get() for this key.190 /// This invalidates the value returned by get() for this key.
191 /// On Windows `key` must be a valid [WTF-8](https://simonsapin.github.io/wtf-8/) string.191 /// On Windows `key` must be a valid [WTF-8](https://simonsapin.github.io/wtf-8/) string.
192 pub fn remove(self: *EnvMap, key: []const u8) void {192 pub fn remove(self: *EnvMap, key: []const u8) void {
193 assert(std.unicode.wtf8ValidateSlice(key));193 assert(unicode.wtf8ValidateSlice(key));
194 const kv = self.hash_map.fetchRemove(key) orelse return;194 const kv = self.hash_map.fetchRemove(key) orelse return;
195 self.free(kv.key);195 self.free(kv.key);
196 self.free(kv.value);196 self.free(kv.value);
...@@ -260,7 +260,7 @@ test EnvMap {...@@ -260,7 +260,7 @@ test EnvMap {
260 try testing.expectEqualStrings("something else", env.get("кириллица").?);260 try testing.expectEqualStrings("something else", env.get("кириллица").?);
261261
262 // and WTF-8 that's not valid UTF-8262 // and WTF-8 that's not valid UTF-8
263 const wtf8_with_surrogate_pair = try std.unicode.wtf16LeToWtf8Alloc(testing.allocator, &[_]u16{263 const wtf8_with_surrogate_pair = try unicode.wtf16LeToWtf8Alloc(testing.allocator, &[_]u16{
264 std.mem.nativeToLittle(u16, 0xD83D), // unpaired high surrogate264 std.mem.nativeToLittle(u16, 0xD83D), // unpaired high surrogate
265 });265 });
266 defer testing.allocator.free(wtf8_with_surrogate_pair);266 defer testing.allocator.free(wtf8_with_surrogate_pair);
...@@ -300,7 +300,7 @@ pub fn getEnvMap(allocator: Allocator) GetEnvMapError!EnvMap {...@@ -300,7 +300,7 @@ pub fn getEnvMap(allocator: Allocator) GetEnvMapError!EnvMap {
300300
301 while (ptr[i] != 0 and ptr[i] != '=') : (i += 1) {}301 while (ptr[i] != 0 and ptr[i] != '=') : (i += 1) {}
302 const key_w = ptr[key_start..i];302 const key_w = ptr[key_start..i];
303 const key = try std.unicode.wtf16LeToWtf8Alloc(allocator, key_w);303 const key = try unicode.wtf16LeToWtf8Alloc(allocator, key_w);
304 errdefer allocator.free(key);304 errdefer allocator.free(key);
305305
306 if (ptr[i] == '=') i += 1;306 if (ptr[i] == '=') i += 1;
...@@ -308,7 +308,7 @@ pub fn getEnvMap(allocator: Allocator) GetEnvMapError!EnvMap {...@@ -308,7 +308,7 @@ pub fn getEnvMap(allocator: Allocator) GetEnvMapError!EnvMap {
308 const value_start = i;308 const value_start = i;
309 while (ptr[i] != 0) : (i += 1) {}309 while (ptr[i] != 0) : (i += 1) {}
310 const value_w = ptr[value_start..i];310 const value_w = ptr[value_start..i];
311 const value = try std.unicode.wtf16LeToWtf8Alloc(allocator, value_w);311 const value = try unicode.wtf16LeToWtf8Alloc(allocator, value_w);
312 errdefer allocator.free(value);312 errdefer allocator.free(value);
313313
314 i += 1; // skip over null byte314 i += 1; // skip over null byte
...@@ -401,13 +401,13 @@ pub fn getEnvVarOwned(allocator: Allocator, key: []const u8) GetEnvVarOwnedError...@@ -401,13 +401,13 @@ pub fn getEnvVarOwned(allocator: Allocator, key: []const u8) GetEnvVarOwnedError
401 const result_w = blk: {401 const result_w = blk: {
402 var stack_alloc = std.heap.stackFallback(256 * @sizeOf(u16), allocator);402 var stack_alloc = std.heap.stackFallback(256 * @sizeOf(u16), allocator);
403 const stack_allocator = stack_alloc.get();403 const stack_allocator = stack_alloc.get();
404 const key_w = try std.unicode.wtf8ToWtf16LeAllocZ(stack_allocator, key);404 const key_w = try unicode.wtf8ToWtf16LeAllocZ(stack_allocator, key);
405 defer stack_allocator.free(key_w);405 defer stack_allocator.free(key_w);
406406
407 break :blk getenvW(key_w) orelse return error.EnvironmentVariableNotFound;407 break :blk getenvW(key_w) orelse return error.EnvironmentVariableNotFound;
408 };408 };
409 // wtf16LeToWtf8Alloc can only fail with OutOfMemory409 // wtf16LeToWtf8Alloc can only fail with OutOfMemory
410 return std.unicode.wtf16LeToWtf8Alloc(allocator, result_w);410 return unicode.wtf16LeToWtf8Alloc(allocator, result_w);
411 } else if (native_os == .wasi and !builtin.link_libc) {411 } else if (native_os == .wasi and !builtin.link_libc) {
412 var envmap = getEnvMap(allocator) catch return error.OutOfMemory;412 var envmap = getEnvMap(allocator) catch return error.OutOfMemory;
413 defer envmap.deinit();413 defer envmap.deinit();
...@@ -422,7 +422,7 @@ pub fn getEnvVarOwned(allocator: Allocator, key: []const u8) GetEnvVarOwnedError...@@ -422,7 +422,7 @@ pub fn getEnvVarOwned(allocator: Allocator, key: []const u8) GetEnvVarOwnedError
422/// On Windows, `key` must be valid UTF-8.422/// On Windows, `key` must be valid UTF-8.
423pub fn hasEnvVarConstant(comptime key: []const u8) bool {423pub fn hasEnvVarConstant(comptime key: []const u8) bool {
424 if (native_os == .windows) {424 if (native_os == .windows) {
425 const key_w = comptime std.unicode.utf8ToUtf16LeStringLiteral(key);425 const key_w = comptime unicode.utf8ToUtf16LeStringLiteral(key);
426 return getenvW(key_w) != null;426 return getenvW(key_w) != null;
427 } else if (native_os == .wasi and !builtin.link_libc) {427 } else if (native_os == .wasi and !builtin.link_libc) {
428 @compileError("hasEnvVarConstant is not supported for WASI without libc");428 @compileError("hasEnvVarConstant is not supported for WASI without libc");
...@@ -445,7 +445,7 @@ pub fn hasEnvVar(allocator: Allocator, key: []const u8) HasEnvVarError!bool {...@@ -445,7 +445,7 @@ pub fn hasEnvVar(allocator: Allocator, key: []const u8) HasEnvVarError!bool {
445 if (native_os == .windows) {445 if (native_os == .windows) {
446 var stack_alloc = std.heap.stackFallback(256 * @sizeOf(u16), allocator);446 var stack_alloc = std.heap.stackFallback(256 * @sizeOf(u16), allocator);
447 const stack_allocator = stack_alloc.get();447 const stack_allocator = stack_alloc.get();
448 const key_w = try std.unicode.wtf8ToWtf16LeAllocZ(stack_allocator, key);448 const key_w = try unicode.wtf8ToWtf16LeAllocZ(stack_allocator, key);
449 defer stack_allocator.free(key_w);449 defer stack_allocator.free(key_w);
450 return getenvW(key_w) != null;450 return getenvW(key_w) != null;
451 } else if (native_os == .wasi and !builtin.link_libc) {451 } else if (native_os == .wasi and !builtin.link_libc) {
...@@ -659,7 +659,7 @@ pub const ArgIteratorWindows = struct {...@@ -659,7 +659,7 @@ pub const ArgIteratorWindows = struct {
659 /// The iterator makes a copy of `cmd_line_w` converted WTF-8 and keeps it; it does *not* take659 /// The iterator makes a copy of `cmd_line_w` converted WTF-8 and keeps it; it does *not* take
660 /// ownership of `cmd_line_w`.660 /// ownership of `cmd_line_w`.
661 pub fn init(allocator: Allocator, cmd_line_w: [*:0]const u16) InitError!ArgIteratorWindows {661 pub fn init(allocator: Allocator, cmd_line_w: [*:0]const u16) InitError!ArgIteratorWindows {
662 const cmd_line = try std.unicode.wtf16LeToWtf8Alloc(allocator, mem.sliceTo(cmd_line_w, 0));662 const cmd_line = try unicode.wtf16LeToWtf8Alloc(allocator, mem.sliceTo(cmd_line_w, 0));
663 errdefer allocator.free(cmd_line);663 errdefer allocator.free(cmd_line);
664664
665 const buffer = try allocator.alloc(u8, cmd_line.len + 1);665 const buffer = try allocator.alloc(u8, cmd_line.len + 1);
...@@ -728,17 +728,17 @@ pub const ArgIteratorWindows = struct {...@@ -728,17 +728,17 @@ pub const ArgIteratorWindows = struct {
728 // must be 6 bytes for a surrogate pair to exist.728 // must be 6 bytes for a surrogate pair to exist.
729 if (self.end - self.start >= 6) {729 if (self.end - self.start >= 6) {
730 const window = self.buffer[self.end - 6 .. self.end];730 const window = self.buffer[self.end - 6 .. self.end];
731 const view = std.unicode.Wtf8View.init(window) catch return;731 const view = unicode.Wtf8View.init(window) catch return;
732 var it = view.iterator();732 var it = view.iterator();
733 var pair: [2]u16 = undefined;733 var pair: [2]u16 = undefined;
734 pair[0] = std.mem.nativeToLittle(u16, std.math.cast(u16, it.nextCodepoint().?) orelse return);734 pair[0] = std.mem.nativeToLittle(u16, std.math.cast(u16, it.nextCodepoint().?) orelse return);
735 if (!std.unicode.utf16IsHighSurrogate(std.mem.littleToNative(u16, pair[0]))) return;735 if (!unicode.utf16IsHighSurrogate(std.mem.littleToNative(u16, pair[0]))) return;
736 pair[1] = std.mem.nativeToLittle(u16, std.math.cast(u16, it.nextCodepoint().?) orelse return);736 pair[1] = std.mem.nativeToLittle(u16, std.math.cast(u16, it.nextCodepoint().?) orelse return);
737 if (!std.unicode.utf16IsLowSurrogate(std.mem.littleToNative(u16, pair[1]))) return;737 if (!unicode.utf16IsLowSurrogate(std.mem.littleToNative(u16, pair[1]))) return;
738 // We know we have a valid surrogate pair, so convert738 // We know we have a valid surrogate pair, so convert
739 // it to UTF-8, overwriting the surrogate pair's bytes739 // it to UTF-8, overwriting the surrogate pair's bytes
740 // and then chop off the extra bytes.740 // and then chop off the extra bytes.
741 const len = std.unicode.utf16LeToUtf8(window, &pair) catch unreachable;741 const len = unicode.utf16LeToUtf8(window, &pair) catch unreachable;
742 const delta = 6 - len;742 const delta = 6 - len;
743 self.end -= delta;743 self.end -= delta;
744 }744 }
...@@ -1341,7 +1341,7 @@ test ArgIteratorWindows {...@@ -1341,7 +1341,7 @@ test ArgIteratorWindows {
1341}1341}
13421342
1343fn testArgIteratorWindows(cmd_line: []const u8, expected_args: []const []const u8) !void {1343fn testArgIteratorWindows(cmd_line: []const u8, expected_args: []const []const u8) !void {
1344 const cmd_line_w = try std.unicode.wtf8ToWtf16LeAllocZ(testing.allocator, cmd_line);1344 const cmd_line_w = try unicode.wtf8ToWtf16LeAllocZ(testing.allocator, cmd_line);
1345 defer testing.allocator.free(cmd_line_w);1345 defer testing.allocator.free(cmd_line_w);
13461346
1347 // next1347 // next
...@@ -1642,7 +1642,7 @@ pub fn execve(...@@ -1642,7 +1642,7 @@ pub fn execve(
16421642
1643 const envp = m: {1643 const envp = m: {
1644 if (env_map) |m| {1644 if (env_map) |m| {
1645 const envp_buf = try child_process.createNullDelimitedEnvMap(arena, m);1645 const envp_buf = try createNullDelimitedEnvMap(arena, m);
1646 break :m envp_buf.ptr;1646 break :m envp_buf.ptr;
1647 } else if (builtin.link_libc) {1647 } else if (builtin.link_libc) {
1648 break :m std.c.environ;1648 break :m std.c.environ;
...@@ -1789,3 +1789,90 @@ pub fn raiseFileDescriptorLimit() void {...@@ -1789,3 +1789,90 @@ pub fn raiseFileDescriptorLimit() void {
1789test raiseFileDescriptorLimit {1789test raiseFileDescriptorLimit {
1790 raiseFileDescriptorLimit();1790 raiseFileDescriptorLimit();
1791}1791}
1792
1793pub fn createNullDelimitedEnvMap(arena: mem.Allocator, env_map: *const EnvMap) ![:null]?[*:0]u8 {
1794 const envp_count = env_map.count();
1795 const envp_buf = try arena.allocSentinel(?[*:0]u8, envp_count, null);
1796 {
1797 var it = env_map.iterator();
1798 var i: usize = 0;
1799 while (it.next()) |pair| : (i += 1) {
1800 const env_buf = try arena.allocSentinel(u8, pair.key_ptr.len + pair.value_ptr.len + 1, 0);
1801 @memcpy(env_buf[0..pair.key_ptr.len], pair.key_ptr.*);
1802 env_buf[pair.key_ptr.len] = '=';
1803 @memcpy(env_buf[pair.key_ptr.len + 1 ..][0..pair.value_ptr.len], pair.value_ptr.*);
1804 envp_buf[i] = env_buf.ptr;
1805 }
1806 assert(i == envp_count);
1807 }
1808 return envp_buf;
1809}
1810
1811test createNullDelimitedEnvMap {
1812 const allocator = testing.allocator;
1813 var envmap = EnvMap.init(allocator);
1814 defer envmap.deinit();
1815
1816 try envmap.put("HOME", "/home/ifreund");
1817 try envmap.put("WAYLAND_DISPLAY", "wayland-1");
1818 try envmap.put("DISPLAY", ":1");
1819 try envmap.put("DEBUGINFOD_URLS", " ");
1820 try envmap.put("XCURSOR_SIZE", "24");
1821
1822 var arena = std.heap.ArenaAllocator.init(allocator);
1823 defer arena.deinit();
1824 const environ = try createNullDelimitedEnvMap(arena.allocator(), &envmap);
1825
1826 try testing.expectEqual(@as(usize, 5), environ.len);
1827
1828 inline for (.{
1829 "HOME=/home/ifreund",
1830 "WAYLAND_DISPLAY=wayland-1",
1831 "DISPLAY=:1",
1832 "DEBUGINFOD_URLS= ",
1833 "XCURSOR_SIZE=24",
1834 }) |target| {
1835 for (environ) |variable| {
1836 if (mem.eql(u8, mem.span(variable orelse continue), target)) break;
1837 } else {
1838 try testing.expect(false); // Environment variable not found
1839 }
1840 }
1841}
1842
1843/// Caller must free result.
1844pub fn createWindowsEnvBlock(allocator: mem.Allocator, env_map: *const EnvMap) ![]u16 {
1845 // count bytes needed
1846 const max_chars_needed = x: {
1847 var max_chars_needed: usize = 4; // 4 for the final 4 null bytes
1848 var it = env_map.iterator();
1849 while (it.next()) |pair| {
1850 // +1 for '='
1851 // +1 for null byte
1852 max_chars_needed += pair.key_ptr.len + pair.value_ptr.len + 2;
1853 }
1854 break :x max_chars_needed;
1855 };
1856 const result = try allocator.alloc(u16, max_chars_needed);
1857 errdefer allocator.free(result);
1858
1859 var it = env_map.iterator();
1860 var i: usize = 0;
1861 while (it.next()) |pair| {
1862 i += try unicode.wtf8ToWtf16Le(result[i..], pair.key_ptr.*);
1863 result[i] = '=';
1864 i += 1;
1865 i += try unicode.wtf8ToWtf16Le(result[i..], pair.value_ptr.*);
1866 result[i] = 0;
1867 i += 1;
1868 }
1869 result[i] = 0;
1870 i += 1;
1871 result[i] = 0;
1872 i += 1;
1873 result[i] = 0;
1874 i += 1;
1875 result[i] = 0;
1876 i += 1;
1877 return try allocator.realloc(result, i);
1878}
lib/std/process/Child.zig created+1795
...@@ -0,0 +1,1795 @@
1const std = @import("../std.zig");
2const builtin = @import("builtin");
3const unicode = std.unicode;
4const fs = std.fs;
5const process = std.process;
6const File = std.fs.File;
7const windows = std.os.windows;
8const linux = std.os.linux;
9const posix = std.posix;
10const mem = std.mem;
11const EnvMap = std.process.EnvMap;
12const maxInt = std.math.maxInt;
13const assert = std.debug.assert;
14const native_os = builtin.os.tag;
15const ChildProcess = @This();
16
17pub const Id = switch (native_os) {
18 .windows => windows.HANDLE,
19 .wasi => void,
20 else => posix.pid_t,
21};
22
23/// Available after calling `spawn()`. This becomes `undefined` after calling `wait()`.
24/// On Windows this is the hProcess.
25/// On POSIX this is the pid.
26id: Id,
27thread_handle: if (native_os == .windows) windows.HANDLE else void,
28
29allocator: mem.Allocator,
30
31/// The writing end of the child process's standard input pipe.
32/// Usage requires `stdin_behavior == StdIo.Pipe`.
33/// Available after calling `spawn()`.
34stdin: ?File,
35
36/// The reading end of the child process's standard output pipe.
37/// Usage requires `stdout_behavior == StdIo.Pipe`.
38/// Available after calling `spawn()`.
39stdout: ?File,
40
41/// The reading end of the child process's standard error pipe.
42/// Usage requires `stderr_behavior == StdIo.Pipe`.
43/// Available after calling `spawn()`.
44stderr: ?File,
45
46/// Terminated state of the child process.
47/// Available after calling `wait()`.
48term: ?(SpawnError!Term),
49
50argv: []const []const u8,
51
52/// Leave as null to use the current env map using the supplied allocator.
53env_map: ?*const EnvMap,
54
55stdin_behavior: StdIo,
56stdout_behavior: StdIo,
57stderr_behavior: StdIo,
58
59/// Set to change the user id when spawning the child process.
60uid: if (native_os == .windows or native_os == .wasi) void else ?posix.uid_t,
61
62/// Set to change the group id when spawning the child process.
63gid: if (native_os == .windows or native_os == .wasi) void else ?posix.gid_t,
64
65/// Set to change the current working directory when spawning the child process.
66cwd: ?[]const u8,
67/// Set to change the current working directory when spawning the child process.
68/// This is not yet implemented for Windows. See https://github.com/ziglang/zig/issues/5190
69/// Once that is done, `cwd` will be deprecated in favor of this field.
70cwd_dir: ?fs.Dir = null,
71
72err_pipe: ?if (native_os == .windows) void else [2]posix.fd_t,
73
74expand_arg0: Arg0Expand,
75
76/// Darwin-only. Disable ASLR for the child process.
77disable_aslr: bool = false,
78
79/// Darwin-only. Start child process in suspended state as if SIGSTOP was sent.
80start_suspended: bool = false,
81
82/// Set to true to obtain rusage information for the child process.
83/// Depending on the target platform and implementation status, the
84/// requested statistics may or may not be available. If they are
85/// available, then the `resource_usage_statistics` field will be populated
86/// after calling `wait`.
87/// On Linux and Darwin, this obtains rusage statistics from wait4().
88request_resource_usage_statistics: bool = false,
89
90/// This is available after calling wait if
91/// `request_resource_usage_statistics` was set to `true` before calling
92/// `spawn`.
93resource_usage_statistics: ResourceUsageStatistics = .{},
94
95pub const ResourceUsageStatistics = struct {
96 rusage: @TypeOf(rusage_init) = rusage_init,
97
98 /// Returns the peak resident set size of the child process, in bytes,
99 /// if available.
100 pub inline fn getMaxRss(rus: ResourceUsageStatistics) ?usize {
101 switch (native_os) {
102 .linux => {
103 if (rus.rusage) |ru| {
104 return @as(usize, @intCast(ru.maxrss)) * 1024;
105 } else {
106 return null;
107 }
108 },
109 .windows => {
110 if (rus.rusage) |ru| {
111 return ru.PeakWorkingSetSize;
112 } else {
113 return null;
114 }
115 },
116 .macos, .ios => {
117 if (rus.rusage) |ru| {
118 // Darwin oddly reports in bytes instead of kilobytes.
119 return @as(usize, @intCast(ru.maxrss));
120 } else {
121 return null;
122 }
123 },
124 else => return null,
125 }
126 }
127
128 const rusage_init = switch (native_os) {
129 .linux, .macos, .ios => @as(?posix.rusage, null),
130 .windows => @as(?windows.VM_COUNTERS, null),
131 else => {},
132 };
133};
134
135pub const Arg0Expand = posix.Arg0Expand;
136
137pub const SpawnError = error{
138 OutOfMemory,
139
140 /// POSIX-only. `StdIo.Ignore` was selected and opening `/dev/null` returned ENODEV.
141 NoDevice,
142
143 /// Windows-only. `cwd` or `argv` was provided and it was invalid WTF-8.
144 /// https://simonsapin.github.io/wtf-8/
145 InvalidWtf8,
146
147 /// Windows-only. `cwd` was provided, but the path did not exist when spawning the child process.
148 CurrentWorkingDirectoryUnlinked,
149
150 /// Windows-only. NUL (U+0000), LF (U+000A), CR (U+000D) are not allowed
151 /// within arguments when executing a `.bat`/`.cmd` script.
152 /// - NUL/LF signifiies end of arguments, so anything afterwards
153 /// would be lost after execution.
154 /// - CR is stripped by `cmd.exe`, so any CR codepoints
155 /// would be lost after execution.
156 InvalidBatchScriptArg,
157} ||
158 posix.ExecveError ||
159 posix.SetIdError ||
160 posix.ChangeCurDirError ||
161 windows.CreateProcessError ||
162 windows.GetProcessMemoryInfoError ||
163 windows.WaitForSingleObjectError;
164
165pub const Term = union(enum) {
166 Exited: u8,
167 Signal: u32,
168 Stopped: u32,
169 Unknown: u32,
170};
171
172/// Behavior of the child process's standard input, output, and error
173/// streams.
174pub const StdIo = enum {
175 /// Inherit the stream from the parent process.
176 Inherit,
177
178 /// Pass a null stream to the child process.
179 /// This is /dev/null on POSIX and NUL on Windows.
180 Ignore,
181
182 /// Create a pipe for the stream.
183 /// The corresponding field (`stdout`, `stderr`, or `stdin`)
184 /// will be assigned a `File` object that can be used
185 /// to read from or write to the pipe.
186 Pipe,
187
188 /// Close the stream after the child process spawns.
189 Close,
190};
191
192/// First argument in argv is the executable.
193pub fn init(argv: []const []const u8, allocator: mem.Allocator) ChildProcess {
194 return .{
195 .allocator = allocator,
196 .argv = argv,
197 .id = undefined,
198 .thread_handle = undefined,
199 .err_pipe = null,
200 .term = null,
201 .env_map = null,
202 .cwd = null,
203 .uid = if (native_os == .windows or native_os == .wasi) {} else null,
204 .gid = if (native_os == .windows or native_os == .wasi) {} else null,
205 .stdin = null,
206 .stdout = null,
207 .stderr = null,
208 .stdin_behavior = StdIo.Inherit,
209 .stdout_behavior = StdIo.Inherit,
210 .stderr_behavior = StdIo.Inherit,
211 .expand_arg0 = .no_expand,
212 };
213}
214
215pub fn setUserName(self: *ChildProcess, name: []const u8) !void {
216 const user_info = try process.getUserInfo(name);
217 self.uid = user_info.uid;
218 self.gid = user_info.gid;
219}
220
221/// On success must call `kill` or `wait`.
222/// After spawning the `id` is available.
223pub fn spawn(self: *ChildProcess) SpawnError!void {
224 if (!process.can_spawn) {
225 @compileError("the target operating system cannot spawn processes");
226 }
227
228 if (native_os == .windows) {
229 return self.spawnWindows();
230 } else {
231 return self.spawnPosix();
232 }
233}
234
235pub fn spawnAndWait(self: *ChildProcess) SpawnError!Term {
236 try self.spawn();
237 return self.wait();
238}
239
240/// Forcibly terminates child process and then cleans up all resources.
241pub fn kill(self: *ChildProcess) !Term {
242 if (native_os == .windows) {
243 return self.killWindows(1);
244 } else {
245 return self.killPosix();
246 }
247}
248
249pub fn killWindows(self: *ChildProcess, exit_code: windows.UINT) !Term {
250 if (self.term) |term| {
251 self.cleanupStreams();
252 return term;
253 }
254
255 windows.TerminateProcess(self.id, exit_code) catch |err| switch (err) {
256 error.PermissionDenied => {
257 // Usually when TerminateProcess triggers a ACCESS_DENIED error, it
258 // indicates that the process has already exited, but there may be
259 // some rare edge cases where our process handle no longer has the
260 // PROCESS_TERMINATE access right, so let's do another check to make
261 // sure the process is really no longer running:
262 windows.WaitForSingleObjectEx(self.id, 0, false) catch return err;
263 return error.AlreadyTerminated;
264 },
265 else => return err,
266 };
267 try self.waitUnwrappedWindows();
268 return self.term.?;
269}
270
271pub fn killPosix(self: *ChildProcess) !Term {
272 if (self.term) |term| {
273 self.cleanupStreams();
274 return term;
275 }
276 posix.kill(self.id, posix.SIG.TERM) catch |err| switch (err) {
277 error.ProcessNotFound => return error.AlreadyTerminated,
278 else => return err,
279 };
280 try self.waitUnwrapped();
281 return self.term.?;
282}
283
284/// Blocks until child process terminates and then cleans up all resources.
285pub fn wait(self: *ChildProcess) !Term {
286 const term = if (native_os == .windows)
287 try self.waitWindows()
288 else
289 try self.waitPosix();
290
291 self.id = undefined;
292
293 return term;
294}
295
296pub const RunResult = struct {
297 term: Term,
298 stdout: []u8,
299 stderr: []u8,
300};
301
302fn fifoToOwnedArrayList(fifo: *std.io.PollFifo) std.ArrayList(u8) {
303 if (fifo.head > 0) {
304 @memcpy(fifo.buf[0..fifo.count], fifo.buf[fifo.head..][0..fifo.count]);
305 }
306 const result = std.ArrayList(u8){
307 .items = fifo.buf[0..fifo.count],
308 .capacity = fifo.buf.len,
309 .allocator = fifo.allocator,
310 };
311 fifo.* = std.io.PollFifo.init(fifo.allocator);
312 return result;
313}
314
315/// Collect the output from the process's stdout and stderr. Will return once all output
316/// has been collected. This does not mean that the process has ended. `wait` should still
317/// be called to wait for and clean up the process.
318///
319/// The process must be started with stdout_behavior and stderr_behavior == .Pipe
320pub fn collectOutput(
321 child: ChildProcess,
322 stdout: *std.ArrayList(u8),
323 stderr: *std.ArrayList(u8),
324 max_output_bytes: usize,
325) !void {
326 assert(child.stdout_behavior == .Pipe);
327 assert(child.stderr_behavior == .Pipe);
328
329 // we could make this work with multiple allocators but YAGNI
330 if (stdout.allocator.ptr != stderr.allocator.ptr or
331 stdout.allocator.vtable != stderr.allocator.vtable)
332 {
333 unreachable; // ChildProcess.collectOutput only supports 1 allocator
334 }
335
336 var poller = std.io.poll(stdout.allocator, enum { stdout, stderr }, .{
337 .stdout = child.stdout.?,
338 .stderr = child.stderr.?,
339 });
340 defer poller.deinit();
341
342 while (try poller.poll()) {
343 if (poller.fifo(.stdout).count > max_output_bytes)
344 return error.StdoutStreamTooLong;
345 if (poller.fifo(.stderr).count > max_output_bytes)
346 return error.StderrStreamTooLong;
347 }
348
349 stdout.* = fifoToOwnedArrayList(poller.fifo(.stdout));
350 stderr.* = fifoToOwnedArrayList(poller.fifo(.stderr));
351}
352
353pub const RunError = posix.GetCwdError || posix.ReadError || SpawnError || posix.PollError || error{
354 StdoutStreamTooLong,
355 StderrStreamTooLong,
356};
357
358/// Spawns a child process, waits for it, collecting stdout and stderr, and then returns.
359/// If it succeeds, the caller owns result.stdout and result.stderr memory.
360pub fn run(args: struct {
361 allocator: mem.Allocator,
362 argv: []const []const u8,
363 cwd: ?[]const u8 = null,
364 cwd_dir: ?fs.Dir = null,
365 env_map: ?*const EnvMap = null,
366 max_output_bytes: usize = 50 * 1024,
367 expand_arg0: Arg0Expand = .no_expand,
368}) RunError!RunResult {
369 var child = ChildProcess.init(args.argv, args.allocator);
370 child.stdin_behavior = .Ignore;
371 child.stdout_behavior = .Pipe;
372 child.stderr_behavior = .Pipe;
373 child.cwd = args.cwd;
374 child.cwd_dir = args.cwd_dir;
375 child.env_map = args.env_map;
376 child.expand_arg0 = args.expand_arg0;
377
378 var stdout = std.ArrayList(u8).init(args.allocator);
379 var stderr = std.ArrayList(u8).init(args.allocator);
380 errdefer {
381 stdout.deinit();
382 stderr.deinit();
383 }
384
385 try child.spawn();
386 try child.collectOutput(&stdout, &stderr, args.max_output_bytes);
387
388 return RunResult{
389 .term = try child.wait(),
390 .stdout = try stdout.toOwnedSlice(),
391 .stderr = try stderr.toOwnedSlice(),
392 };
393}
394
395fn waitWindows(self: *ChildProcess) !Term {
396 if (self.term) |term| {
397 self.cleanupStreams();
398 return term;
399 }
400
401 try self.waitUnwrappedWindows();
402 return self.term.?;
403}
404
405fn waitPosix(self: *ChildProcess) !Term {
406 if (self.term) |term| {
407 self.cleanupStreams();
408 return term;
409 }
410
411 try self.waitUnwrapped();
412 return self.term.?;
413}
414
415fn waitUnwrappedWindows(self: *ChildProcess) !void {
416 const result = windows.WaitForSingleObjectEx(self.id, windows.INFINITE, false);
417
418 self.term = @as(SpawnError!Term, x: {
419 var exit_code: windows.DWORD = undefined;
420 if (windows.kernel32.GetExitCodeProcess(self.id, &exit_code) == 0) {
421 break :x Term{ .Unknown = 0 };
422 } else {
423 break :x Term{ .Exited = @as(u8, @truncate(exit_code)) };
424 }
425 });
426
427 if (self.request_resource_usage_statistics) {
428 self.resource_usage_statistics.rusage = try windows.GetProcessMemoryInfo(self.id);
429 }
430
431 posix.close(self.id);
432 posix.close(self.thread_handle);
433 self.cleanupStreams();
434 return result;
435}
436
437fn waitUnwrapped(self: *ChildProcess) !void {
438 const res: posix.WaitPidResult = res: {
439 if (self.request_resource_usage_statistics) {
440 switch (native_os) {
441 .linux, .macos, .ios => {
442 var ru: posix.rusage = undefined;
443 const res = posix.wait4(self.id, 0, &ru);
444 self.resource_usage_statistics.rusage = ru;
445 break :res res;
446 },
447 else => {},
448 }
449 }
450
451 break :res posix.waitpid(self.id, 0);
452 };
453 const status = res.status;
454 self.cleanupStreams();
455 self.handleWaitResult(status);
456}
457
458fn handleWaitResult(self: *ChildProcess, status: u32) void {
459 self.term = self.cleanupAfterWait(status);
460}
461
462fn cleanupStreams(self: *ChildProcess) void {
463 if (self.stdin) |*stdin| {
464 stdin.close();
465 self.stdin = null;
466 }
467 if (self.stdout) |*stdout| {
468 stdout.close();
469 self.stdout = null;
470 }
471 if (self.stderr) |*stderr| {
472 stderr.close();
473 self.stderr = null;
474 }
475}
476
477fn cleanupAfterWait(self: *ChildProcess, status: u32) !Term {
478 if (self.err_pipe) |err_pipe| {
479 defer destroyPipe(err_pipe);
480
481 if (native_os == .linux) {
482 var fd = [1]posix.pollfd{posix.pollfd{
483 .fd = err_pipe[0],
484 .events = posix.POLL.IN,
485 .revents = undefined,
486 }};
487
488 // Check if the eventfd buffer stores a non-zero value by polling
489 // it, that's the error code returned by the child process.
490 _ = posix.poll(&fd, 0) catch unreachable;
491
492 // According to eventfd(2) the descriptor is readable if the counter
493 // has a value greater than 0
494 if ((fd[0].revents & posix.POLL.IN) != 0) {
495 const err_int = try readIntFd(err_pipe[0]);
496 return @as(SpawnError, @errorCast(@errorFromInt(err_int)));
497 }
498 } else {
499 // Write maxInt(ErrInt) to the write end of the err_pipe. This is after
500 // waitpid, so this write is guaranteed to be after the child
501 // pid potentially wrote an error. This way we can do a blocking
502 // read on the error pipe and either get maxInt(ErrInt) (no error) or
503 // an error code.
504 try writeIntFd(err_pipe[1], maxInt(ErrInt));
505 const err_int = try readIntFd(err_pipe[0]);
506 // Here we potentially return the fork child's error from the parent
507 // pid.
508 if (err_int != maxInt(ErrInt)) {
509 return @as(SpawnError, @errorCast(@errorFromInt(err_int)));
510 }
511 }
512 }
513
514 return statusToTerm(status);
515}
516
517fn statusToTerm(status: u32) Term {
518 return if (posix.W.IFEXITED(status))
519 Term{ .Exited = posix.W.EXITSTATUS(status) }
520 else if (posix.W.IFSIGNALED(status))
521 Term{ .Signal = posix.W.TERMSIG(status) }
522 else if (posix.W.IFSTOPPED(status))
523 Term{ .Stopped = posix.W.STOPSIG(status) }
524 else
525 Term{ .Unknown = status };
526}
527
528fn spawnPosix(self: *ChildProcess) SpawnError!void {
529 const pipe_flags: posix.O = .{};
530 const stdin_pipe = if (self.stdin_behavior == StdIo.Pipe) try posix.pipe2(pipe_flags) else undefined;
531 errdefer if (self.stdin_behavior == StdIo.Pipe) {
532 destroyPipe(stdin_pipe);
533 };
534
535 const stdout_pipe = if (self.stdout_behavior == StdIo.Pipe) try posix.pipe2(pipe_flags) else undefined;
536 errdefer if (self.stdout_behavior == StdIo.Pipe) {
537 destroyPipe(stdout_pipe);
538 };
539
540 const stderr_pipe = if (self.stderr_behavior == StdIo.Pipe) try posix.pipe2(pipe_flags) else undefined;
541 errdefer if (self.stderr_behavior == StdIo.Pipe) {
542 destroyPipe(stderr_pipe);
543 };
544
545 const any_ignore = (self.stdin_behavior == StdIo.Ignore or self.stdout_behavior == StdIo.Ignore or self.stderr_behavior == StdIo.Ignore);
546 const dev_null_fd = if (any_ignore)
547 posix.openZ("/dev/null", .{ .ACCMODE = .RDWR }, 0) catch |err| switch (err) {
548 error.PathAlreadyExists => unreachable,
549 error.NoSpaceLeft => unreachable,
550 error.FileTooBig => unreachable,
551 error.DeviceBusy => unreachable,
552 error.FileLocksNotSupported => unreachable,
553 error.BadPathName => unreachable, // Windows-only
554 error.WouldBlock => unreachable,
555 error.NetworkNotFound => unreachable, // Windows-only
556 else => |e| return e,
557 }
558 else
559 undefined;
560 defer {
561 if (any_ignore) posix.close(dev_null_fd);
562 }
563
564 var arena_allocator = std.heap.ArenaAllocator.init(self.allocator);
565 defer arena_allocator.deinit();
566 const arena = arena_allocator.allocator();
567
568 // The POSIX standard does not allow malloc() between fork() and execve(),
569 // and `self.allocator` may be a libc allocator.
570 // I have personally observed the child process deadlocking when it tries
571 // to call malloc() due to a heap allocation between fork() and execve(),
572 // in musl v1.1.24.
573 // Additionally, we want to reduce the number of possible ways things
574 // can fail between fork() and execve().
575 // Therefore, we do all the allocation for the execve() before the fork().
576 // This means we must do the null-termination of argv and env vars here.
577 const argv_buf = try arena.allocSentinel(?[*:0]const u8, self.argv.len, null);
578 for (self.argv, 0..) |arg, i| argv_buf[i] = (try arena.dupeZ(u8, arg)).ptr;
579
580 const envp = m: {
581 if (self.env_map) |env_map| {
582 const envp_buf = try process.createNullDelimitedEnvMap(arena, env_map);
583 break :m envp_buf.ptr;
584 } else if (builtin.link_libc) {
585 break :m std.c.environ;
586 } else if (builtin.output_mode == .Exe) {
587 // Then we have Zig start code and this works.
588 // TODO type-safety for null-termination of `os.environ`.
589 break :m @as([*:null]const ?[*:0]const u8, @ptrCast(std.os.environ.ptr));
590 } else {
591 // TODO come up with a solution for this.
592 @compileError("missing std lib enhancement: ChildProcess implementation has no way to collect the environment variables to forward to the child process");
593 }
594 };
595
596 // This pipe is used to communicate errors between the time of fork
597 // and execve from the child process to the parent process.
598 const err_pipe = blk: {
599 if (native_os == .linux) {
600 const fd = try posix.eventfd(0, linux.EFD.CLOEXEC);
601 // There's no distinction between the readable and the writeable
602 // end with eventfd
603 break :blk [2]posix.fd_t{ fd, fd };
604 } else {
605 break :blk try posix.pipe2(.{ .CLOEXEC = true });
606 }
607 };
608 errdefer destroyPipe(err_pipe);
609
610 const pid_result = try posix.fork();
611 if (pid_result == 0) {
612 // we are the child
613 setUpChildIo(self.stdin_behavior, stdin_pipe[0], posix.STDIN_FILENO, dev_null_fd) catch |err| forkChildErrReport(err_pipe[1], err);
614 setUpChildIo(self.stdout_behavior, stdout_pipe[1], posix.STDOUT_FILENO, dev_null_fd) catch |err| forkChildErrReport(err_pipe[1], err);
615 setUpChildIo(self.stderr_behavior, stderr_pipe[1], posix.STDERR_FILENO, dev_null_fd) catch |err| forkChildErrReport(err_pipe[1], err);
616
617 if (self.stdin_behavior == .Pipe) {
618 posix.close(stdin_pipe[0]);
619 posix.close(stdin_pipe[1]);
620 }
621 if (self.stdout_behavior == .Pipe) {
622 posix.close(stdout_pipe[0]);
623 posix.close(stdout_pipe[1]);
624 }
625 if (self.stderr_behavior == .Pipe) {
626 posix.close(stderr_pipe[0]);
627 posix.close(stderr_pipe[1]);
628 }
629
630 if (self.cwd_dir) |cwd| {
631 posix.fchdir(cwd.fd) catch |err| forkChildErrReport(err_pipe[1], err);
632 } else if (self.cwd) |cwd| {
633 posix.chdir(cwd) catch |err| forkChildErrReport(err_pipe[1], err);
634 }
635
636 if (self.gid) |gid| {
637 posix.setregid(gid, gid) catch |err| forkChildErrReport(err_pipe[1], err);
638 }
639
640 if (self.uid) |uid| {
641 posix.setreuid(uid, uid) catch |err| forkChildErrReport(err_pipe[1], err);
642 }
643
644 const err = switch (self.expand_arg0) {
645 .expand => posix.execvpeZ_expandArg0(.expand, argv_buf.ptr[0].?, argv_buf.ptr, envp),
646 .no_expand => posix.execvpeZ_expandArg0(.no_expand, argv_buf.ptr[0].?, argv_buf.ptr, envp),
647 };
648 forkChildErrReport(err_pipe[1], err);
649 }
650
651 // we are the parent
652 const pid: i32 = @intCast(pid_result);
653 if (self.stdin_behavior == StdIo.Pipe) {
654 self.stdin = File{ .handle = stdin_pipe[1] };
655 } else {
656 self.stdin = null;
657 }
658 if (self.stdout_behavior == StdIo.Pipe) {
659 self.stdout = File{ .handle = stdout_pipe[0] };
660 } else {
661 self.stdout = null;
662 }
663 if (self.stderr_behavior == StdIo.Pipe) {
664 self.stderr = File{ .handle = stderr_pipe[0] };
665 } else {
666 self.stderr = null;
667 }
668
669 self.id = pid;
670 self.err_pipe = err_pipe;
671 self.term = null;
672
673 if (self.stdin_behavior == StdIo.Pipe) {
674 posix.close(stdin_pipe[0]);
675 }
676 if (self.stdout_behavior == StdIo.Pipe) {
677 posix.close(stdout_pipe[1]);
678 }
679 if (self.stderr_behavior == StdIo.Pipe) {
680 posix.close(stderr_pipe[1]);
681 }
682}
683
684fn spawnWindows(self: *ChildProcess) SpawnError!void {
685 var saAttr = windows.SECURITY_ATTRIBUTES{
686 .nLength = @sizeOf(windows.SECURITY_ATTRIBUTES),
687 .bInheritHandle = windows.TRUE,
688 .lpSecurityDescriptor = null,
689 };
690
691 const any_ignore = (self.stdin_behavior == StdIo.Ignore or self.stdout_behavior == StdIo.Ignore or self.stderr_behavior == StdIo.Ignore);
692
693 const nul_handle = if (any_ignore)
694 // "\Device\Null" or "\??\NUL"
695 windows.OpenFile(&[_]u16{ '\\', 'D', 'e', 'v', 'i', 'c', 'e', '\\', 'N', 'u', 'l', 'l' }, .{
696 .access_mask = windows.GENERIC_READ | windows.GENERIC_WRITE | windows.SYNCHRONIZE,
697 .share_access = windows.FILE_SHARE_READ | windows.FILE_SHARE_WRITE | windows.FILE_SHARE_DELETE,
698 .sa = &saAttr,
699 .creation = windows.OPEN_EXISTING,
700 }) catch |err| switch (err) {
701 error.PathAlreadyExists => return error.Unexpected, // not possible for "NUL"
702 error.PipeBusy => return error.Unexpected, // not possible for "NUL"
703 error.FileNotFound => return error.Unexpected, // not possible for "NUL"
704 error.AccessDenied => return error.Unexpected, // not possible for "NUL"
705 error.NameTooLong => return error.Unexpected, // not possible for "NUL"
706 error.WouldBlock => return error.Unexpected, // not possible for "NUL"
707 error.NetworkNotFound => return error.Unexpected, // not possible for "NUL"
708 error.AntivirusInterference => return error.Unexpected, // not possible for "NUL"
709 else => |e| return e,
710 }
711 else
712 undefined;
713 defer {
714 if (any_ignore) posix.close(nul_handle);
715 }
716
717 var g_hChildStd_IN_Rd: ?windows.HANDLE = null;
718 var g_hChildStd_IN_Wr: ?windows.HANDLE = null;
719 switch (self.stdin_behavior) {
720 StdIo.Pipe => {
721 try windowsMakePipeIn(&g_hChildStd_IN_Rd, &g_hChildStd_IN_Wr, &saAttr);
722 },
723 StdIo.Ignore => {
724 g_hChildStd_IN_Rd = nul_handle;
725 },
726 StdIo.Inherit => {
727 g_hChildStd_IN_Rd = windows.GetStdHandle(windows.STD_INPUT_HANDLE) catch null;
728 },
729 StdIo.Close => {
730 g_hChildStd_IN_Rd = null;
731 },
732 }
733 errdefer if (self.stdin_behavior == StdIo.Pipe) {
734 windowsDestroyPipe(g_hChildStd_IN_Rd, g_hChildStd_IN_Wr);
735 };
736
737 var g_hChildStd_OUT_Rd: ?windows.HANDLE = null;
738 var g_hChildStd_OUT_Wr: ?windows.HANDLE = null;
739 switch (self.stdout_behavior) {
740 StdIo.Pipe => {
741 try windowsMakeAsyncPipe(&g_hChildStd_OUT_Rd, &g_hChildStd_OUT_Wr, &saAttr);
742 },
743 StdIo.Ignore => {
744 g_hChildStd_OUT_Wr = nul_handle;
745 },
746 StdIo.Inherit => {
747 g_hChildStd_OUT_Wr = windows.GetStdHandle(windows.STD_OUTPUT_HANDLE) catch null;
748 },
749 StdIo.Close => {
750 g_hChildStd_OUT_Wr = null;
751 },
752 }
753 errdefer if (self.stdout_behavior == StdIo.Pipe) {
754 windowsDestroyPipe(g_hChildStd_OUT_Rd, g_hChildStd_OUT_Wr);
755 };
756
757 var g_hChildStd_ERR_Rd: ?windows.HANDLE = null;
758 var g_hChildStd_ERR_Wr: ?windows.HANDLE = null;
759 switch (self.stderr_behavior) {
760 StdIo.Pipe => {
761 try windowsMakeAsyncPipe(&g_hChildStd_ERR_Rd, &g_hChildStd_ERR_Wr, &saAttr);
762 },
763 StdIo.Ignore => {
764 g_hChildStd_ERR_Wr = nul_handle;
765 },
766 StdIo.Inherit => {
767 g_hChildStd_ERR_Wr = windows.GetStdHandle(windows.STD_ERROR_HANDLE) catch null;
768 },
769 StdIo.Close => {
770 g_hChildStd_ERR_Wr = null;
771 },
772 }
773 errdefer if (self.stderr_behavior == StdIo.Pipe) {
774 windowsDestroyPipe(g_hChildStd_ERR_Rd, g_hChildStd_ERR_Wr);
775 };
776
777 var siStartInfo = windows.STARTUPINFOW{
778 .cb = @sizeOf(windows.STARTUPINFOW),
779 .hStdError = g_hChildStd_ERR_Wr,
780 .hStdOutput = g_hChildStd_OUT_Wr,
781 .hStdInput = g_hChildStd_IN_Rd,
782 .dwFlags = windows.STARTF_USESTDHANDLES,
783
784 .lpReserved = null,
785 .lpDesktop = null,
786 .lpTitle = null,
787 .dwX = 0,
788 .dwY = 0,
789 .dwXSize = 0,
790 .dwYSize = 0,
791 .dwXCountChars = 0,
792 .dwYCountChars = 0,
793 .dwFillAttribute = 0,
794 .wShowWindow = 0,
795 .cbReserved2 = 0,
796 .lpReserved2 = null,
797 };
798 var piProcInfo: windows.PROCESS_INFORMATION = undefined;
799
800 const cwd_w = if (self.cwd) |cwd| try unicode.wtf8ToWtf16LeAllocZ(self.allocator, cwd) else null;
801 defer if (cwd_w) |cwd| self.allocator.free(cwd);
802 const cwd_w_ptr = if (cwd_w) |cwd| cwd.ptr else null;
803
804 const maybe_envp_buf = if (self.env_map) |env_map| try process.createWindowsEnvBlock(self.allocator, env_map) else null;
805 defer if (maybe_envp_buf) |envp_buf| self.allocator.free(envp_buf);
806 const envp_ptr = if (maybe_envp_buf) |envp_buf| envp_buf.ptr else null;
807
808 const app_name_wtf8 = self.argv[0];
809 const app_name_is_absolute = fs.path.isAbsolute(app_name_wtf8);
810
811 // the cwd set in ChildProcess is in effect when choosing the executable path
812 // to match posix semantics
813 var cwd_path_w_needs_free = false;
814 const cwd_path_w = x: {
815 // If the app name is absolute, then we need to use its dirname as the cwd
816 if (app_name_is_absolute) {
817 cwd_path_w_needs_free = true;
818 const dir = fs.path.dirname(app_name_wtf8).?;
819 break :x try unicode.wtf8ToWtf16LeAllocZ(self.allocator, dir);
820 } else if (self.cwd) |cwd| {
821 cwd_path_w_needs_free = true;
822 break :x try unicode.wtf8ToWtf16LeAllocZ(self.allocator, cwd);
823 } else {
824 break :x &[_:0]u16{}; // empty for cwd
825 }
826 };
827 defer if (cwd_path_w_needs_free) self.allocator.free(cwd_path_w);
828
829 // If the app name has more than just a filename, then we need to separate that
830 // into the basename and dirname and use the dirname as an addition to the cwd
831 // path. This is because NtQueryDirectoryFile cannot accept FileName params with
832 // path separators.
833 const app_basename_wtf8 = fs.path.basename(app_name_wtf8);
834 // If the app name is absolute, then the cwd will already have the app's dirname in it,
835 // so only populate app_dirname if app name is a relative path with > 0 path separators.
836 const maybe_app_dirname_wtf8 = if (!app_name_is_absolute) fs.path.dirname(app_name_wtf8) else null;
837 const app_dirname_w: ?[:0]u16 = x: {
838 if (maybe_app_dirname_wtf8) |app_dirname_wtf8| {
839 break :x try unicode.wtf8ToWtf16LeAllocZ(self.allocator, app_dirname_wtf8);
840 }
841 break :x null;
842 };
843 defer if (app_dirname_w != null) self.allocator.free(app_dirname_w.?);
844
845 const app_name_w = try unicode.wtf8ToWtf16LeAllocZ(self.allocator, app_basename_wtf8);
846 defer self.allocator.free(app_name_w);
847
848 run: {
849 const PATH: [:0]const u16 = process.getenvW(unicode.utf8ToUtf16LeStringLiteral("PATH")) orelse &[_:0]u16{};
850 const PATHEXT: [:0]const u16 = process.getenvW(unicode.utf8ToUtf16LeStringLiteral("PATHEXT")) orelse &[_:0]u16{};
851
852 // In case the command ends up being a .bat/.cmd script, we need to escape things using the cmd.exe rules
853 // and invoke cmd.exe ourselves in order to mitigate arbitrary command execution from maliciously
854 // constructed arguments.
855 //
856 // We'll need to wait until we're actually trying to run the command to know for sure
857 // if the resolved command has the `.bat` or `.cmd` extension, so we defer actually
858 // serializing the command line until we determine how it should be serialized.
859 var cmd_line_cache = WindowsCommandLineCache.init(self.allocator, self.argv);
860 defer cmd_line_cache.deinit();
861
862 var app_buf = std.ArrayListUnmanaged(u16){};
863 defer app_buf.deinit(self.allocator);
864
865 try app_buf.appendSlice(self.allocator, app_name_w);
866
867 var dir_buf = std.ArrayListUnmanaged(u16){};
868 defer dir_buf.deinit(self.allocator);
869
870 if (cwd_path_w.len > 0) {
871 try dir_buf.appendSlice(self.allocator, cwd_path_w);
872 }
873 if (app_dirname_w) |app_dir| {
874 if (dir_buf.items.len > 0) try dir_buf.append(self.allocator, fs.path.sep);
875 try dir_buf.appendSlice(self.allocator, app_dir);
876 }
877 if (dir_buf.items.len > 0) {
878 // Need to normalize the path, openDirW can't handle things like double backslashes
879 const normalized_len = windows.normalizePath(u16, dir_buf.items) catch return error.BadPathName;
880 dir_buf.shrinkRetainingCapacity(normalized_len);
881 }
882
883 windowsCreateProcessPathExt(self.allocator, &dir_buf, &app_buf, PATHEXT, &cmd_line_cache, envp_ptr, cwd_w_ptr, &siStartInfo, &piProcInfo) catch |no_path_err| {
884 const original_err = switch (no_path_err) {
885 // argv[0] contains unsupported characters that will never resolve to a valid exe.
886 error.InvalidArg0 => return error.FileNotFound,
887 error.FileNotFound, error.InvalidExe, error.AccessDenied => |e| e,
888 error.UnrecoverableInvalidExe => return error.InvalidExe,
889 else => |e| return e,
890 };
891
892 // If the app name had path separators, that disallows PATH searching,
893 // and there's no need to search the PATH if the app name is absolute.
894 // We still search the path if the cwd is absolute because of the
895 // "cwd set in ChildProcess is in effect when choosing the executable path
896 // to match posix semantics" behavior--we don't want to skip searching
897 // the PATH just because we were trying to set the cwd of the child process.
898 if (app_dirname_w != null or app_name_is_absolute) {
899 return original_err;
900 }
901
902 var it = mem.tokenizeScalar(u16, PATH, ';');
903 while (it.next()) |search_path| {
904 dir_buf.clearRetainingCapacity();
905 try dir_buf.appendSlice(self.allocator, search_path);
906 // Need to normalize the path, some PATH values can contain things like double
907 // backslashes which openDirW can't handle
908 const normalized_len = windows.normalizePath(u16, dir_buf.items) catch continue;
909 dir_buf.shrinkRetainingCapacity(normalized_len);
910
911 if (windowsCreateProcessPathExt(self.allocator, &dir_buf, &app_buf, PATHEXT, &cmd_line_cache, envp_ptr, cwd_w_ptr, &siStartInfo, &piProcInfo)) {
912 break :run;
913 } else |err| switch (err) {
914 // argv[0] contains unsupported characters that will never resolve to a valid exe.
915 error.InvalidArg0 => return error.FileNotFound,
916 error.FileNotFound, error.AccessDenied, error.InvalidExe => continue,
917 error.UnrecoverableInvalidExe => return error.InvalidExe,
918 else => |e| return e,
919 }
920 } else {
921 return original_err;
922 }
923 };
924 }
925
926 if (g_hChildStd_IN_Wr) |h| {
927 self.stdin = File{ .handle = h };
928 } else {
929 self.stdin = null;
930 }
931 if (g_hChildStd_OUT_Rd) |h| {
932 self.stdout = File{ .handle = h };
933 } else {
934 self.stdout = null;
935 }
936 if (g_hChildStd_ERR_Rd) |h| {
937 self.stderr = File{ .handle = h };
938 } else {
939 self.stderr = null;
940 }
941
942 self.id = piProcInfo.hProcess;
943 self.thread_handle = piProcInfo.hThread;
944 self.term = null;
945
946 if (self.stdin_behavior == StdIo.Pipe) {
947 posix.close(g_hChildStd_IN_Rd.?);
948 }
949 if (self.stderr_behavior == StdIo.Pipe) {
950 posix.close(g_hChildStd_ERR_Wr.?);
951 }
952 if (self.stdout_behavior == StdIo.Pipe) {
953 posix.close(g_hChildStd_OUT_Wr.?);
954 }
955}
956
957fn setUpChildIo(stdio: StdIo, pipe_fd: i32, std_fileno: i32, dev_null_fd: i32) !void {
958 switch (stdio) {
959 .Pipe => try posix.dup2(pipe_fd, std_fileno),
960 .Close => posix.close(std_fileno),
961 .Inherit => {},
962 .Ignore => try posix.dup2(dev_null_fd, std_fileno),
963 }
964}
965
966fn destroyPipe(pipe: [2]posix.fd_t) void {
967 posix.close(pipe[0]);
968 if (pipe[0] != pipe[1]) posix.close(pipe[1]);
969}
970
971// Child of fork calls this to report an error to the fork parent.
972// Then the child exits.
973fn forkChildErrReport(fd: i32, err: ChildProcess.SpawnError) noreturn {
974 writeIntFd(fd, @as(ErrInt, @intFromError(err))) catch {};
975 // If we're linking libc, some naughty applications may have registered atexit handlers
976 // which we really do not want to run in the fork child. I caught LLVM doing this and
977 // it caused a deadlock instead of doing an exit syscall. In the words of Avril Lavigne,
978 // "Why'd you have to go and make things so complicated?"
979 if (builtin.link_libc) {
980 // The _exit(2) function does nothing but make the exit syscall, unlike exit(3)
981 std.c._exit(1);
982 }
983 posix.exit(1);
984}
985
986fn writeIntFd(fd: i32, value: ErrInt) !void {
987 const file: File = .{ .handle = fd };
988 file.writer().writeInt(u64, @intCast(value), .little) catch return error.SystemResources;
989}
990
991fn readIntFd(fd: i32) !ErrInt {
992 const file: File = .{ .handle = fd };
993 return @intCast(file.reader().readInt(u64, .little) catch return error.SystemResources);
994}
995
996const ErrInt = std.meta.Int(.unsigned, @sizeOf(anyerror) * 8);
997
998/// Expects `app_buf` to contain exactly the app name, and `dir_buf` to contain exactly the dir path.
999/// After return, `app_buf` will always contain exactly the app name and `dir_buf` will always contain exactly the dir path.
1000/// Note: `app_buf` should not contain any leading path separators.
1001/// Note: If the dir is the cwd, dir_buf should be empty (len = 0).
1002fn windowsCreateProcessPathExt(
1003 allocator: mem.Allocator,
1004 dir_buf: *std.ArrayListUnmanaged(u16),
1005 app_buf: *std.ArrayListUnmanaged(u16),
1006 pathext: [:0]const u16,
1007 cmd_line_cache: *WindowsCommandLineCache,
1008 envp_ptr: ?[*]u16,
1009 cwd_ptr: ?[*:0]u16,
1010 lpStartupInfo: *windows.STARTUPINFOW,
1011 lpProcessInformation: *windows.PROCESS_INFORMATION,
1012) !void {
1013 const app_name_len = app_buf.items.len;
1014 const dir_path_len = dir_buf.items.len;
1015
1016 if (app_name_len == 0) return error.FileNotFound;
1017
1018 defer app_buf.shrinkRetainingCapacity(app_name_len);
1019 defer dir_buf.shrinkRetainingCapacity(dir_path_len);
1020
1021 // The name of the game here is to avoid CreateProcessW calls at all costs,
1022 // and only ever try calling it when we have a real candidate for execution.
1023 // Secondarily, we want to minimize the number of syscalls used when checking
1024 // for each PATHEXT-appended version of the app name.
1025 //
1026 // An overview of the technique used:
1027 // - Open the search directory for iteration (either cwd or a path from PATH)
1028 // - Use NtQueryDirectoryFile with a wildcard filename of `<app name>*` to
1029 // check if anything that could possibly match either the unappended version
1030 // of the app name or any of the versions with a PATHEXT value appended exists.
1031 // - If the wildcard NtQueryDirectoryFile call found nothing, we can exit early
1032 // without needing to use PATHEXT at all.
1033 //
1034 // This allows us to use a <open dir, NtQueryDirectoryFile, close dir> sequence
1035 // for any directory that doesn't contain any possible matches, instead of having
1036 // to use a separate look up for each individual filename combination (unappended +
1037 // each PATHEXT appended). For directories where the wildcard *does* match something,
1038 // we iterate the matches and take note of any that are either the unappended version,
1039 // or a version with a supported PATHEXT appended. We then try calling CreateProcessW
1040 // with the found versions in the appropriate order.
1041
1042 var dir = dir: {
1043 // needs to be null-terminated
1044 try dir_buf.append(allocator, 0);
1045 defer dir_buf.shrinkRetainingCapacity(dir_path_len);
1046 const dir_path_z = dir_buf.items[0 .. dir_buf.items.len - 1 :0];
1047 const prefixed_path = try windows.wToPrefixedFileW(null, dir_path_z);
1048 break :dir fs.cwd().openDirW(prefixed_path.span().ptr, .{ .iterate = true }) catch
1049 return error.FileNotFound;
1050 };
1051 defer dir.close();
1052
1053 // Add wildcard and null-terminator
1054 try app_buf.append(allocator, '*');
1055 try app_buf.append(allocator, 0);
1056 const app_name_wildcard = app_buf.items[0 .. app_buf.items.len - 1 :0];
1057
1058 // This 2048 is arbitrary, we just want it to be large enough to get multiple FILE_DIRECTORY_INFORMATION entries
1059 // returned per NtQueryDirectoryFile call.
1060 var file_information_buf: [2048]u8 align(@alignOf(windows.FILE_DIRECTORY_INFORMATION)) = undefined;
1061 const file_info_maximum_single_entry_size = @sizeOf(windows.FILE_DIRECTORY_INFORMATION) + (windows.NAME_MAX * 2);
1062 if (file_information_buf.len < file_info_maximum_single_entry_size) {
1063 @compileError("file_information_buf must be large enough to contain at least one maximum size FILE_DIRECTORY_INFORMATION entry");
1064 }
1065 var io_status: windows.IO_STATUS_BLOCK = undefined;
1066
1067 const num_supported_pathext = @typeInfo(CreateProcessSupportedExtension).Enum.fields.len;
1068 var pathext_seen = [_]bool{false} ** num_supported_pathext;
1069 var any_pathext_seen = false;
1070 var unappended_exists = false;
1071
1072 // Fully iterate the wildcard matches via NtQueryDirectoryFile and take note of all versions
1073 // of the app_name we should try to spawn.
1074 // Note: This is necessary because the order of the files returned is filesystem-dependent:
1075 // On NTFS, `blah.exe*` will always return `blah.exe` first if it exists.
1076 // On FAT32, it's possible for something like `blah.exe.obj` to be returned first.
1077 while (true) {
1078 const app_name_len_bytes = std.math.cast(u16, app_name_wildcard.len * 2) orelse return error.NameTooLong;
1079 var app_name_unicode_string = windows.UNICODE_STRING{
1080 .Length = app_name_len_bytes,
1081 .MaximumLength = app_name_len_bytes,
1082 .Buffer = @constCast(app_name_wildcard.ptr),
1083 };
1084 const rc = windows.ntdll.NtQueryDirectoryFile(
1085 dir.fd,
1086 null,
1087 null,
1088 null,
1089 &io_status,
1090 &file_information_buf,
1091 file_information_buf.len,
1092 .FileDirectoryInformation,
1093 windows.FALSE, // single result
1094 &app_name_unicode_string,
1095 windows.FALSE, // restart iteration
1096 );
1097
1098 // If we get nothing with the wildcard, then we can just bail out
1099 // as we know appending PATHEXT will not yield anything.
1100 switch (rc) {
1101 .SUCCESS => {},
1102 .NO_SUCH_FILE => return error.FileNotFound,
1103 .NO_MORE_FILES => break,
1104 .ACCESS_DENIED => return error.AccessDenied,
1105 else => return windows.unexpectedStatus(rc),
1106 }
1107
1108 // According to the docs, this can only happen if there is not enough room in the
1109 // buffer to write at least one complete FILE_DIRECTORY_INFORMATION entry.
1110 // Therefore, this condition should not be possible to hit with the buffer size we use.
1111 std.debug.assert(io_status.Information != 0);
1112
1113 var it = windows.FileInformationIterator(windows.FILE_DIRECTORY_INFORMATION){ .buf = &file_information_buf };
1114 while (it.next()) |info| {
1115 // Skip directories
1116 if (info.FileAttributes & windows.FILE_ATTRIBUTE_DIRECTORY != 0) continue;
1117 const filename = @as([*]u16, @ptrCast(&info.FileName))[0 .. info.FileNameLength / 2];
1118 // Because all results start with the app_name since we're using the wildcard `app_name*`,
1119 // if the length is equal to app_name then this is an exact match
1120 if (filename.len == app_name_len) {
1121 // Note: We can't break early here because it's possible that the unappended version
1122 // fails to spawn, in which case we still want to try the PATHEXT appended versions.
1123 unappended_exists = true;
1124 } else if (windowsCreateProcessSupportsExtension(filename[app_name_len..])) |pathext_ext| {
1125 pathext_seen[@intFromEnum(pathext_ext)] = true;
1126 any_pathext_seen = true;
1127 }
1128 }
1129 }
1130
1131 const unappended_err = unappended: {
1132 if (unappended_exists) {
1133 if (dir_path_len != 0) switch (dir_buf.items[dir_buf.items.len - 1]) {
1134 '/', '\\' => {},
1135 else => try dir_buf.append(allocator, fs.path.sep),
1136 };
1137 try dir_buf.appendSlice(allocator, app_buf.items[0..app_name_len]);
1138 try dir_buf.append(allocator, 0);
1139 const full_app_name = dir_buf.items[0 .. dir_buf.items.len - 1 :0];
1140
1141 const is_bat_or_cmd = bat_or_cmd: {
1142 const app_name = app_buf.items[0..app_name_len];
1143 const ext_start = std.mem.lastIndexOfScalar(u16, app_name, '.') orelse break :bat_or_cmd false;
1144 const ext = app_name[ext_start..];
1145 const ext_enum = windowsCreateProcessSupportsExtension(ext) orelse break :bat_or_cmd false;
1146 switch (ext_enum) {
1147 .cmd, .bat => break :bat_or_cmd true,
1148 else => break :bat_or_cmd false,
1149 }
1150 };
1151 const cmd_line_w = if (is_bat_or_cmd)
1152 try cmd_line_cache.scriptCommandLine(full_app_name)
1153 else
1154 try cmd_line_cache.commandLine();
1155 const app_name_w = if (is_bat_or_cmd)
1156 try cmd_line_cache.cmdExePath()
1157 else
1158 full_app_name;
1159
1160 if (windowsCreateProcess(app_name_w.ptr, cmd_line_w.ptr, envp_ptr, cwd_ptr, lpStartupInfo, lpProcessInformation)) |_| {
1161 return;
1162 } else |err| switch (err) {
1163 error.FileNotFound,
1164 error.AccessDenied,
1165 => break :unappended err,
1166 error.InvalidExe => {
1167 // On InvalidExe, if the extension of the app name is .exe then
1168 // it's treated as an unrecoverable error. Otherwise, it'll be
1169 // skipped as normal.
1170 const app_name = app_buf.items[0..app_name_len];
1171 const ext_start = std.mem.lastIndexOfScalar(u16, app_name, '.') orelse break :unappended err;
1172 const ext = app_name[ext_start..];
1173 if (windows.eqlIgnoreCaseWTF16(ext, unicode.utf8ToUtf16LeStringLiteral(".EXE"))) {
1174 return error.UnrecoverableInvalidExe;
1175 }
1176 break :unappended err;
1177 },
1178 else => return err,
1179 }
1180 }
1181 break :unappended error.FileNotFound;
1182 };
1183
1184 if (!any_pathext_seen) return unappended_err;
1185
1186 // Now try any PATHEXT appended versions that we've seen
1187 var ext_it = mem.tokenizeScalar(u16, pathext, ';');
1188 while (ext_it.next()) |ext| {
1189 const ext_enum = windowsCreateProcessSupportsExtension(ext) orelse continue;
1190 if (!pathext_seen[@intFromEnum(ext_enum)]) continue;
1191
1192 dir_buf.shrinkRetainingCapacity(dir_path_len);
1193 if (dir_path_len != 0) switch (dir_buf.items[dir_buf.items.len - 1]) {
1194 '/', '\\' => {},
1195 else => try dir_buf.append(allocator, fs.path.sep),
1196 };
1197 try dir_buf.appendSlice(allocator, app_buf.items[0..app_name_len]);
1198 try dir_buf.appendSlice(allocator, ext);
1199 try dir_buf.append(allocator, 0);
1200 const full_app_name = dir_buf.items[0 .. dir_buf.items.len - 1 :0];
1201
1202 const is_bat_or_cmd = switch (ext_enum) {
1203 .cmd, .bat => true,
1204 else => false,
1205 };
1206 const cmd_line_w = if (is_bat_or_cmd)
1207 try cmd_line_cache.scriptCommandLine(full_app_name)
1208 else
1209 try cmd_line_cache.commandLine();
1210 const app_name_w = if (is_bat_or_cmd)
1211 try cmd_line_cache.cmdExePath()
1212 else
1213 full_app_name;
1214
1215 if (windowsCreateProcess(app_name_w.ptr, cmd_line_w.ptr, envp_ptr, cwd_ptr, lpStartupInfo, lpProcessInformation)) |_| {
1216 return;
1217 } else |err| switch (err) {
1218 error.FileNotFound => continue,
1219 error.AccessDenied => continue,
1220 error.InvalidExe => {
1221 // On InvalidExe, if the extension of the app name is .exe then
1222 // it's treated as an unrecoverable error. Otherwise, it'll be
1223 // skipped as normal.
1224 if (windows.eqlIgnoreCaseWTF16(ext, unicode.utf8ToUtf16LeStringLiteral(".EXE"))) {
1225 return error.UnrecoverableInvalidExe;
1226 }
1227 continue;
1228 },
1229 else => return err,
1230 }
1231 }
1232
1233 return unappended_err;
1234}
1235
1236fn windowsCreateProcess(
1237 app_name: [*:0]u16,
1238 cmd_line: [*:0]u16,
1239 envp_ptr: ?[*]u16,
1240 cwd_ptr: ?[*:0]u16,
1241 lpStartupInfo: *windows.STARTUPINFOW,
1242 lpProcessInformation: *windows.PROCESS_INFORMATION,
1243) !void {
1244 // TODO the docs for environment pointer say:
1245 // > A pointer to the environment block for the new process. If this parameter
1246 // > is NULL, the new process uses the environment of the calling process.
1247 // > ...
1248 // > An environment block can contain either Unicode or ANSI characters. If
1249 // > the environment block pointed to by lpEnvironment contains Unicode
1250 // > characters, be sure that dwCreationFlags includes CREATE_UNICODE_ENVIRONMENT.
1251 // > If this parameter is NULL and the environment block of the parent process
1252 // > contains Unicode characters, you must also ensure that dwCreationFlags
1253 // > includes CREATE_UNICODE_ENVIRONMENT.
1254 // This seems to imply that we have to somehow know whether our process parent passed
1255 // CREATE_UNICODE_ENVIRONMENT if we want to pass NULL for the environment parameter.
1256 // Since we do not know this information that would imply that we must not pass NULL
1257 // for the parameter.
1258 // However this would imply that programs compiled with -DUNICODE could not pass
1259 // environment variables to programs that were not, which seems unlikely.
1260 // More investigation is needed.
1261 return windows.CreateProcessW(
1262 app_name,
1263 cmd_line,
1264 null,
1265 null,
1266 windows.TRUE,
1267 windows.CREATE_UNICODE_ENVIRONMENT,
1268 @as(?*anyopaque, @ptrCast(envp_ptr)),
1269 cwd_ptr,
1270 lpStartupInfo,
1271 lpProcessInformation,
1272 );
1273}
1274
1275fn windowsMakePipeIn(rd: *?windows.HANDLE, wr: *?windows.HANDLE, sattr: *const windows.SECURITY_ATTRIBUTES) !void {
1276 var rd_h: windows.HANDLE = undefined;
1277 var wr_h: windows.HANDLE = undefined;
1278 try windows.CreatePipe(&rd_h, &wr_h, sattr);
1279 errdefer windowsDestroyPipe(rd_h, wr_h);
1280 try windows.SetHandleInformation(wr_h, windows.HANDLE_FLAG_INHERIT, 0);
1281 rd.* = rd_h;
1282 wr.* = wr_h;
1283}
1284
1285fn windowsDestroyPipe(rd: ?windows.HANDLE, wr: ?windows.HANDLE) void {
1286 if (rd) |h| posix.close(h);
1287 if (wr) |h| posix.close(h);
1288}
1289
1290fn windowsMakeAsyncPipe(rd: *?windows.HANDLE, wr: *?windows.HANDLE, sattr: *const windows.SECURITY_ATTRIBUTES) !void {
1291 var tmp_bufw: [128]u16 = undefined;
1292
1293 // Anonymous pipes are built upon Named pipes.
1294 // https://docs.microsoft.com/en-us/windows/win32/api/namedpipeapi/nf-namedpipeapi-createpipe
1295 // Asynchronous (overlapped) read and write operations are not supported by anonymous pipes.
1296 // https://docs.microsoft.com/en-us/windows/win32/ipc/anonymous-pipe-operations
1297 const pipe_path = blk: {
1298 var tmp_buf: [128]u8 = undefined;
1299 // Forge a random path for the pipe.
1300 const pipe_path = std.fmt.bufPrintZ(
1301 &tmp_buf,
1302 "\\\\.\\pipe\\zig-childprocess-{d}-{d}",
1303 .{ windows.GetCurrentProcessId(), pipe_name_counter.fetchAdd(1, .monotonic) },
1304 ) catch unreachable;
1305 const len = std.unicode.wtf8ToWtf16Le(&tmp_bufw, pipe_path) catch unreachable;
1306 tmp_bufw[len] = 0;
1307 break :blk tmp_bufw[0..len :0];
1308 };
1309
1310 // Create the read handle that can be used with overlapped IO ops.
1311 const read_handle = windows.kernel32.CreateNamedPipeW(
1312 pipe_path.ptr,
1313 windows.PIPE_ACCESS_INBOUND | windows.FILE_FLAG_OVERLAPPED,
1314 windows.PIPE_TYPE_BYTE,
1315 1,
1316 4096,
1317 4096,
1318 0,
1319 sattr,
1320 );
1321 if (read_handle == windows.INVALID_HANDLE_VALUE) {
1322 switch (windows.kernel32.GetLastError()) {
1323 else => |err| return windows.unexpectedError(err),
1324 }
1325 }
1326 errdefer posix.close(read_handle);
1327
1328 var sattr_copy = sattr.*;
1329 const write_handle = windows.kernel32.CreateFileW(
1330 pipe_path.ptr,
1331 windows.GENERIC_WRITE,
1332 0,
1333 &sattr_copy,
1334 windows.OPEN_EXISTING,
1335 windows.FILE_ATTRIBUTE_NORMAL,
1336 null,
1337 );
1338 if (write_handle == windows.INVALID_HANDLE_VALUE) {
1339 switch (windows.kernel32.GetLastError()) {
1340 else => |err| return windows.unexpectedError(err),
1341 }
1342 }
1343 errdefer posix.close(write_handle);
1344
1345 try windows.SetHandleInformation(read_handle, windows.HANDLE_FLAG_INHERIT, 0);
1346
1347 rd.* = read_handle;
1348 wr.* = write_handle;
1349}
1350
1351var pipe_name_counter = std.atomic.Value(u32).init(1);
1352
1353// Should be kept in sync with `windowsCreateProcessSupportsExtension`
1354const CreateProcessSupportedExtension = enum {
1355 bat,
1356 cmd,
1357 com,
1358 exe,
1359};
1360
1361/// Case-insensitive WTF-16 lookup
1362fn windowsCreateProcessSupportsExtension(ext: []const u16) ?CreateProcessSupportedExtension {
1363 if (ext.len != 4) return null;
1364 const State = enum {
1365 start,
1366 dot,
1367 b,
1368 ba,
1369 c,
1370 cm,
1371 co,
1372 e,
1373 ex,
1374 };
1375 var state: State = .start;
1376 for (ext) |c| switch (state) {
1377 .start => switch (c) {
1378 '.' => state = .dot,
1379 else => return null,
1380 },
1381 .dot => switch (c) {
1382 'b', 'B' => state = .b,
1383 'c', 'C' => state = .c,
1384 'e', 'E' => state = .e,
1385 else => return null,
1386 },
1387 .b => switch (c) {
1388 'a', 'A' => state = .ba,
1389 else => return null,
1390 },
1391 .c => switch (c) {
1392 'm', 'M' => state = .cm,
1393 'o', 'O' => state = .co,
1394 else => return null,
1395 },
1396 .e => switch (c) {
1397 'x', 'X' => state = .ex,
1398 else => return null,
1399 },
1400 .ba => switch (c) {
1401 't', 'T' => return .bat,
1402 else => return null,
1403 },
1404 .cm => switch (c) {
1405 'd', 'D' => return .cmd,
1406 else => return null,
1407 },
1408 .co => switch (c) {
1409 'm', 'M' => return .com,
1410 else => return null,
1411 },
1412 .ex => switch (c) {
1413 'e', 'E' => return .exe,
1414 else => return null,
1415 },
1416 };
1417 return null;
1418}
1419
1420test windowsCreateProcessSupportsExtension {
1421 try std.testing.expectEqual(CreateProcessSupportedExtension.exe, windowsCreateProcessSupportsExtension(&[_]u16{ '.', 'e', 'X', 'e' }).?);
1422 try std.testing.expect(windowsCreateProcessSupportsExtension(&[_]u16{ '.', 'e', 'X', 'e', 'c' }) == null);
1423}
1424
1425/// Serializes argv into a WTF-16 encoded command-line string for use with CreateProcessW.
1426///
1427/// Serialization is done on-demand and the result is cached in order to allow for:
1428/// - Only serializing the particular type of command line needed (`.bat`/`.cmd`
1429/// command line serialization is different from `.exe`/etc)
1430/// - Reusing the serialized command lines if necessary (i.e. if the execution
1431/// of a command fails and the PATH is going to be continued to be searched
1432/// for more candidates)
1433const WindowsCommandLineCache = struct {
1434 cmd_line: ?[:0]u16 = null,
1435 script_cmd_line: ?[:0]u16 = null,
1436 cmd_exe_path: ?[:0]u16 = null,
1437 argv: []const []const u8,
1438 allocator: mem.Allocator,
1439
1440 fn init(allocator: mem.Allocator, argv: []const []const u8) WindowsCommandLineCache {
1441 return .{
1442 .allocator = allocator,
1443 .argv = argv,
1444 };
1445 }
1446
1447 fn deinit(self: *WindowsCommandLineCache) void {
1448 if (self.cmd_line) |cmd_line| self.allocator.free(cmd_line);
1449 if (self.script_cmd_line) |script_cmd_line| self.allocator.free(script_cmd_line);
1450 if (self.cmd_exe_path) |cmd_exe_path| self.allocator.free(cmd_exe_path);
1451 }
1452
1453 fn commandLine(self: *WindowsCommandLineCache) ![:0]u16 {
1454 if (self.cmd_line == null) {
1455 self.cmd_line = try argvToCommandLineWindows(self.allocator, self.argv);
1456 }
1457 return self.cmd_line.?;
1458 }
1459
1460 /// Not cached, since the path to the batch script will change during PATH searching.
1461 /// `script_path` should be as qualified as possible, e.g. if the PATH is being searched,
1462 /// then script_path should include both the search path and the script filename
1463 /// (this allows avoiding cmd.exe having to search the PATH again).
1464 fn scriptCommandLine(self: *WindowsCommandLineCache, script_path: []const u16) ![:0]u16 {
1465 if (self.script_cmd_line) |v| self.allocator.free(v);
1466 self.script_cmd_line = try argvToScriptCommandLineWindows(
1467 self.allocator,
1468 script_path,
1469 self.argv[1..],
1470 );
1471 return self.script_cmd_line.?;
1472 }
1473
1474 fn cmdExePath(self: *WindowsCommandLineCache) ![:0]u16 {
1475 if (self.cmd_exe_path == null) {
1476 self.cmd_exe_path = try windowsCmdExePath(self.allocator);
1477 }
1478 return self.cmd_exe_path.?;
1479 }
1480};
1481
1482fn windowsCmdExePath(allocator: mem.Allocator) error{ OutOfMemory, Unexpected }![:0]u16 {
1483 var buf = try std.ArrayListUnmanaged(u16).initCapacity(allocator, 128);
1484 errdefer buf.deinit(allocator);
1485 while (true) {
1486 const unused_slice = buf.unusedCapacitySlice();
1487 // TODO: Get the system directory from PEB.ReadOnlyStaticServerData
1488 const len = windows.kernel32.GetSystemDirectoryW(@ptrCast(unused_slice), @intCast(unused_slice.len));
1489 if (len == 0) {
1490 switch (windows.kernel32.GetLastError()) {
1491 else => |err| return windows.unexpectedError(err),
1492 }
1493 }
1494 if (len > unused_slice.len) {
1495 try buf.ensureUnusedCapacity(allocator, len);
1496 } else {
1497 buf.items.len = len;
1498 break;
1499 }
1500 }
1501 switch (buf.items[buf.items.len - 1]) {
1502 '/', '\\' => {},
1503 else => try buf.append(allocator, fs.path.sep),
1504 }
1505 try buf.appendSlice(allocator, unicode.utf8ToUtf16LeStringLiteral("cmd.exe"));
1506 return try buf.toOwnedSliceSentinel(allocator, 0);
1507}
1508
1509const ArgvToCommandLineError = error{ OutOfMemory, InvalidWtf8, InvalidArg0 };
1510
1511/// Serializes `argv` to a Windows command-line string suitable for passing to a child process and
1512/// parsing by the `CommandLineToArgvW` algorithm. The caller owns the returned slice.
1513fn argvToCommandLineWindows(
1514 allocator: mem.Allocator,
1515 argv: []const []const u8,
1516) ArgvToCommandLineError![:0]u16 {
1517 var buf = std.ArrayList(u8).init(allocator);
1518 defer buf.deinit();
1519
1520 if (argv.len != 0) {
1521 const arg0 = argv[0];
1522
1523 // The first argument must be quoted if it contains spaces or ASCII control characters
1524 // (excluding DEL). It also follows special quoting rules where backslashes have no special
1525 // interpretation, which makes it impossible to pass certain first arguments containing
1526 // double quotes to a child process without characters from the first argument leaking into
1527 // subsequent ones (which could have security implications).
1528 //
1529 // Empty arguments technically don't need quotes, but we quote them anyway for maximum
1530 // compatibility with different implementations of the 'CommandLineToArgvW' algorithm.
1531 //
1532 // Double quotes are illegal in paths on Windows, so for the sake of simplicity we reject
1533 // all first arguments containing double quotes, even ones that we could theoretically
1534 // serialize in unquoted form.
1535 var needs_quotes = arg0.len == 0;
1536 for (arg0) |c| {
1537 if (c <= ' ') {
1538 needs_quotes = true;
1539 } else if (c == '"') {
1540 return error.InvalidArg0;
1541 }
1542 }
1543 if (needs_quotes) {
1544 try buf.append('"');
1545 try buf.appendSlice(arg0);
1546 try buf.append('"');
1547 } else {
1548 try buf.appendSlice(arg0);
1549 }
1550
1551 for (argv[1..]) |arg| {
1552 try buf.append(' ');
1553
1554 // Subsequent arguments must be quoted if they contain spaces, tabs or double quotes,
1555 // or if they are empty. For simplicity and for maximum compatibility with different
1556 // implementations of the 'CommandLineToArgvW' algorithm, we also quote all ASCII
1557 // control characters (again, excluding DEL).
1558 needs_quotes = for (arg) |c| {
1559 if (c <= ' ' or c == '"') {
1560 break true;
1561 }
1562 } else arg.len == 0;
1563 if (!needs_quotes) {
1564 try buf.appendSlice(arg);
1565 continue;
1566 }
1567
1568 try buf.append('"');
1569 var backslash_count: usize = 0;
1570 for (arg) |byte| {
1571 switch (byte) {
1572 '\\' => {
1573 backslash_count += 1;
1574 },
1575 '"' => {
1576 try buf.appendNTimes('\\', backslash_count * 2 + 1);
1577 try buf.append('"');
1578 backslash_count = 0;
1579 },
1580 else => {
1581 try buf.appendNTimes('\\', backslash_count);
1582 try buf.append(byte);
1583 backslash_count = 0;
1584 },
1585 }
1586 }
1587 try buf.appendNTimes('\\', backslash_count * 2);
1588 try buf.append('"');
1589 }
1590 }
1591
1592 return try unicode.wtf8ToWtf16LeAllocZ(allocator, buf.items);
1593}
1594
1595test argvToCommandLineWindows {
1596 const t = testArgvToCommandLineWindows;
1597
1598 try t(&.{
1599 \\C:\Program Files\zig\zig.exe
1600 ,
1601 \\run
1602 ,
1603 \\.\src\main.zig
1604 ,
1605 \\-target
1606 ,
1607 \\x86_64-windows-gnu
1608 ,
1609 \\-O
1610 ,
1611 \\ReleaseSafe
1612 ,
1613 \\--
1614 ,
1615 \\--emoji=🗿
1616 ,
1617 \\--eval=new Regex("Dwayne \"The Rock\" Johnson")
1618 ,
1619 },
1620 \\"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\")"
1621 );
1622
1623 try t(&.{}, "");
1624 try t(&.{""}, "\"\"");
1625 try t(&.{" "}, "\" \"");
1626 try t(&.{"\t"}, "\"\t\"");
1627 try t(&.{"\x07"}, "\"\x07\"");
1628 try t(&.{"🦎"}, "🦎");
1629
1630 try t(
1631 &.{ "zig", "aa aa", "bb\tbb", "cc\ncc", "dd\r\ndd", "ee\x7Fee" },
1632 "zig \"aa aa\" \"bb\tbb\" \"cc\ncc\" \"dd\r\ndd\" ee\x7Fee",
1633 );
1634
1635 try t(
1636 &.{ "\\\\foo bar\\foo bar\\", "\\\\zig zag\\zig zag\\" },
1637 "\"\\\\foo bar\\foo bar\\\" \"\\\\zig zag\\zig zag\\\\\"",
1638 );
1639
1640 try std.testing.expectError(
1641 error.InvalidArg0,
1642 argvToCommandLineWindows(std.testing.allocator, &.{"\"quotes\"quotes\""}),
1643 );
1644 try std.testing.expectError(
1645 error.InvalidArg0,
1646 argvToCommandLineWindows(std.testing.allocator, &.{"quotes\"quotes"}),
1647 );
1648 try std.testing.expectError(
1649 error.InvalidArg0,
1650 argvToCommandLineWindows(std.testing.allocator, &.{"q u o t e s \" q u o t e s"}),
1651 );
1652}
1653
1654fn testArgvToCommandLineWindows(argv: []const []const u8, expected_cmd_line: []const u8) !void {
1655 const cmd_line_w = try argvToCommandLineWindows(std.testing.allocator, argv);
1656 defer std.testing.allocator.free(cmd_line_w);
1657
1658 const cmd_line = try unicode.wtf16LeToWtf8Alloc(std.testing.allocator, cmd_line_w);
1659 defer std.testing.allocator.free(cmd_line);
1660
1661 try std.testing.expectEqualStrings(expected_cmd_line, cmd_line);
1662}
1663
1664const ArgvToScriptCommandLineError = error{
1665 OutOfMemory,
1666 InvalidWtf8,
1667 /// NUL (U+0000), LF (U+000A), CR (U+000D) are not allowed
1668 /// within arguments when executing a `.bat`/`.cmd` script.
1669 /// - NUL/LF signifiies end of arguments, so anything afterwards
1670 /// would be lost after execution.
1671 /// - CR is stripped by `cmd.exe`, so any CR codepoints
1672 /// would be lost after execution.
1673 InvalidBatchScriptArg,
1674};
1675
1676/// Serializes `argv` to a Windows command-line string that uses `cmd.exe /c` and `cmd.exe`-specific
1677/// escaping rules. The caller owns the returned slice.
1678///
1679/// Escapes `argv` using the suggested mitigation against arbitrary command execution from:
1680/// https://flatt.tech/research/posts/batbadbut-you-cant-securely-execute-commands-on-windows/
1681fn argvToScriptCommandLineWindows(
1682 allocator: mem.Allocator,
1683 /// Path to the `.bat`/`.cmd` script. If this path is relative, it is assumed to be relative to the CWD.
1684 /// The script must have been verified to exist at this path before calling this function.
1685 script_path: []const u16,
1686 /// Arguments, not including the script name itself. Expected to be encoded as WTF-8.
1687 script_args: []const []const u8,
1688) ArgvToScriptCommandLineError![:0]u16 {
1689 var buf = try std.ArrayList(u8).initCapacity(allocator, 64);
1690 defer buf.deinit();
1691
1692 // `/d` disables execution of AutoRun commands.
1693 // `/e:ON` and `/v:OFF` are needed for BatBadBut mitigation:
1694 // > If delayed expansion is enabled via the registry value DelayedExpansion,
1695 // > it must be disabled by explicitly calling cmd.exe with the /V:OFF option.
1696 // > Escaping for % requires the command extension to be enabled.
1697 // > If it’s disabled via the registry value EnableExtensions, it must be enabled with the /E:ON option.
1698 // https://flatt.tech/research/posts/batbadbut-you-cant-securely-execute-commands-on-windows/
1699 buf.appendSliceAssumeCapacity("cmd.exe /d /e:ON /v:OFF /c \"");
1700
1701 // Always quote the path to the script arg
1702 buf.appendAssumeCapacity('"');
1703 // We always want the path to the batch script to include a path separator in order to
1704 // avoid cmd.exe searching the PATH for the script. This is not part of the arbitrary
1705 // command execution mitigation, we just know exactly what script we want to execute
1706 // at this point, and potentially making cmd.exe re-find it is unnecessary.
1707 //
1708 // If the script path does not have a path separator, then we know its relative to CWD and
1709 // we can just put `.\` in the front.
1710 if (mem.indexOfAny(u16, script_path, &[_]u16{ mem.nativeToLittle(u16, '\\'), mem.nativeToLittle(u16, '/') }) == null) {
1711 try buf.appendSlice(".\\");
1712 }
1713 // Note that we don't do any escaping/mitigations for this argument, since the relevant
1714 // characters (", %, etc) are illegal in file paths and this function should only be called
1715 // with script paths that have been verified to exist.
1716 try unicode.wtf16LeToWtf8ArrayList(&buf, script_path);
1717 buf.appendAssumeCapacity('"');
1718
1719 for (script_args) |arg| {
1720 // Literal carriage returns get stripped when run through cmd.exe
1721 // and NUL/newlines act as 'end of command.' Because of this, it's basically
1722 // always a mistake to include these characters in argv, so it's
1723 // an error condition in order to ensure that the return of this
1724 // function can always roundtrip through cmd.exe.
1725 if (std.mem.indexOfAny(u8, arg, "\x00\r\n") != null) {
1726 return error.InvalidBatchScriptArg;
1727 }
1728
1729 // Separate args with a space.
1730 try buf.append(' ');
1731
1732 // Need to quote if the argument is empty (otherwise the arg would just be lost)
1733 // or if the last character is a `\`, since then something like "%~2" in a .bat
1734 // script would cause the closing " to be escaped which we don't want.
1735 var needs_quotes = arg.len == 0 or arg[arg.len - 1] == '\\';
1736 if (!needs_quotes) {
1737 for (arg) |c| {
1738 switch (c) {
1739 // Known good characters that don't need to be quoted
1740 'A'...'Z', 'a'...'z', '0'...'9', '#', '$', '*', '+', '-', '.', '/', ':', '?', '@', '\\', '_' => {},
1741 // When in doubt, quote
1742 else => {
1743 needs_quotes = true;
1744 break;
1745 },
1746 }
1747 }
1748 }
1749 if (needs_quotes) {
1750 try buf.append('"');
1751 }
1752 var backslashes: usize = 0;
1753 for (arg) |c| {
1754 switch (c) {
1755 '\\' => {
1756 backslashes += 1;
1757 },
1758 '"' => {
1759 try buf.appendNTimes('\\', backslashes);
1760 try buf.append('"');
1761 backslashes = 0;
1762 },
1763 // Replace `%` with `%%cd:~,%`.
1764 //
1765 // cmd.exe allows extracting a substring from an environment
1766 // variable with the syntax: `%foo:~<start_index>,<end_index>%`.
1767 // Therefore, `%cd:~,%` will always expand to an empty string
1768 // since both the start and end index are blank, and it is assumed
1769 // that `%cd%` is always available since it is a built-in variable
1770 // that corresponds to the current directory.
1771 //
1772 // This means that replacing `%foo%` with `%%cd:~,%foo%%cd:~,%`
1773 // will stop `%foo%` from being expanded and *after* expansion
1774 // we'll still be left with `%foo%` (the literal string).
1775 '%' => {
1776 // the trailing `%` is appended outside the switch
1777 try buf.appendSlice("%%cd:~,");
1778 backslashes = 0;
1779 },
1780 else => {
1781 backslashes = 0;
1782 },
1783 }
1784 try buf.append(c);
1785 }
1786 if (needs_quotes) {
1787 try buf.appendNTimes('\\', backslashes);
1788 try buf.append('"');
1789 }
1790 }
1791
1792 try buf.append('"');
1793
1794 return try unicode.wtf8ToWtf16LeAllocZ(allocator, buf.items);
1795}
lib/std/std.zig-2
...@@ -14,8 +14,6 @@ pub const BoundedArrayAligned = @import("bounded_array.zig").BoundedArrayAligned...@@ -14,8 +14,6 @@ pub const BoundedArrayAligned = @import("bounded_array.zig").BoundedArrayAligned
14pub const Build = @import("Build.zig");14pub const Build = @import("Build.zig");
15pub const BufMap = @import("buf_map.zig").BufMap;15pub const BufMap = @import("buf_map.zig").BufMap;
16pub const BufSet = @import("buf_set.zig").BufSet;16pub const BufSet = @import("buf_set.zig").BufSet;
17/// Deprecated: use `process.Child`.
18pub const ChildProcess = @import("child_process.zig").ChildProcess;
19pub const StaticStringMap = static_string_map.StaticStringMap;17pub const StaticStringMap = static_string_map.StaticStringMap;
20pub const StaticStringMapWithEql = static_string_map.StaticStringMapWithEql;18pub const StaticStringMapWithEql = static_string_map.StaticStringMapWithEql;
21pub const DoublyLinkedList = @import("linked_list.zig").DoublyLinkedList;19pub const DoublyLinkedList = @import("linked_list.zig").DoublyLinkedList;
lib/std/zig/LibCInstallation.zig+2-2
...@@ -261,7 +261,7 @@ fn findNativeIncludeDirPosix(self: *LibCInstallation, args: FindNativeOptions) F...@@ -261,7 +261,7 @@ fn findNativeIncludeDirPosix(self: *LibCInstallation, args: FindNativeOptions) F
261 dev_null,261 dev_null,
262 });262 });
263263
264 const run_res = std.ChildProcess.run(.{264 const run_res = std.process.Child.run(.{
265 .allocator = allocator,265 .allocator = allocator,
266 .argv = argv.items,266 .argv = argv.items,
267 .max_output_bytes = 1024 * 1024,267 .max_output_bytes = 1024 * 1024,
...@@ -588,7 +588,7 @@ fn ccPrintFileName(args: CCPrintFileNameOptions) ![:0]u8 {...@@ -588,7 +588,7 @@ fn ccPrintFileName(args: CCPrintFileNameOptions) ![:0]u8 {
588 try appendCcExe(&argv, skip_cc_env_var);588 try appendCcExe(&argv, skip_cc_env_var);
589 try argv.append(arg1);589 try argv.append(arg1);
590590
591 const run_res = std.ChildProcess.run(.{591 const run_res = std.process.Child.run(.{
592 .allocator = allocator,592 .allocator = allocator,
593 .argv = argv.items,593 .argv = argv.items,
594 .max_output_bytes = 1024 * 1024,594 .max_output_bytes = 1024 * 1024,
src/Compilation.zig+3-3
...@@ -1935,7 +1935,7 @@ pub fn getTarget(self: Compilation) Target {...@@ -1935,7 +1935,7 @@ pub fn getTarget(self: Compilation) Target {
1935pub fn hotCodeSwap(1935pub fn hotCodeSwap(
1936 comp: *Compilation,1936 comp: *Compilation,
1937 prog_node: *std.Progress.Node,1937 prog_node: *std.Progress.Node,
1938 pid: std.ChildProcess.Id,1938 pid: std.process.Child.Id,
1939) !void {1939) !void {
1940 const lf = comp.bin_file.?;1940 const lf = comp.bin_file.?;
1941 lf.child_pid = pid;1941 lf.child_pid = pid;
...@@ -4614,7 +4614,7 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: *std.P...@@ -4614,7 +4614,7 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: *std.P
4614 log.warn("failed to delete '{s}': {s}", .{ dep_file_path, @errorName(err) });4614 log.warn("failed to delete '{s}': {s}", .{ dep_file_path, @errorName(err) });
4615 };4615 };
4616 if (std.process.can_spawn) {4616 if (std.process.can_spawn) {
4617 var child = std.ChildProcess.init(argv.items, arena);4617 var child = std.process.Child.init(argv.items, arena);
4618 if (comp.clang_passthrough_mode) {4618 if (comp.clang_passthrough_mode) {
4619 child.stdin_behavior = .Inherit;4619 child.stdin_behavior = .Inherit;
4620 child.stdout_behavior = .Inherit;4620 child.stdout_behavior = .Inherit;
...@@ -4972,7 +4972,7 @@ fn spawnZigRc(...@@ -4972,7 +4972,7 @@ fn spawnZigRc(
4972 var node_name: std.ArrayListUnmanaged(u8) = .{};4972 var node_name: std.ArrayListUnmanaged(u8) = .{};
4973 defer node_name.deinit(arena);4973 defer node_name.deinit(arena);
49744974
4975 var child = std.ChildProcess.init(argv, arena);4975 var child = std.process.Child.init(argv, arena);
4976 child.stdin_behavior = .Ignore;4976 child.stdin_behavior = .Ignore;
4977 child.stdout_behavior = .Pipe;4977 child.stdout_behavior = .Pipe;
4978 child.stderr_behavior = .Pipe;4978 child.stderr_behavior = .Pipe;
src/link.zig+2-2
...@@ -72,7 +72,7 @@ pub const File = struct {...@@ -72,7 +72,7 @@ pub const File = struct {
72 /// Prevents other processes from clobbering files in the output directory72 /// Prevents other processes from clobbering files in the output directory
73 /// of this linking operation.73 /// of this linking operation.
74 lock: ?Cache.Lock = null,74 lock: ?Cache.Lock = null,
75 child_pid: ?std.ChildProcess.Id = null,75 child_pid: ?std.process.Child.Id = null,
7676
77 pub const OpenOptions = struct {77 pub const OpenOptions = struct {
78 symbol_count_hint: u64 = 32,78 symbol_count_hint: u64 = 32,
...@@ -529,7 +529,7 @@ pub const File = struct {...@@ -529,7 +529,7 @@ pub const File = struct {
529 } ||529 } ||
530 fs.File.WriteFileError ||530 fs.File.WriteFileError ||
531 fs.File.OpenError ||531 fs.File.OpenError ||
532 std.ChildProcess.SpawnError ||532 std.process.Child.SpawnError ||
533 fs.Dir.CopyFileError;533 fs.Dir.CopyFileError;
534534
535 /// Commit pending changes and write headers. Takes into account final output mode535 /// Commit pending changes and write headers. Takes into account final output mode
src/link/Coff.zig+5-5
...@@ -893,7 +893,7 @@ fn writeAtom(self: *Coff, atom_index: Atom.Index, code: []u8) !void {...@@ -893,7 +893,7 @@ fn writeAtom(self: *Coff, atom_index: Atom.Index, code: []u8) !void {
893 }893 }
894}894}
895895
896fn debugMem(allocator: Allocator, handle: std.ChildProcess.Id, pvaddr: std.os.windows.LPVOID, code: []const u8) !void {896fn debugMem(allocator: Allocator, handle: std.process.Child.Id, pvaddr: std.os.windows.LPVOID, code: []const u8) !void {
897 const buffer = try allocator.alloc(u8, code.len);897 const buffer = try allocator.alloc(u8, code.len);
898 defer allocator.free(buffer);898 defer allocator.free(buffer);
899 const memread = try std.os.windows.ReadProcessMemory(handle, pvaddr, buffer);899 const memread = try std.os.windows.ReadProcessMemory(handle, pvaddr, buffer);
...@@ -901,7 +901,7 @@ fn debugMem(allocator: Allocator, handle: std.ChildProcess.Id, pvaddr: std.os.wi...@@ -901,7 +901,7 @@ fn debugMem(allocator: Allocator, handle: std.ChildProcess.Id, pvaddr: std.os.wi
901 log.debug("in memory: {x}", .{std.fmt.fmtSliceHexLower(memread)});901 log.debug("in memory: {x}", .{std.fmt.fmtSliceHexLower(memread)});
902}902}
903903
904fn writeMemProtected(handle: std.ChildProcess.Id, pvaddr: std.os.windows.LPVOID, code: []const u8) !void {904fn writeMemProtected(handle: std.process.Child.Id, pvaddr: std.os.windows.LPVOID, code: []const u8) !void {
905 const old_prot = try std.os.windows.VirtualProtectEx(handle, pvaddr, code.len, std.os.windows.PAGE_EXECUTE_WRITECOPY);905 const old_prot = try std.os.windows.VirtualProtectEx(handle, pvaddr, code.len, std.os.windows.PAGE_EXECUTE_WRITECOPY);
906 try writeMem(handle, pvaddr, code);906 try writeMem(handle, pvaddr, code);
907 // TODO: We can probably just set the pages writeable and leave it at that without having to restore the attributes.907 // TODO: We can probably just set the pages writeable and leave it at that without having to restore the attributes.
...@@ -909,7 +909,7 @@ fn writeMemProtected(handle: std.ChildProcess.Id, pvaddr: std.os.windows.LPVOID,...@@ -909,7 +909,7 @@ fn writeMemProtected(handle: std.ChildProcess.Id, pvaddr: std.os.windows.LPVOID,
909 _ = try std.os.windows.VirtualProtectEx(handle, pvaddr, code.len, old_prot);909 _ = try std.os.windows.VirtualProtectEx(handle, pvaddr, code.len, old_prot);
910}910}
911911
912fn writeMem(handle: std.ChildProcess.Id, pvaddr: std.os.windows.LPVOID, code: []const u8) !void {912fn writeMem(handle: std.process.Child.Id, pvaddr: std.os.windows.LPVOID, code: []const u8) !void {
913 const amt = try std.os.windows.WriteProcessMemory(handle, pvaddr, code);913 const amt = try std.os.windows.WriteProcessMemory(handle, pvaddr, code);
914 if (amt != code.len) return error.InputOutput;914 if (amt != code.len) return error.InputOutput;
915}915}
...@@ -1031,7 +1031,7 @@ fn resolveRelocs(self: *Coff, atom_index: Atom.Index, relocs: []*const Relocatio...@@ -1031,7 +1031,7 @@ fn resolveRelocs(self: *Coff, atom_index: Atom.Index, relocs: []*const Relocatio
1031 }1031 }
1032}1032}
10331033
1034pub fn ptraceAttach(self: *Coff, handle: std.ChildProcess.Id) !void {1034pub fn ptraceAttach(self: *Coff, handle: std.process.Child.Id) !void {
1035 if (!is_hot_update_compatible) return;1035 if (!is_hot_update_compatible) return;
10361036
1037 log.debug("attaching to process with handle {*}", .{handle});1037 log.debug("attaching to process with handle {*}", .{handle});
...@@ -1041,7 +1041,7 @@ pub fn ptraceAttach(self: *Coff, handle: std.ChildProcess.Id) !void {...@@ -1041,7 +1041,7 @@ pub fn ptraceAttach(self: *Coff, handle: std.ChildProcess.Id) !void {
1041 };1041 };
1042}1042}
10431043
1044pub fn ptraceDetach(self: *Coff, handle: std.ChildProcess.Id) void {1044pub fn ptraceDetach(self: *Coff, handle: std.process.Child.Id) void {
1045 if (!is_hot_update_compatible) return;1045 if (!is_hot_update_compatible) return;
10461046
1047 log.debug("detaching from process with handle {*}", .{handle});1047 log.debug("detaching from process with handle {*}", .{handle});
src/link/Wasm.zig+1-1
...@@ -3637,7 +3637,7 @@ fn linkWithLLD(wasm: *Wasm, arena: Allocator, prog_node: *std.Progress.Node) !vo...@@ -3637,7 +3637,7 @@ fn linkWithLLD(wasm: *Wasm, arena: Allocator, prog_node: *std.Progress.Node) !vo
3637 // If possible, we run LLD as a child process because it does not always3637 // If possible, we run LLD as a child process because it does not always
3638 // behave properly as a library, unfortunately.3638 // behave properly as a library, unfortunately.
3639 // https://github.com/ziglang/zig/issues/38253639 // https://github.com/ziglang/zig/issues/3825
3640 var child = std.ChildProcess.init(argv.items, arena);3640 var child = std.process.Child.init(argv.items, arena);
3641 if (comp.clang_passthrough_mode) {3641 if (comp.clang_passthrough_mode) {
3642 child.stdin_behavior = .Inherit;3642 child.stdin_behavior = .Inherit;
3643 child.stdout_behavior = .Inherit;3643 child.stdout_behavior = .Inherit;
src/main.zig+6-6
...@@ -4026,7 +4026,7 @@ fn serve(...@@ -4026,7 +4026,7 @@ fn serve(
4026 });4026 });
4027 defer server.deinit();4027 defer server.deinit();
40284028
4029 var child_pid: ?std.ChildProcess.Id = null;4029 var child_pid: ?std.process.Child.Id = null;
40304030
4031 var progress: std.Progress = .{4031 var progress: std.Progress = .{
4032 .terminal = null,4032 .terminal = null,
...@@ -4316,7 +4316,7 @@ fn runOrTest(...@@ -4316,7 +4316,7 @@ fn runOrTest(
4316 const cmd = try std.mem.join(arena, " ", argv.items);4316 const cmd = try std.mem.join(arena, " ", argv.items);
4317 fatal("the following command failed to execve with '{s}':\n{s}", .{ @errorName(err), cmd });4317 fatal("the following command failed to execve with '{s}':\n{s}", .{ @errorName(err), cmd });
4318 } else if (process.can_spawn) {4318 } else if (process.can_spawn) {
4319 var child = std.ChildProcess.init(argv.items, gpa);4319 var child = std.process.Child.init(argv.items, gpa);
4320 child.env_map = &env_map;4320 child.env_map = &env_map;
4321 child.stdin_behavior = .Inherit;4321 child.stdin_behavior = .Inherit;
4322 child.stdout_behavior = .Inherit;4322 child.stdout_behavior = .Inherit;
...@@ -4379,7 +4379,7 @@ fn runOrTestHotSwap(...@@ -4379,7 +4379,7 @@ fn runOrTestHotSwap(
4379 arg_mode: ArgMode,4379 arg_mode: ArgMode,
4380 all_args: []const []const u8,4380 all_args: []const []const u8,
4381 runtime_args_start: ?usize,4381 runtime_args_start: ?usize,
4382) !std.ChildProcess.Id {4382) !std.process.Child.Id {
4383 const lf = comp.bin_file.?;4383 const lf = comp.bin_file.?;
43844384
4385 const exe_path = switch (builtin.target.os.tag) {4385 const exe_path = switch (builtin.target.os.tag) {
...@@ -4456,7 +4456,7 @@ fn runOrTestHotSwap(...@@ -4456,7 +4456,7 @@ fn runOrTestHotSwap(
4456 return pid;4456 return pid;
4457 },4457 },
4458 else => {4458 else => {
4459 var child = std.ChildProcess.init(argv.items, gpa);4459 var child = std.process.Child.init(argv.items, gpa);
44604460
4461 child.stdin_behavior = .Inherit;4461 child.stdin_behavior = .Inherit;
4462 child.stdout_behavior = .Inherit;4462 child.stdout_behavior = .Inherit;
...@@ -5245,7 +5245,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {...@@ -5245,7 +5245,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
5245 }5245 }
52465246
5247 if (process.can_spawn) {5247 if (process.can_spawn) {
5248 var child = std.ChildProcess.init(child_argv.items, gpa);5248 var child = std.process.Child.init(child_argv.items, gpa);
5249 child.stdin_behavior = .Inherit;5249 child.stdin_behavior = .Inherit;
5250 child.stdout_behavior = .Inherit;5250 child.stdout_behavior = .Inherit;
5251 child.stderr_behavior = .Inherit;5251 child.stderr_behavior = .Inherit;
...@@ -5549,7 +5549,7 @@ fn jitCmd(...@@ -5549,7 +5549,7 @@ fn jitCmd(
5549 });5549 });
5550 }5550 }
55515551
5552 var child = std.ChildProcess.init(child_argv.items, gpa);5552 var child = std.process.Child.init(child_argv.items, gpa);
5553 child.stdin_behavior = .Inherit;5553 child.stdin_behavior = .Inherit;
5554 child.stdout_behavior = if (options.capture == null) .Inherit else .Pipe;5554 child.stdout_behavior = if (options.capture == null) .Inherit else .Pipe;
5555 child.stderr_behavior = .Inherit;5555 child.stderr_behavior = .Inherit;
test/src/Cases.zig+1-1
...@@ -1839,7 +1839,7 @@ fn runOneCase(...@@ -1839,7 +1839,7 @@ fn runOneCase(
1839 try comp.makeBinFileExecutable();1839 try comp.makeBinFileExecutable();
18401840
1841 while (true) {1841 while (true) {
1842 break :x std.ChildProcess.run(.{1842 break :x std.process.Child.run(.{
1843 .allocator = allocator,1843 .allocator = allocator,
1844 .argv = argv.items,1844 .argv = argv.items,
1845 .cwd_dir = tmp.dir,1845 .cwd_dir = tmp.dir,
test/standalone/child_process/main.zig+1-1
...@@ -13,7 +13,7 @@ pub fn main() !void {...@@ -13,7 +13,7 @@ pub fn main() !void {
13 _ = it.next() orelse unreachable; // skip binary name13 _ = it.next() orelse unreachable; // skip binary name
14 const child_path = it.next() orelse unreachable;14 const child_path = it.next() orelse unreachable;
1515
16 var child = std.ChildProcess.init(&.{ child_path, "hello arg" }, gpa);16 var child = std.process.Child.init(&.{ child_path, "hello arg" }, gpa);
17 child.stdin_behavior = .Pipe;17 child.stdin_behavior = .Pipe;
18 child.stdout_behavior = .Pipe;18 child.stdout_behavior = .Pipe;
19 child.stderr_behavior = .Inherit;19 child.stderr_behavior = .Inherit;
test/standalone/windows_bat_args/fuzz.zig+1-1
...@@ -87,7 +87,7 @@ fn testExecBat(allocator: std.mem.Allocator, bat: []const u8, args: []const []co...@@ -87,7 +87,7 @@ fn testExecBat(allocator: std.mem.Allocator, bat: []const u8, args: []const []co
8787
88 const can_have_trailing_empty_args = std.mem.eql(u8, bat, "args3.bat");88 const can_have_trailing_empty_args = std.mem.eql(u8, bat, "args3.bat");
8989
90 const result = try std.ChildProcess.run(.{90 const result = try std.process.Child.run(.{
91 .allocator = allocator,91 .allocator = allocator,
92 .env_map = env,92 .env_map = env,
93 .argv = argv.items,93 .argv = argv.items,
test/standalone/windows_bat_args/test.zig+1-1
...@@ -109,7 +109,7 @@ fn testExecBat(allocator: std.mem.Allocator, bat: []const u8, args: []const []co...@@ -109,7 +109,7 @@ fn testExecBat(allocator: std.mem.Allocator, bat: []const u8, args: []const []co
109109
110 const can_have_trailing_empty_args = std.mem.eql(u8, bat, "args3.bat");110 const can_have_trailing_empty_args = std.mem.eql(u8, bat, "args3.bat");
111111
112 const result = try std.ChildProcess.run(.{112 const result = try std.process.Child.run(.{
113 .allocator = allocator,113 .allocator = allocator,
114 .env_map = env,114 .env_map = env,
115 .argv = argv.items,115 .argv = argv.items,
test/standalone/windows_spawn/main.zig+1-1
...@@ -158,7 +158,7 @@ fn testExec(allocator: std.mem.Allocator, command: []const u8, expected_stdout:...@@ -158,7 +158,7 @@ fn testExec(allocator: std.mem.Allocator, command: []const u8, expected_stdout:
158}158}
159159
160fn testExecWithCwd(allocator: std.mem.Allocator, command: []const u8, cwd: ?[]const u8, expected_stdout: []const u8) !void {160fn testExecWithCwd(allocator: std.mem.Allocator, command: []const u8, cwd: ?[]const u8, expected_stdout: []const u8) !void {
161 const result = try std.ChildProcess.run(.{161 const result = try std.process.Child.run(.{
162 .allocator = allocator,162 .allocator = allocator,
163 .argv = &[_][]const u8{command},163 .argv = &[_][]const u8{command},
164 .cwd = cwd,164 .cwd = cwd,
tools/docgen.zig+1-1
...@@ -3,7 +3,7 @@ const builtin = @import("builtin");...@@ -3,7 +3,7 @@ const builtin = @import("builtin");
3const io = std.io;3const io = std.io;
4const fs = std.fs;4const fs = std.fs;
5const process = std.process;5const process = std.process;
6const ChildProcess = std.ChildProcess;6const ChildProcess = std.process.Child;
7const Progress = std.Progress;7const Progress = std.Progress;
8const print = std.debug.print;8const print = std.debug.print;
9const mem = std.mem;9const mem = std.mem;
tools/generate_linux_syscalls.zig+2-2
...@@ -257,7 +257,7 @@ pub fn main() !void {...@@ -257,7 +257,7 @@ pub fn main() !void {
257 "arch/arm64/include/uapi/asm/unistd.h",257 "arch/arm64/include/uapi/asm/unistd.h",
258 };258 };
259259
260 const child_result = try std.ChildProcess.run(.{260 const child_result = try std.process.Child.run(.{
261 .allocator = allocator,261 .allocator = allocator,
262 .argv = &child_args,262 .argv = &child_args,
263 .cwd = linux_path,263 .cwd = linux_path,
...@@ -318,7 +318,7 @@ pub fn main() !void {...@@ -318,7 +318,7 @@ pub fn main() !void {
318 "arch/riscv/include/uapi/asm/unistd.h",318 "arch/riscv/include/uapi/asm/unistd.h",
319 };319 };
320320
321 const child_result = try std.ChildProcess.run(.{321 const child_result = try std.process.Child.run(.{
322 .allocator = allocator,322 .allocator = allocator,
323 .argv = &child_args,323 .argv = &child_args,
324 .cwd = linux_path,324 .cwd = linux_path,
tools/update_clang_options.zig+1-1
...@@ -624,7 +624,7 @@ pub fn main() anyerror!void {...@@ -624,7 +624,7 @@ pub fn main() anyerror!void {
624 try std.fmt.allocPrint(allocator, "-I={s}/clang/include/clang/Driver", .{llvm_src_root}),624 try std.fmt.allocPrint(allocator, "-I={s}/clang/include/clang/Driver", .{llvm_src_root}),
625 };625 };
626626
627 const child_result = try std.ChildProcess.run(.{627 const child_result = try std.process.Child.run(.{
628 .allocator = allocator,628 .allocator = allocator,
629 .argv = &child_args,629 .argv = &child_args,
630 .max_output_bytes = 100 * 1024 * 1024,630 .max_output_bytes = 100 * 1024 * 1024,
tools/update_cpu_features.zig+1-1
...@@ -1106,7 +1106,7 @@ fn processOneTarget(job: Job) anyerror!void {...@@ -1106,7 +1106,7 @@ fn processOneTarget(job: Job) anyerror!void {
1106 }),1106 }),
1107 };1107 };
11081108
1109 const child_result = try std.ChildProcess.run(.{1109 const child_result = try std.process.Child.run(.{
1110 .allocator = arena,1110 .allocator = arena,
1111 .argv = &child_args,1111 .argv = &child_args,
1112 .max_output_bytes = 400 * 1024 * 1024,1112 .max_output_bytes = 400 * 1024 * 1024,