| ... | @@ -73,7 +73,7 @@ cwd: ?[]const u8, | ... | @@ -73,7 +73,7 @@ cwd: ?[]const u8, |
| 73 | /// Once that is done, `cwd` will be deprecated in favor of this field. | 73 | /// Once that is done, `cwd` will be deprecated in favor of this field. |
| 74 | cwd_dir: ?fs.Dir = null, | 74 | cwd_dir: ?fs.Dir = null, |
| 75 | | 75 | |
| 76 | err_pipe: ?if (native_os == .windows) void else [2]posix.fd_t, | 76 | err_pipe: if (native_os == .windows) void else ?posix.fd_t, |
| 77 | | 77 | |
| 78 | expand_arg0: Arg0Expand, | 78 | expand_arg0: Arg0Expand, |
| 79 | | 79 | |
| ... | @@ -211,7 +211,7 @@ pub fn init(argv: []const []const u8, allocator: mem.Allocator) ChildProcess { | ... | @@ -211,7 +211,7 @@ pub fn init(argv: []const []const u8, allocator: mem.Allocator) ChildProcess { |
| 211 | .argv = argv, | 211 | .argv = argv, |
| 212 | .id = undefined, | 212 | .id = undefined, |
| 213 | .thread_handle = undefined, | 213 | .thread_handle = undefined, |
| 214 | .err_pipe = null, | 214 | .err_pipe = if (native_os == .windows) {} else null, |
| 215 | .term = null, | 215 | .term = null, |
| 216 | .env_map = null, | 216 | .env_map = null, |
| 217 | .cwd = null, | 217 | .cwd = null, |
| ... | @@ -293,17 +293,49 @@ pub fn killPosix(self: *ChildProcess) !Term { | ... | @@ -293,17 +293,49 @@ pub fn killPosix(self: *ChildProcess) !Term { |
| 293 | error.ProcessNotFound => return error.AlreadyTerminated, | 293 | error.ProcessNotFound => return error.AlreadyTerminated, |
| 294 | else => return err, | 294 | else => return err, |
| 295 | }; | 295 | }; |
| 296 | self.waitUnwrapped(); | 296 | self.waitUnwrappedPosix(); |
| 297 | return self.term.?; | 297 | return self.term.?; |
| 298 | } | 298 | } |
| 299 | | 299 | |
| 300 | pub const WaitError = SpawnError || std.os.windows.GetProcessMemoryInfoError; | 300 | pub const WaitError = SpawnError || std.os.windows.GetProcessMemoryInfoError; |
| 301 | | 301 | |
| | 302 | /// On some targets, `spawn` may not report all spawn errors, such as `error.InvalidExe`. |
| | 303 | /// This function will block until any spawn errors can be reported, and return them. |
| | 304 | pub fn waitForSpawn(self: *ChildProcess) SpawnError!void { |
| | 305 | if (native_os == .windows) return; // `spawn` reports everything |
| | 306 | if (self.term) |term| { |
| | 307 | _ = term catch |spawn_err| return spawn_err; |
| | 308 | return; |
| | 309 | } |
| | 310 | |
| | 311 | const err_pipe = self.err_pipe orelse return; |
| | 312 | self.err_pipe = null; |
| | 313 | |
| | 314 | // Wait for the child to report any errors in or before `execvpe`. |
| | 315 | if (readIntFd(err_pipe)) |child_err_int| { |
| | 316 | posix.close(err_pipe); |
| | 317 | const child_err: SpawnError = @errorCast(@errorFromInt(child_err_int)); |
| | 318 | self.term = child_err; |
| | 319 | return child_err; |
| | 320 | } else |_| { |
| | 321 | // Write end closed by CLOEXEC at the time of the `execvpe` call, indicating success! |
| | 322 | posix.close(err_pipe); |
| | 323 | } |
| | 324 | } |
| | 325 | |
| 302 | /// Blocks until child process terminates and then cleans up all resources. | 326 | /// Blocks until child process terminates and then cleans up all resources. |
| 303 | pub fn wait(self: *ChildProcess) WaitError!Term { | 327 | pub fn wait(self: *ChildProcess) WaitError!Term { |
| 304 | const term = if (native_os == .windows) try self.waitWindows() else self.waitPosix(); | 328 | try self.waitForSpawn(); // report spawn errors |
| | 329 | if (self.term) |term| { |
| | 330 | self.cleanupStreams(); |
| | 331 | return term; |
| | 332 | } |
| | 333 | switch (native_os) { |
| | 334 | .windows => try self.waitUnwrappedWindows(), |
| | 335 | else => self.waitUnwrappedPosix(), |
| | 336 | } |
| 305 | self.id = undefined; | 337 | self.id = undefined; |
| 306 | return term; | 338 | return self.term.?; |
| 307 | } | 339 | } |
| 308 | | 340 | |
| 309 | pub const RunResult = struct { | 341 | pub const RunResult = struct { |
| ... | @@ -405,26 +437,6 @@ pub fn run(args: struct { | ... | @@ -405,26 +437,6 @@ pub fn run(args: struct { |
| 405 | }; | 437 | }; |
| 406 | } | 438 | } |
| 407 | | 439 | |
| 408 | fn waitWindows(self: *ChildProcess) WaitError!Term { | | |
| 409 | if (self.term) |term| { | | |
| 410 | self.cleanupStreams(); | | |
| 411 | return term; | | |
| 412 | } | | |
| 413 | | | |
| 414 | try self.waitUnwrappedWindows(); | | |
| 415 | return self.term.?; | | |
| 416 | } | | |
| 417 | | | |
| 418 | fn waitPosix(self: *ChildProcess) SpawnError!Term { | | |
| 419 | if (self.term) |term| { | | |
| 420 | self.cleanupStreams(); | | |
| 421 | return term; | | |
| 422 | } | | |
| 423 | | | |
| 424 | self.waitUnwrapped(); | | |
| 425 | return self.term.?; | | |
| 426 | } | | |
| 427 | | | |
| 428 | fn waitUnwrappedWindows(self: *ChildProcess) WaitError!void { | 440 | fn waitUnwrappedWindows(self: *ChildProcess) WaitError!void { |
| 429 | const result = windows.WaitForSingleObjectEx(self.id, windows.INFINITE, false); | 441 | const result = windows.WaitForSingleObjectEx(self.id, windows.INFINITE, false); |
| 430 | | 442 | |
| ... | @@ -447,7 +459,7 @@ fn waitUnwrappedWindows(self: *ChildProcess) WaitError!void { | ... | @@ -447,7 +459,7 @@ fn waitUnwrappedWindows(self: *ChildProcess) WaitError!void { |
| 447 | return result; | 459 | return result; |
| 448 | } | 460 | } |
| 449 | | 461 | |
| 450 | fn waitUnwrapped(self: *ChildProcess) void { | 462 | fn waitUnwrappedPosix(self: *ChildProcess) void { |
| 451 | const res: posix.WaitPidResult = res: { | 463 | const res: posix.WaitPidResult = res: { |
| 452 | if (self.request_resource_usage_statistics) { | 464 | if (self.request_resource_usage_statistics) { |
| 453 | switch (native_os) { | 465 | switch (native_os) { |
| ... | @@ -469,7 +481,7 @@ fn waitUnwrapped(self: *ChildProcess) void { | ... | @@ -469,7 +481,7 @@ fn waitUnwrapped(self: *ChildProcess) void { |
| 469 | } | 481 | } |
| 470 | | 482 | |
| 471 | fn handleWaitResult(self: *ChildProcess, status: u32) void { | 483 | fn handleWaitResult(self: *ChildProcess, status: u32) void { |
| 472 | self.term = self.cleanupAfterWait(status); | 484 | self.term = statusToTerm(status); |
| 473 | } | 485 | } |
| 474 | | 486 | |
| 475 | fn cleanupStreams(self: *ChildProcess) void { | 487 | fn cleanupStreams(self: *ChildProcess) void { |
| ... | @@ -487,46 +499,6 @@ fn cleanupStreams(self: *ChildProcess) void { | ... | @@ -487,46 +499,6 @@ fn cleanupStreams(self: *ChildProcess) void { |
| 487 | } | 499 | } |
| 488 | } | 500 | } |
| 489 | | 501 | |
| 490 | fn cleanupAfterWait(self: *ChildProcess, status: u32) !Term { | | |
| 491 | if (self.err_pipe) |err_pipe| { | | |
| 492 | defer destroyPipe(err_pipe); | | |
| 493 | | | |
| 494 | if (native_os == .linux) { | | |
| 495 | var fd = [1]posix.pollfd{posix.pollfd{ | | |
| 496 | .fd = err_pipe[0], | | |
| 497 | .events = posix.POLL.IN, | | |
| 498 | .revents = undefined, | | |
| 499 | }}; | | |
| 500 | | | |
| 501 | // Check if the eventfd buffer stores a non-zero value by polling | | |
| 502 | // it, that's the error code returned by the child process. | | |
| 503 | _ = posix.poll(&fd, 0) catch unreachable; | | |
| 504 | | | |
| 505 | // According to eventfd(2) the descriptor is readable if the counter | | |
| 506 | // has a value greater than 0 | | |
| 507 | if ((fd[0].revents & posix.POLL.IN) != 0) { | | |
| 508 | const err_int = try readIntFd(err_pipe[0]); | | |
| 509 | return @as(SpawnError, @errorCast(@errorFromInt(err_int))); | | |
| 510 | } | | |
| 511 | } else { | | |
| 512 | // Write maxInt(ErrInt) to the write end of the err_pipe. This is after | | |
| 513 | // waitpid, so this write is guaranteed to be after the child | | |
| 514 | // pid potentially wrote an error. This way we can do a blocking | | |
| 515 | // read on the error pipe and either get maxInt(ErrInt) (no error) or | | |
| 516 | // an error code. | | |
| 517 | try writeIntFd(err_pipe[1], maxInt(ErrInt)); | | |
| 518 | const err_int = try readIntFd(err_pipe[0]); | | |
| 519 | // Here we potentially return the fork child's error from the parent | | |
| 520 | // pid. | | |
| 521 | if (err_int != maxInt(ErrInt)) { | | |
| 522 | return @as(SpawnError, @errorCast(@errorFromInt(err_int))); | | |
| 523 | } | | |
| 524 | } | | |
| 525 | } | | |
| 526 | | | |
| 527 | return statusToTerm(status); | | |
| 528 | } | | |
| 529 | | | |
| 530 | fn statusToTerm(status: u32) Term { | 502 | fn statusToTerm(status: u32) Term { |
| 531 | return if (posix.W.IFEXITED(status)) | 503 | return if (posix.W.IFEXITED(status)) |
| 532 | Term{ .Exited = posix.W.EXITSTATUS(status) } | 504 | Term{ .Exited = posix.W.EXITSTATUS(status) } |
| ... | @@ -636,18 +608,9 @@ fn spawnPosix(self: *ChildProcess) SpawnError!void { | ... | @@ -636,18 +608,9 @@ fn spawnPosix(self: *ChildProcess) SpawnError!void { |
| 636 | } | 608 | } |
| 637 | }; | 609 | }; |
| 638 | | 610 | |
| 639 | // This pipe is used to communicate errors between the time of fork | 611 | // This pipe communicates to the parent errors in the child between `fork` and `execvpe`. |
| 640 | // and execve from the child process to the parent process. | 612 | // It is closed by the child (via CLOEXEC) without writing if `execvpe` succeeds. |
| 641 | const err_pipe = blk: { | 613 | const err_pipe: [2]posix.fd_t = try posix.pipe2(.{ .CLOEXEC = true }); |
| 642 | if (native_os == .linux) { | | |
| 643 | const fd = try posix.eventfd(0, linux.EFD.CLOEXEC); | | |
| 644 | // There's no distinction between the readable and the writeable | | |
| 645 | // end with eventfd | | |
| 646 | break :blk [2]posix.fd_t{ fd, fd }; | | |
| 647 | } else { | | |
| 648 | break :blk try posix.pipe2(.{ .CLOEXEC = true }); | | |
| 649 | } | | |
| 650 | }; | | |
| 651 | errdefer destroyPipe(err_pipe); | 614 | errdefer destroyPipe(err_pipe); |
| 652 | | 615 | |
| 653 | const pid_result = try posix.fork(); | 616 | const pid_result = try posix.fork(); |
| ... | @@ -687,6 +650,11 @@ fn spawnPosix(self: *ChildProcess) SpawnError!void { | ... | @@ -687,6 +650,11 @@ fn spawnPosix(self: *ChildProcess) SpawnError!void { |
| 687 | } | 650 | } |
| 688 | | 651 | |
| 689 | // we are the parent | 652 | // we are the parent |
| | 653 | errdefer comptime unreachable; // The child is forked; we must not error from now on |
| | 654 | |
| | 655 | posix.close(err_pipe[1]); // make sure only the child holds the write end open |
| | 656 | self.err_pipe = err_pipe[0]; |
| | 657 | |
| 690 | const pid: i32 = @intCast(pid_result); | 658 | const pid: i32 = @intCast(pid_result); |
| 691 | if (self.stdin_behavior == .Pipe) { | 659 | if (self.stdin_behavior == .Pipe) { |
| 692 | self.stdin = .{ .handle = stdin_pipe[1] }; | 660 | self.stdin = .{ .handle = stdin_pipe[1] }; |
| ... | @@ -705,7 +673,6 @@ fn spawnPosix(self: *ChildProcess) SpawnError!void { | ... | @@ -705,7 +673,6 @@ fn spawnPosix(self: *ChildProcess) SpawnError!void { |
| 705 | } | 673 | } |
| 706 | | 674 | |
| 707 | self.id = pid; | 675 | self.id = pid; |
| 708 | self.err_pipe = err_pipe; | | |
| 709 | self.term = null; | 676 | self.term = null; |
| 710 | | 677 | |
| 711 | if (self.stdin_behavior == .Pipe) { | 678 | if (self.stdin_behavior == .Pipe) { |