| ... | ... | @@ -50,8 +50,20 @@ pub const ChildProcess = struct { |
| 50 | 50 | err_pipe: if (builtin.os == .windows) void else [2]os.fd_t, |
| 51 | 51 | llnode: if (builtin.os == .windows) void else TailQueue(*ChildProcess).Node, |
| 52 | 52 | |
| 53 | | pub const SpawnError = error{OutOfMemory} || os.ExecveError || os.SetIdError || |
| 54 | | os.ChangeCurDirError || windows.CreateProcessError; |
| 53 | pub const SpawnError = error{ |
| 54 | OutOfMemory, |
| 55 | |
| 56 | /// POSIX-only. `StdIo.Ignore` was selected and opening `/dev/null` returned ENODEV. |
| 57 | NoDevice, |
| 58 | |
| 59 | /// Windows-only. One of: |
| 60 | /// * `cwd` was provided and it could not be re-encoded into UTF16LE, or |
| 61 | /// * The `PATH` or `PATHEXT` environment variable contained invalid UTF-8. |
| 62 | InvalidUtf8, |
| 63 | |
| 64 | /// Windows-only. `cwd` was provided, but the path did not exist when spawning the child process. |
| 65 | CurrentWorkingDirectoryUnlinked, |
| 66 | } || os.ExecveError || os.SetIdError || os.ChangeCurDirError || windows.CreateProcessError; |
| 55 | 67 | |
| 56 | 68 | pub const Term = union(enum) { |
| 57 | 69 | Exited: u32, |
| ... | ... | @@ -102,7 +114,7 @@ pub const ChildProcess = struct { |
| 102 | 114 | } |
| 103 | 115 | |
| 104 | 116 | /// On success must call `kill` or `wait`. |
| 105 | | pub fn spawn(self: *ChildProcess) !void { |
| 117 | pub fn spawn(self: *ChildProcess) SpawnError!void { |
| 106 | 118 | if (builtin.os == .windows) { |
| 107 | 119 | return self.spawnWindows(); |
| 108 | 120 | } else { |
| ... | ... | @@ -110,7 +122,7 @@ pub const ChildProcess = struct { |
| 110 | 122 | } |
| 111 | 123 | } |
| 112 | 124 | |
| 113 | | pub fn spawnAndWait(self: *ChildProcess) !Term { |
| 125 | pub fn spawnAndWait(self: *ChildProcess) SpawnError!Term { |
| 114 | 126 | try self.spawn(); |
| 115 | 127 | return self.wait(); |
| 116 | 128 | } |
| ... | ... | @@ -162,7 +174,13 @@ pub const ChildProcess = struct { |
| 162 | 174 | |
| 163 | 175 | /// Spawns a child process, waits for it, collecting stdout and stderr, and then returns. |
| 164 | 176 | /// If it succeeds, the caller owns result.stdout and result.stderr memory. |
| 165 | | pub fn exec(allocator: *mem.Allocator, argv: []const []const u8, cwd: ?[]const u8, env_map: ?*const BufMap, max_output_size: usize) !ExecResult { |
| 177 | pub fn exec( |
| 178 | allocator: *mem.Allocator, |
| 179 | argv: []const []const u8, |
| 180 | cwd: ?[]const u8, |
| 181 | env_map: ?*const BufMap, |
| 182 | max_output_size: usize, |
| 183 | ) !ExecResult { |
| 166 | 184 | const child = try ChildProcess.init(argv, allocator); |
| 167 | 185 | defer child.deinit(); |
| 168 | 186 | |
| ... | ... | @@ -292,7 +310,7 @@ pub const ChildProcess = struct { |
| 292 | 310 | Term{ .Unknown = status }; |
| 293 | 311 | } |
| 294 | 312 | |
| 295 | | fn spawnPosix(self: *ChildProcess) !void { |
| 313 | fn spawnPosix(self: *ChildProcess) SpawnError!void { |
| 296 | 314 | const stdin_pipe = if (self.stdin_behavior == StdIo.Pipe) try os.pipe() else undefined; |
| 297 | 315 | errdefer if (self.stdin_behavior == StdIo.Pipe) { |
| 298 | 316 | destroyPipe(stdin_pipe); |
| ... | ... | @@ -309,7 +327,16 @@ pub const ChildProcess = struct { |
| 309 | 327 | }; |
| 310 | 328 | |
| 311 | 329 | const any_ignore = (self.stdin_behavior == StdIo.Ignore or self.stdout_behavior == StdIo.Ignore or self.stderr_behavior == StdIo.Ignore); |
| 312 | | const dev_null_fd = if (any_ignore) try os.openC(c"/dev/null", os.O_RDWR, 0) else undefined; |
| 330 | const dev_null_fd = if (any_ignore) |
| 331 | os.openC(c"/dev/null", os.O_RDWR, 0) catch |err| switch (err) { |
| 332 | error.PathAlreadyExists => unreachable, |
| 333 | error.NoSpaceLeft => unreachable, |
| 334 | error.FileTooBig => unreachable, |
| 335 | error.DeviceBusy => unreachable, |
| 336 | else => |e| return e, |
| 337 | } |
| 338 | else |
| 339 | undefined; |
| 313 | 340 | defer { |
| 314 | 341 | if (any_ignore) os.close(dev_null_fd); |
| 315 | 342 | } |
| ... | ... | @@ -403,7 +430,7 @@ pub const ChildProcess = struct { |
| 403 | 430 | } |
| 404 | 431 | } |
| 405 | 432 | |
| 406 | | fn spawnWindows(self: *ChildProcess) !void { |
| 433 | fn spawnWindows(self: *ChildProcess) SpawnError!void { |
| 407 | 434 | const saAttr = windows.SECURITY_ATTRIBUTES{ |
| 408 | 435 | .nLength = @sizeOf(windows.SECURITY_ATTRIBUTES), |
| 409 | 436 | .bInheritHandle = windows.TRUE, |
| ... | ... | @@ -412,11 +439,28 @@ pub const ChildProcess = struct { |
| 412 | 439 | |
| 413 | 440 | const any_ignore = (self.stdin_behavior == StdIo.Ignore or self.stdout_behavior == StdIo.Ignore or self.stderr_behavior == StdIo.Ignore); |
| 414 | 441 | |
| 415 | | const nul_handle = if (any_ignore) blk: { |
| 416 | | break :blk try windows.CreateFile("NUL", windows.GENERIC_READ, windows.FILE_SHARE_READ, null, windows.OPEN_EXISTING, windows.FILE_ATTRIBUTE_NORMAL, null); |
| 417 | | } else blk: { |
| 418 | | break :blk undefined; |
| 419 | | }; |
| 442 | const nul_handle = if (any_ignore) |
| 443 | windows.CreateFile( |
| 444 | "NUL", |
| 445 | windows.GENERIC_READ, |
| 446 | windows.FILE_SHARE_READ, |
| 447 | null, |
| 448 | windows.OPEN_EXISTING, |
| 449 | windows.FILE_ATTRIBUTE_NORMAL, |
| 450 | null, |
| 451 | ) catch |err| switch (err) { |
| 452 | error.SharingViolation => unreachable, // not possible for "NUL" |
| 453 | error.PathAlreadyExists => unreachable, // not possible for "NUL" |
| 454 | error.PipeBusy => unreachable, // not possible for "NUL" |
| 455 | error.InvalidUtf8 => unreachable, // not possible for "NUL" |
| 456 | error.BadPathName => unreachable, // not possible for "NUL" |
| 457 | error.FileNotFound => unreachable, // not possible for "NUL" |
| 458 | error.AccessDenied => unreachable, // not possible for "NUL" |
| 459 | error.NameTooLong => unreachable, // not possible for "NUL" |
| 460 | else => |e| return e, |
| 461 | } |
| 462 | else |
| 463 | undefined; |
| 420 | 464 | defer { |
| 421 | 465 | if (any_ignore) os.close(nul_handle); |
| 422 | 466 | } |
| ... | ... | @@ -542,10 +586,25 @@ pub const ChildProcess = struct { |
| 542 | 586 | windowsCreateProcess(app_name_w.ptr, cmd_line_w.ptr, envp_ptr, cwd_w_ptr, &siStartInfo, &piProcInfo) catch |no_path_err| { |
| 543 | 587 | if (no_path_err != error.FileNotFound) return no_path_err; |
| 544 | 588 | |
| 545 | | const PATH = try process.getEnvVarOwned(self.allocator, "PATH"); |
| 546 | | defer self.allocator.free(PATH); |
| 547 | | const PATHEXT = try process.getEnvVarOwned(self.allocator, "PATHEXT"); |
| 548 | | defer self.allocator.free(PATHEXT); |
| 589 | var free_path = true; |
| 590 | const PATH = process.getEnvVarOwned(self.allocator, "PATH") catch |err| switch (err) { |
| 591 | error.EnvironmentVariableNotFound => blk: { |
| 592 | free_path = false; |
| 593 | break :blk ""; |
| 594 | }, |
| 595 | else => |e| return e, |
| 596 | }; |
| 597 | defer if (free_path) self.allocator.free(PATH); |
| 598 | |
| 599 | var free_path_ext = true; |
| 600 | const PATHEXT = process.getEnvVarOwned(self.allocator, "PATHEXT") catch |err| switch (err) { |
| 601 | error.EnvironmentVariableNotFound => blk: { |
| 602 | free_path_ext = false; |
| 603 | break :blk ""; |
| 604 | }, |
| 605 | else => |e| return e, |
| 606 | }; |
| 607 | defer if (free_path_ext) self.allocator.free(PATHEXT); |
| 549 | 608 | |
| 550 | 609 | var it = mem.tokenize(PATH, ";"); |
| 551 | 610 | retry: while (it.next()) |search_path| { |
| ... | ... | @@ -717,7 +776,7 @@ fn destroyPipe(pipe: [2]os.fd_t) void { |
| 717 | 776 | // Child of fork calls this to report an error to the fork parent. |
| 718 | 777 | // Then the child exits. |
| 719 | 778 | fn forkChildErrReport(fd: i32, err: ChildProcess.SpawnError) noreturn { |
| 720 | | writeIntFd(fd, @as(ErrInt,@errorToInt(err))) catch {}; |
| 779 | writeIntFd(fd, @as(ErrInt, @errorToInt(err))) catch {}; |
| 721 | 780 | os.exit(1); |
| 722 | 781 | } |
| 723 | 782 | |