@@ -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,15 +114,15 @@ 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 {
109
121
return self .spawnPosix ();
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
0 commit comments