| author | |
| committer | |
| log | f47824f24d1b20c276a797df8cf99066c08203b6 |
| tree | d988ba1a65733978fd93b85009d2088dc9a3076e |
| parent | 793f820b393e0a79842b99afb008d13eb1343ed8 |
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. |
| 194 | set(ZIG_STAGE2_SOURCES | 194 | set(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.zig | 196 | lib/compiler_rt.zig |
| 291 | lib/compiler_rt/absv.zig | 197 | lib/compiler_rt/absv.zig |
| 292 | lib/compiler_rt/absvdi2.zig | 198 | 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.zig | 269 | lib/compiler_rt/fixxfdi.zig |
| 364 | lib/compiler_rt/fixxfsi.zig | 270 | lib/compiler_rt/fixxfsi.zig |
| 365 | lib/compiler_rt/fixxfti.zig | 271 | lib/compiler_rt/fixxfti.zig |
| 366 | lib/compiler_rt/int_from_float.zig | 272 | lib/compiler_rt/float_from_int.zig |
| 367 | lib/compiler_rt/floatdidf.zig | 273 | lib/compiler_rt/floatdidf.zig |
| 368 | lib/compiler_rt/floatdihf.zig | 274 | lib/compiler_rt/floatdihf.zig |
| 369 | lib/compiler_rt/floatdisf.zig | 275 | 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.zig | 310 | lib/compiler_rt/getf2.zig |
| 405 | lib/compiler_rt/gexf2.zig | 311 | lib/compiler_rt/gexf2.zig |
| 406 | lib/compiler_rt/int.zig | 312 | lib/compiler_rt/int.zig |
| 407 | lib/compiler_rt/float_from_int.zig | 313 | lib/compiler_rt/int_from_float.zig |
| 408 | lib/compiler_rt/log.zig | 314 | lib/compiler_rt/log.zig |
| 409 | lib/compiler_rt/log10.zig | 315 | lib/compiler_rt/log10.zig |
| 410 | lib/compiler_rt/log2.zig | 316 | 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.zig | 323 | lib/compiler_rt/multf3.zig |
| 418 | lib/compiler_rt/mulxf3.zig | 324 | lib/compiler_rt/mulxf3.zig |
| 419 | lib/compiler_rt/negXi2.zig | 325 | 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.zig | 329 | lib/compiler_rt/negv.zig |
| 330 | lib/compiler_rt/negxf2.zig | ||
| 421 | lib/compiler_rt/os_version_check.zig | 331 | lib/compiler_rt/os_version_check.zig |
| 422 | lib/compiler_rt/parity.zig | 332 | lib/compiler_rt/parity.zig |
| 423 | lib/compiler_rt/popcount.zig | 333 | 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.zig | 340 | lib/compiler_rt/sincos.zig |
| 431 | lib/compiler_rt/sqrt.zig | 341 | lib/compiler_rt/sqrt.zig |
| 432 | lib/compiler_rt/stack_probe.zig | 342 | lib/compiler_rt/stack_probe.zig |
| 343 | lib/compiler_rt/subdf3.zig | ||
| 433 | lib/compiler_rt/subo.zig | 344 | lib/compiler_rt/subo.zig |
| 434 | lib/compiler_rt/subsf3.zig | 345 | lib/compiler_rt/subsf3.zig |
| 435 | lib/compiler_rt/subdf3.zig | ||
| 436 | lib/compiler_rt/subtf3.zig | 346 | lib/compiler_rt/subtf3.zig |
| 437 | lib/compiler_rt/subxf3.zig | 347 | 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.zig | 348 | lib/compiler_rt/tan.zig |
| 443 | lib/compiler_rt/trig.zig | 349 | lib/compiler_rt/trig.zig |
| 444 | lib/compiler_rt/trunc.zig | 350 | 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.zig | 367 | lib/compiler_rt/unorddf2.zig |
| 462 | lib/compiler_rt/unordsf2.zig | 368 | lib/compiler_rt/unordsf2.zig |
| 463 | lib/compiler_rt/unordtf2.zig | 369 | lib/compiler_rt/unordtf2.zig |
| 464 | lib/std/start.zig | 370 | lib/std/BitStack.zig |
| 465 | lib/std/std.zig | 371 | 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.zig | 376 | lib/std/Target.zig |
| 467 | lib/std/Target/Query.zig | 377 | lib/std/Target/Query.zig |
| 468 | lib/std/Target/aarch64.zig | 378 | 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.zig | 386 | lib/std/Target/nvptx.zig |
| 477 | lib/std/Target/powerpc.zig | 387 | lib/std/Target/powerpc.zig |
| 478 | lib/std/Target/riscv.zig | 388 | lib/std/Target/riscv.zig |
| 479 | lib/std/Target/sparc.zig | ||
| 480 | lib/std/Target/s390x.zig | 389 | lib/std/Target/s390x.zig |
| 390 | lib/std/Target/sparc.zig | ||
| 481 | lib/std/Target/wasm.zig | 391 | lib/std/Target/wasm.zig |
| 482 | lib/std/Target/x86.zig | 392 | lib/std/Target/x86.zig |
| 483 | lib/std/Thread.zig | 393 | 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.zig | 396 | lib/std/Thread/Pool.zig |
| 487 | lib/std/Thread/ResetEvent.zig | 397 | lib/std/Thread/ResetEvent.zig |
| 488 | lib/std/Thread/WaitGroup.zig | 398 | 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.zig | 489 | lib/std/time.zig |
| 490 | lib/std/treap.zig | 490 | lib/std/treap.zig |
| 491 | lib/std/unicode.zig | 491 | 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 better | 807 | // 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 | }); |
| 304 | 304 | ||
| 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; |
| 182 | 182 | ||
| 183 | pub const PkgConfigError = error{ | 183 | pub 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); |
| 1725 | 1725 | ||
| ... | @@ -1727,7 +1727,7 @@ pub fn runAllowFail( | ... | @@ -1727,7 +1727,7 @@ pub fn runAllowFail( |
| 1727 | return error.ExecNotSupported; | 1727 | return error.ExecNotSupported; |
| 1728 | 1728 | ||
| 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); |
| 277 | 277 | ||
| 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); |
| 315 | 315 | ||
| 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( |
| 480 | 480 | ||
| 481 | pub fn handleChildProcessTerm( | 481 | pub 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 @@ | ||
| 1 | const std = @import("std.zig"); | ||
| 2 | const builtin = @import("builtin"); | ||
| 3 | const unicode = std.unicode; | ||
| 4 | const io = std.io; | ||
| 5 | const fs = std.fs; | ||
| 6 | const process = std.process; | ||
| 7 | const File = std.fs.File; | ||
| 8 | const windows = std.os.windows; | ||
| 9 | const linux = std.os.linux; | ||
| 10 | const posix = std.posix; | ||
| 11 | const mem = std.mem; | ||
| 12 | const math = std.math; | ||
| 13 | const debug = std.debug; | ||
| 14 | const EnvMap = process.EnvMap; | ||
| 15 | const maxInt = std.math.maxInt; | ||
| 16 | const assert = std.debug.assert; | ||
| 17 | const native_os = builtin.os.tag; | ||
| 18 | |||
| 19 | pub 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). | ||
| 974 | fn 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 | |||
| 1208 | fn 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` | ||
| 1241 | const CreateProcessSupportedExtension = enum { | ||
| 1242 | bat, | ||
| 1243 | cmd, | ||
| 1244 | com, | ||
| 1245 | exe, | ||
| 1246 | }; | ||
| 1247 | |||
| 1248 | /// Case-insensitive WTF-16 lookup | ||
| 1249 | fn 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 | |||
| 1307 | test 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) | ||
| 1320 | pub 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 | |||
| 1369 | pub 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 | |||
| 1396 | pub 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/ | ||
| 1413 | pub 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 | |||
| 1529 | pub 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. | ||
| 1533 | pub 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 | |||
| 1615 | test 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 | |||
| 1674 | fn 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 | |||
| 1684 | fn windowsDestroyPipe(rd: ?windows.HANDLE, wr: ?windows.HANDLE) void { | ||
| 1685 | if (rd) |h| posix.close(h); | ||
| 1686 | if (wr) |h| posix.close(h); | ||
| 1687 | } | ||
| 1688 | |||
| 1689 | fn 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 | |||
| 1699 | var pipe_name_counter = std.atomic.Value(u32).init(1); | ||
| 1700 | |||
| 1701 | fn 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 | |||
| 1762 | fn 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. | ||
| 1769 | fn 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 | |||
| 1782 | const ErrInt = std.meta.Int(.unsigned, @sizeOf(anyerror) * 8); | ||
| 1783 | |||
| 1784 | fn 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 | |||
| 1789 | fn 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. | ||
| 1795 | pub 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 | |||
| 1831 | pub 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 | |||
| 1849 | test 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; |
| 6 | const Allocator = mem.Allocator; | 6 | const Allocator = mem.Allocator; |
| 7 | const assert = std.debug.assert; | 7 | const assert = std.debug.assert; |
| 8 | const testing = std.testing; | 8 | const testing = std.testing; |
| 9 | const child_process = @import("child_process.zig"); | ||
| 10 | const native_os = builtin.os.tag; | 9 | const native_os = builtin.os.tag; |
| 11 | const posix = std.posix; | 10 | const posix = std.posix; |
| 12 | const windows = std.os.windows; | 11 | const windows = std.os.windows; |
| 12 | const unicode = std.unicode; | ||
| 13 | 13 | ||
| 14 | pub const Child = child_process.ChildProcess; | 14 | pub const Child = @import("process/Child.zig"); |
| 15 | pub const abort = posix.abort; | 15 | pub const abort = posix.abort; |
| 16 | pub const exit = posix.exit; | 16 | pub const exit = posix.exit; |
| 17 | pub const changeCurDir = posix.chdir; | 17 | pub 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 | } |
| 179 | 179 | ||
| ... | @@ -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 | } |
| 188 | 188 | ||
| ... | @@ -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("кириллица").?); |
| 261 | 261 | ||
| 262 | // and WTF-8 that's not valid UTF-8 | 262 | // 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 surrogate | 264 | 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 { |
| 300 | 300 | ||
| 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); |
| 305 | 305 | ||
| 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); |
| 313 | 313 | ||
| 314 | i += 1; // skip over null byte | 314 | 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); |
| 406 | 406 | ||
| 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 OutOfMemory | 409 | // 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. |
| 423 | pub fn hasEnvVarConstant(comptime key: []const u8) bool { | 423 | pub 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* take | 659 | /// 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); |
| 664 | 664 | ||
| 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 convert | 738 | // We know we have a valid surrogate pair, so convert |
| 739 | // it to UTF-8, overwriting the surrogate pair's bytes | 739 | // 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 | } |
| 1342 | 1342 | ||
| 1343 | fn testArgIteratorWindows(cmd_line: []const u8, expected_args: []const []const u8) !void { | 1343 | fn 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); |
| 1346 | 1346 | ||
| 1347 | // next | 1347 | // next |
| ... | @@ -1642,7 +1642,7 @@ pub fn execve( | ... | @@ -1642,7 +1642,7 @@ pub fn execve( |
| 1642 | 1642 | ||
| 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 { |
| 1789 | test raiseFileDescriptorLimit { | 1789 | test raiseFileDescriptorLimit { |
| 1790 | raiseFileDescriptorLimit(); | 1790 | raiseFileDescriptorLimit(); |
| 1791 | } | 1791 | } |
| 1792 | |||
| 1793 | pub 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 | |||
| 1811 | test 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. | ||
| 1844 | pub 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 @@ | ||
| 1 | const std = @import("../std.zig"); | ||
| 2 | const builtin = @import("builtin"); | ||
| 3 | const unicode = std.unicode; | ||
| 4 | const fs = std.fs; | ||
| 5 | const process = std.process; | ||
| 6 | const File = std.fs.File; | ||
| 7 | const windows = std.os.windows; | ||
| 8 | const linux = std.os.linux; | ||
| 9 | const posix = std.posix; | ||
| 10 | const mem = std.mem; | ||
| 11 | const EnvMap = std.process.EnvMap; | ||
| 12 | const maxInt = std.math.maxInt; | ||
| 13 | const assert = std.debug.assert; | ||
| 14 | const native_os = builtin.os.tag; | ||
| 15 | const ChildProcess = @This(); | ||
| 16 | |||
| 17 | pub 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. | ||
| 26 | id: Id, | ||
| 27 | thread_handle: if (native_os == .windows) windows.HANDLE else void, | ||
| 28 | |||
| 29 | allocator: 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()`. | ||
| 34 | stdin: ?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()`. | ||
| 39 | stdout: ?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()`. | ||
| 44 | stderr: ?File, | ||
| 45 | |||
| 46 | /// Terminated state of the child process. | ||
| 47 | /// Available after calling `wait()`. | ||
| 48 | term: ?(SpawnError!Term), | ||
| 49 | |||
| 50 | argv: []const []const u8, | ||
| 51 | |||
| 52 | /// Leave as null to use the current env map using the supplied allocator. | ||
| 53 | env_map: ?*const EnvMap, | ||
| 54 | |||
| 55 | stdin_behavior: StdIo, | ||
| 56 | stdout_behavior: StdIo, | ||
| 57 | stderr_behavior: StdIo, | ||
| 58 | |||
| 59 | /// Set to change the user id when spawning the child process. | ||
| 60 | uid: 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. | ||
| 63 | gid: 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. | ||
| 66 | cwd: ?[]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. | ||
| 70 | cwd_dir: ?fs.Dir = null, | ||
| 71 | |||
| 72 | err_pipe: ?if (native_os == .windows) void else [2]posix.fd_t, | ||
| 73 | |||
| 74 | expand_arg0: Arg0Expand, | ||
| 75 | |||
| 76 | /// Darwin-only. Disable ASLR for the child process. | ||
| 77 | disable_aslr: bool = false, | ||
| 78 | |||
| 79 | /// Darwin-only. Start child process in suspended state as if SIGSTOP was sent. | ||
| 80 | start_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(). | ||
| 88 | request_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`. | ||
| 93 | resource_usage_statistics: ResourceUsageStatistics = .{}, | ||
| 94 | |||
| 95 | pub 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 | |||
| 135 | pub const Arg0Expand = posix.Arg0Expand; | ||
| 136 | |||
| 137 | pub 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 | |||
| 165 | pub 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. | ||
| 174 | pub 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. | ||
| 193 | pub 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 | |||
| 215 | pub 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. | ||
| 223 | pub 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 | |||
| 235 | pub 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. | ||
| 241 | pub fn kill(self: *ChildProcess) !Term { | ||
| 242 | if (native_os == .windows) { | ||
| 243 | return self.killWindows(1); | ||
| 244 | } else { | ||
| 245 | return self.killPosix(); | ||
| 246 | } | ||
| 247 | } | ||
| 248 | |||
| 249 | pub 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 | |||
| 271 | pub 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. | ||
| 285 | pub 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 | |||
| 296 | pub const RunResult = struct { | ||
| 297 | term: Term, | ||
| 298 | stdout: []u8, | ||
| 299 | stderr: []u8, | ||
| 300 | }; | ||
| 301 | |||
| 302 | fn 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 | ||
| 320 | pub 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 | |||
| 353 | pub 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. | ||
| 360 | pub 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 | |||
| 395 | fn 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 | |||
| 405 | fn 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 | |||
| 415 | fn 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 | |||
| 437 | fn 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 | |||
| 458 | fn handleWaitResult(self: *ChildProcess, status: u32) void { | ||
| 459 | self.term = self.cleanupAfterWait(status); | ||
| 460 | } | ||
| 461 | |||
| 462 | fn 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 | |||
| 477 | fn 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 | |||
| 517 | fn 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 | |||
| 528 | fn 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 | |||
| 684 | fn 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 | |||
| 957 | fn 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 | |||
| 966 | fn 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. | ||
| 973 | fn 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 | |||
| 986 | fn 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 | |||
| 991 | fn readIntFd(fd: i32) !ErrInt { | ||
| 992 | const file: File = .{ .handle = fd }; | ||
| 993 | return @intCast(file.reader().readInt(u64, .little) catch return error.SystemResources); | ||
| 994 | } | ||
| 995 | |||
| 996 | const 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). | ||
| 1002 | fn 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 | |||
| 1236 | fn 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 | |||
| 1275 | fn 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 | |||
| 1285 | fn windowsDestroyPipe(rd: ?windows.HANDLE, wr: ?windows.HANDLE) void { | ||
| 1286 | if (rd) |h| posix.close(h); | ||
| 1287 | if (wr) |h| posix.close(h); | ||
| 1288 | } | ||
| 1289 | |||
| 1290 | fn 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 | |||
| 1351 | var pipe_name_counter = std.atomic.Value(u32).init(1); | ||
| 1352 | |||
| 1353 | // Should be kept in sync with `windowsCreateProcessSupportsExtension` | ||
| 1354 | const CreateProcessSupportedExtension = enum { | ||
| 1355 | bat, | ||
| 1356 | cmd, | ||
| 1357 | com, | ||
| 1358 | exe, | ||
| 1359 | }; | ||
| 1360 | |||
| 1361 | /// Case-insensitive WTF-16 lookup | ||
| 1362 | fn 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 | |||
| 1420 | test 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) | ||
| 1433 | const 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 | |||
| 1482 | fn 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 | |||
| 1509 | const 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. | ||
| 1513 | fn 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 | |||
| 1595 | test 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 | |||
| 1654 | fn 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 | |||
| 1664 | const 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/ | ||
| 1681 | fn 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 |
| 14 | pub const Build = @import("Build.zig"); | 14 | pub const Build = @import("Build.zig"); |
| 15 | pub const BufMap = @import("buf_map.zig").BufMap; | 15 | pub const BufMap = @import("buf_map.zig").BufMap; |
| 16 | pub const BufSet = @import("buf_set.zig").BufSet; | 16 | pub const BufSet = @import("buf_set.zig").BufSet; |
| 17 | /// Deprecated: use `process.Child`. | ||
| 18 | pub const ChildProcess = @import("child_process.zig").ChildProcess; | ||
| 19 | pub const StaticStringMap = static_string_map.StaticStringMap; | 17 | pub const StaticStringMap = static_string_map.StaticStringMap; |
| 20 | pub const StaticStringMapWithEql = static_string_map.StaticStringMapWithEql; | 18 | pub const StaticStringMapWithEql = static_string_map.StaticStringMapWithEql; |
| 21 | pub const DoublyLinkedList = @import("linked_list.zig").DoublyLinkedList; | 19 | pub 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 | }); |
| 263 | 263 | ||
| 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); |
| 590 | 590 | ||
| 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 { |
| 1935 | pub fn hotCodeSwap( | 1935 | pub 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); |
| 4974 | 4974 | ||
| 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 directory | 72 | /// 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, |
| 76 | 76 | ||
| 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; |
| 534 | 534 | ||
| 535 | /// Commit pending changes and write headers. Takes into account final output mode | 535 | /// 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 | } |
| 895 | 895 | ||
| 896 | fn debugMem(allocator: Allocator, handle: std.ChildProcess.Id, pvaddr: std.os.windows.LPVOID, code: []const u8) !void { | 896 | fn 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 | } |
| 903 | 903 | ||
| 904 | fn writeMemProtected(handle: std.ChildProcess.Id, pvaddr: std.os.windows.LPVOID, code: []const u8) !void { | 904 | fn 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 | } |
| 911 | 911 | ||
| 912 | fn writeMem(handle: std.ChildProcess.Id, pvaddr: std.os.windows.LPVOID, code: []const u8) !void { | 912 | fn 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 | } |
| 1033 | 1033 | ||
| 1034 | pub fn ptraceAttach(self: *Coff, handle: std.ChildProcess.Id) !void { | 1034 | pub fn ptraceAttach(self: *Coff, handle: std.process.Child.Id) !void { |
| 1035 | if (!is_hot_update_compatible) return; | 1035 | if (!is_hot_update_compatible) return; |
| 1036 | 1036 | ||
| 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 | } |
| 1043 | 1043 | ||
| 1044 | pub fn ptraceDetach(self: *Coff, handle: std.ChildProcess.Id) void { | 1044 | pub fn ptraceDetach(self: *Coff, handle: std.process.Child.Id) void { |
| 1045 | if (!is_hot_update_compatible) return; | 1045 | if (!is_hot_update_compatible) return; |
| 1046 | 1046 | ||
| 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 always | 3637 | // 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/3825 | 3639 | // 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(); |
| 4028 | 4028 | ||
| 4029 | var child_pid: ?std.ChildProcess.Id = null; | 4029 | var child_pid: ?std.process.Child.Id = null; |
| 4030 | 4030 | ||
| 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.?; |
| 4384 | 4384 | ||
| 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); |
| 4460 | 4460 | ||
| 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 | } |
| 5246 | 5246 | ||
| 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 | } |
| 5551 | 5551 | ||
| 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(); |
| 1840 | 1840 | ||
| 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 name | 13 | _ = it.next() orelse unreachable; // skip binary name |
| 14 | const child_path = it.next() orelse unreachable; | 14 | const child_path = it.next() orelse unreachable; |
| 15 | 15 | ||
| 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 |
| 87 | 87 | ||
| 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"); |
| 89 | 89 | ||
| 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 |
| 109 | 109 | ||
| 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"); |
| 111 | 111 | ||
| 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 | } |
| 159 | 159 | ||
| 160 | fn testExecWithCwd(allocator: std.mem.Allocator, command: []const u8, cwd: ?[]const u8, expected_stdout: []const u8) !void { | 160 | fn 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"); |
| 3 | const io = std.io; | 3 | const io = std.io; |
| 4 | const fs = std.fs; | 4 | const fs = std.fs; |
| 5 | const process = std.process; | 5 | const process = std.process; |
| 6 | const ChildProcess = std.ChildProcess; | 6 | const ChildProcess = std.process.Child; |
| 7 | const Progress = std.Progress; | 7 | const Progress = std.Progress; |
| 8 | const print = std.debug.print; | 8 | const print = std.debug.print; |
| 9 | const mem = std.mem; | 9 | const 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 | }; |
| 259 | 259 | ||
| 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 | }; |
| 320 | 320 | ||
| 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 | }; |
| 626 | 626 | ||
| 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 | }; |
| 1108 | 1108 | ||
| 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, |