2019-05-24 18:27:18 -04:00
const std = @import ( " std.zig " ) ;
2021-10-04 23:47:27 -07:00
const builtin = @import ( " builtin " ) ;
2017-10-13 09:31:03 -04:00
const cstr = std . cstr ;
2018-09-02 23:25:04 -04:00
const unicode = std . unicode ;
2017-10-13 09:31:03 -04:00
const io = std . io ;
2019-05-26 13:17:34 -04:00
const fs = std . fs ;
2017-10-13 09:31:03 -04:00
const os = std . os ;
2019-05-26 13:17:34 -04:00
const process = std . process ;
2019-05-24 22:52:07 -04:00
const File = std . fs . File ;
2017-10-13 09:31:03 -04:00
const windows = os . windows ;
2021-08-25 00:00:39 -07:00
const linux = os . linux ;
2017-10-13 09:31:03 -04:00
const mem = std . mem ;
2021-06-17 17:36:42 -06:00
const math = std . math ;
2017-10-13 09:31:03 -04:00
const debug = std . debug ;
const BufMap = std . BufMap ;
2021-10-04 23:47:27 -07:00
const Os = std . builtin . Os ;
2019-05-04 13:54:28 +10:00
const TailQueue = std . TailQueue ;
2018-10-26 14:59:58 -04:00
const maxInt = std . math . maxInt ;
2020-12-26 13:50:26 -07:00
const assert = std . debug . assert ;
2017-04-04 00:17:24 -04:00
2018-11-13 05:08:37 -08:00
pub const ChildProcess = struct {
2022-01-14 17:04:03 -07:00
/// Available after calling `spawn()`. It is a race condition to use this
/// value after `wait()`.
2020-02-25 01:52:27 -05:00
pid : if ( builtin . os . tag == . windows ) void else i32 ,
handle : if ( builtin . os . tag == . windows ) windows . HANDLE else void ,
thread_handle : if ( builtin . os . tag == . windows ) windows . HANDLE else void ,
2017-10-13 09:31:03 -04:00
2021-10-29 00:37:25 +01:00
allocator : mem . Allocator ,
2017-09-07 23:10:23 -04:00
2019-10-21 22:22:08 +03:00
stdin : ? File ,
stdout : ? File ,
stderr : ? File ,
2017-09-26 01:01:49 -04:00
2019-10-21 22:22:08 +03:00
term : ? ( SpawnError ! Term ) ,
2017-09-26 01:01:49 -04:00
2019-10-21 22:22:08 +03:00
argv : [ ] const [ ] const u8 ,
2017-09-26 01:01:49 -04:00
/// Leave as null to use the current env map using the supplied allocator.
2019-10-21 22:22:08 +03:00
env_map : ? * const BufMap ,
2017-09-07 23:10:23 -04:00
2019-10-21 22:22:08 +03:00
stdin_behavior : StdIo ,
stdout_behavior : StdIo ,
stderr_behavior : StdIo ,
2017-04-04 00:17:24 -04:00
2017-09-26 01:01:49 -04:00
/// Set to change the user id when spawning the child process.
2020-09-03 15:08:37 +02:00
uid : if ( builtin . os . tag == . windows or builtin . os . tag == . wasi ) void else ? os . uid_t ,
2017-09-07 23:10:23 -04:00
2017-09-26 02:42:06 -04:00
/// Set to change the group id when spawning the child process.
2020-09-03 15:08:37 +02:00
gid : if ( builtin . os . tag == . windows or builtin . os . tag == . wasi ) void else ? os . gid_t ,
2017-09-26 02:42:06 -04:00
2017-09-26 01:01:49 -04:00
/// Set to change the current working directory when spawning the child process.
2019-10-21 22:22:08 +03:00
cwd : ? [ ] const u8 ,
2020-04-27 18:26:59 -04:00
/// Set to change the current working directory when spawning the child process.
/// This is not yet implemented for Windows. See https://github.com/ziglang/zig/issues/5190
/// Once that is done, `cwd` will be deprecated in favor of this field.
cwd_dir : ? fs . Dir = null ,
2017-09-26 01:01:49 -04:00
2020-02-25 01:52:27 -05:00
err_pipe : if ( builtin . os . tag == . windows ) void else [ 2 ] os . fd_t ,
2017-04-04 00:17:24 -04:00
2020-02-17 00:58:30 -05:00
expand_arg0 : Arg0Expand ,
pub const Arg0Expand = os . Arg0Expand ;
2019-11-10 14:58:24 -05:00
pub const SpawnError = error {
OutOfMemory ,
/// POSIX-only. `StdIo.Ignore` was selected and opening `/dev/null` returned ENODEV.
NoDevice ,
/// Windows-only. One of:
/// * `cwd` was provided and it could not be re-encoded into UTF16LE, or
/// * The `PATH` or `PATHEXT` environment variable contained invalid UTF-8.
InvalidUtf8 ,
/// Windows-only. `cwd` was provided, but the path did not exist when spawning the child process.
CurrentWorkingDirectoryUnlinked ,
2019-11-19 13:13:02 +02:00
} || os . ExecveError || os . SetIdError || os . ChangeCurDirError || windows . CreateProcessError || windows . WaitForSingleObjectError ;
2018-02-03 11:51:29 -05:00
2018-11-13 05:08:37 -08:00
pub const Term = union ( enum ) {
2021-03-24 18:27:56 -07:00
Exited : u8 ,
2019-05-26 23:35:26 -04:00
Signal : u32 ,
Stopped : u32 ,
Unknown : u32 ,
2017-04-04 00:17:24 -04:00
} ;
2018-11-13 05:08:37 -08:00
pub const StdIo = enum {
2017-04-04 00:17:24 -04:00
Inherit ,
Ignore ,
Pipe ,
Close ,
} ;
2017-09-26 01:01:49 -04:00
/// First argument in argv is the executable.
/// On success must call deinit.
2021-10-29 00:37:25 +01:00
pub fn init ( argv : [ ] const [ ] const u8 , allocator : mem . Allocator ) ! * ChildProcess {
2019-02-03 16:13:28 -05:00
const child = try allocator . create ( ChildProcess ) ;
child .* = ChildProcess {
2017-09-26 01:01:49 -04:00
. allocator = allocator ,
. argv = argv ,
. pid = undefined ,
2017-10-13 09:31:03 -04:00
. handle = undefined ,
2017-10-15 14:01:55 -04:00
. thread_handle = undefined ,
2017-09-26 01:01:49 -04:00
. err_pipe = undefined ,
. term = null ,
. env_map = null ,
. cwd = null ,
2020-10-04 19:22:34 +03:00
. uid = if ( builtin . os . tag == . windows or builtin . os . tag == . wasi ) { } else null ,
. gid = if ( builtin . os . tag == . windows or builtin . os . tag == . wasi ) { } else null ,
2017-09-26 01:01:49 -04:00
. stdin = null ,
. stdout = null ,
. stderr = null ,
. stdin_behavior = StdIo . Inherit ,
. stdout_behavior = StdIo . Inherit ,
. stderr_behavior = StdIo . Inherit ,
2020-02-17 00:58:30 -05:00
. expand_arg0 = . no_expand ,
2019-02-03 16:13:28 -05:00
} ;
2018-06-21 00:40:21 +09:00
errdefer allocator . destroy ( child ) ;
2017-09-26 01:01:49 -04:00
return child ;
}
2018-05-31 10:56:59 -04:00
pub fn setUserName ( self : * ChildProcess , name : [ ] const u8 ) ! void {
2018-01-07 16:51:46 -05:00
const user_info = try os . getUserInfo ( name ) ;
2017-09-26 02:42:06 -04:00
self . uid = user_info . uid ;
self . gid = user_info . gid ;
2017-09-26 01:01:49 -04:00
}
/// On success must call `kill` or `wait`.
2022-01-14 17:04:03 -07:00
/// After spawning the `pid` is available.
2019-11-10 14:58:24 -05:00
pub fn spawn ( self : * ChildProcess ) SpawnError ! void {
2020-02-25 01:52:27 -05:00
if ( builtin . os . tag == . windows ) {
2017-10-13 09:31:03 -04:00
return self . spawnWindows ( ) ;
} else {
return self . spawnPosix ( ) ;
2017-12-22 00:50:30 -05:00
}
2017-09-26 01:01:49 -04:00
}
2019-11-10 14:58:24 -05:00
pub fn spawnAndWait ( self : * ChildProcess ) SpawnError ! Term {
2018-01-07 16:51:46 -05:00
try self . spawn ( ) ;
2017-09-26 01:01:49 -04:00
return self . wait ( ) ;
2017-04-04 00:17:24 -04:00
}
2017-09-06 18:30:45 -04:00
/// Forcibly terminates child process and then cleans up all resources.
2018-05-31 10:56:59 -04:00
pub fn kill ( self : * ChildProcess ) ! Term {
2020-02-25 01:52:27 -05:00
if ( builtin . os . tag == . windows ) {
2017-10-13 09:31:03 -04:00
return self . killWindows ( 1 ) ;
} else {
return self . killPosix ( ) ;
}
}
2018-05-31 10:56:59 -04:00
pub fn killWindows ( self : * ChildProcess , exit_code : windows . UINT ) ! Term {
2017-10-13 09:31:03 -04:00
if ( self . term ) | term | {
self . cleanupStreams ( ) ;
return term ;
}
2019-05-24 22:52:07 -04:00
try windows . TerminateProcess ( self . handle , exit_code ) ;
2018-02-09 00:24:23 -05:00
try self . waitUnwrappedWindows ( ) ;
2018-06-09 23:42:14 -04:00
return self . term .? ;
2017-10-13 09:31:03 -04:00
}
2018-05-31 10:56:59 -04:00
pub fn killPosix ( self : * ChildProcess ) ! Term {
2017-09-07 23:10:23 -04:00
if ( self . term ) | term | {
2017-09-16 21:07:02 -04:00
self . cleanupStreams ( ) ;
2017-09-07 23:10:23 -04:00
return term ;
}
2021-09-06 22:16:02 +09:00
try os . kill ( self . pid , os . SIG . TERM ) ;
2017-09-07 23:10:23 -04:00
self . waitUnwrapped ( ) ;
2018-06-09 23:42:14 -04:00
return self . term .? ;
2017-09-06 18:30:45 -04:00
}
2017-04-16 14:14:11 -04:00
/// Blocks until child process terminates and then cleans up all resources.
2022-01-14 17:04:03 -07:00
/// TODO: set the pid to undefined in this function.
2018-05-31 10:56:59 -04:00
pub fn wait ( self : * ChildProcess ) ! Term {
2020-02-25 01:52:27 -05:00
if ( builtin . os . tag == . windows ) {
2017-10-13 09:31:03 -04:00
return self . waitWindows ( ) ;
} else {
return self . waitPosix ( ) ;
}
}
2018-11-13 05:08:37 -08:00
pub const ExecResult = struct {
2019-05-26 23:35:26 -04:00
term : Term ,
2017-12-12 14:35:53 -05:00
stdout : [ ] u8 ,
stderr : [ ] u8 ,
} ;
2020-12-29 11:13:00 -07:00
fn collectOutputPosix (
child : * const ChildProcess ,
stdout : * std . ArrayList ( u8 ) ,
stderr : * std . ArrayList ( u8 ) ,
max_output_bytes : usize ,
) ! void {
2020-10-20 08:51:21 +02:00
var poll_fds = [ _ ] os . pollfd {
2021-08-25 00:00:39 -07:00
. { . fd = child . stdout .? . handle , . events = os . POLL . IN , . revents = undefined } ,
. { . fd = child . stderr .? . handle , . events = os . POLL . IN , . revents = undefined } ,
2020-10-20 08:51:21 +02:00
} ;
var dead_fds : usize = 0 ;
2021-09-16 18:22:04 -07:00
// We ask for ensureTotalCapacity with this much extra space. This has more of an
2020-12-29 11:13:00 -07:00
// effect on small reads because once the reads start to get larger the amount
// of space an ArrayList will allocate grows exponentially.
const bump_amt = 512 ;
2020-10-20 08:51:21 +02:00
2021-08-25 00:00:39 -07:00
const err_mask = os . POLL . ERR | os . POLL . NVAL | os . POLL . HUP ;
2021-05-16 15:59:34 -04:00
2020-10-20 08:51:21 +02:00
while ( dead_fds < poll_fds . len ) {
const events = try os . poll ( & poll_fds , std . math . maxInt ( i32 ) ) ;
if ( events == 0 ) continue ;
2021-06-05 20:26:59 +02:00
var remove_stdout = false ;
var remove_stderr = false ;
2020-10-20 08:51:21 +02:00
// Try reading whatever is available before checking the error
// conditions.
2021-08-25 00:00:39 -07:00
// It's still possible to read after a POLL.HUP is received, always
2021-06-05 20:26:59 +02:00
// check if there's some data waiting to be read first.
2021-08-25 00:00:39 -07:00
if ( poll_fds [ 0 ] . revents & os . POLL . IN != 0 ) {
2020-10-20 08:51:21 +02:00
// stdout is ready.
2020-12-29 11:13:00 -07:00
const new_capacity = std . math . min ( stdout . items . len + bump_amt , max_output_bytes ) ;
2021-09-16 18:22:04 -07:00
try stdout . ensureTotalCapacity ( new_capacity ) ;
2020-12-29 14:02:12 -07:00
const buf = stdout . unusedCapacitySlice ( ) ;
if ( buf . len == 0 ) return error . StdoutStreamTooLong ;
2021-05-16 15:59:34 -04:00
const nread = try os . read ( poll_fds [ 0 ] . fd , buf ) ;
stdout . items . len += nread ;
2021-06-05 20:26:59 +02:00
// Remove the fd when the EOF condition is met.
remove_stdout = nread == 0 ;
} else {
remove_stdout = poll_fds [ 0 ] . revents & err_mask != 0 ;
2020-10-20 08:51:21 +02:00
}
2021-06-05 20:26:59 +02:00
2021-08-25 00:00:39 -07:00
if ( poll_fds [ 1 ] . revents & os . POLL . IN != 0 ) {
2020-10-20 08:51:21 +02:00
// stderr is ready.
2020-12-29 11:13:00 -07:00
const new_capacity = std . math . min ( stderr . items . len + bump_amt , max_output_bytes ) ;
2021-09-16 18:22:04 -07:00
try stderr . ensureTotalCapacity ( new_capacity ) ;
2020-12-29 14:02:12 -07:00
const buf = stderr . unusedCapacitySlice ( ) ;
if ( buf . len == 0 ) return error . StderrStreamTooLong ;
2021-05-16 15:59:34 -04:00
const nread = try os . read ( poll_fds [ 1 ] . fd , buf ) ;
stderr . items . len += nread ;
2021-06-05 20:26:59 +02:00
// Remove the fd when the EOF condition is met.
remove_stderr = nread == 0 ;
} else {
remove_stderr = poll_fds [ 1 ] . revents & err_mask != 0 ;
2020-10-20 08:51:21 +02:00
}
// Exclude the fds that signaled an error.
2021-06-05 20:26:59 +02:00
if ( remove_stdout ) {
2020-10-20 08:51:21 +02:00
poll_fds [ 0 ] . fd = - 1 ;
dead_fds += 1 ;
}
2021-06-05 20:26:59 +02:00
if ( remove_stderr ) {
2020-10-20 08:51:21 +02:00
poll_fds [ 1 ] . fd = - 1 ;
dead_fds += 1 ;
}
}
}
2021-06-17 17:36:42 -06:00
fn collectOutputWindows ( child : * const ChildProcess , outs : [ 2 ] * std . ArrayList ( u8 ) , max_output_bytes : usize ) ! void {
const bump_amt = 512 ;
const handles = [ _ ] windows . HANDLE {
child . stdout .? . handle ,
child . stderr .? . handle ,
2020-10-20 08:51:21 +02:00
} ;
2020-10-21 16:54:38 +02:00
2020-10-20 08:51:21 +02:00
var overlapped = [ _ ] windows . OVERLAPPED {
mem . zeroes ( windows . OVERLAPPED ) ,
mem . zeroes ( windows . OVERLAPPED ) ,
} ;
2021-06-17 17:36:42 -06:00
var wait_objects : [ 2 ] windows . HANDLE = undefined ;
var wait_object_count : u2 = 0 ;
// we need to cancel all pending IO before returning so our OVERLAPPED values don't go out of scope
defer for ( wait_objects [ 0 .. wait_object_count ] ) | o | {
_ = windows . kernel32 . CancelIo ( o ) ;
} ;
// Windows Async IO requires an initial call to ReadFile before waiting on the handle
for ( [ _ ] u1 { 0 , 1 } ) | i | {
2021-09-17 12:43:47 -07:00
const new_capacity = std . math . min ( outs [ i ] . items . len + bump_amt , max_output_bytes ) ;
try outs [ i ] . ensureTotalCapacity ( new_capacity ) ;
2021-06-17 17:36:42 -06:00
const buf = outs [ i ] . unusedCapacitySlice ( ) ;
_ = windows . kernel32 . ReadFile ( handles [ i ] , buf . ptr , math . cast ( u32 , buf . len ) catch maxInt ( u32 ) , null , & overlapped [ i ] ) ;
wait_objects [ wait_object_count ] = handles [ i ] ;
wait_object_count += 1 ;
}
while ( true ) {
const status = windows . kernel32 . WaitForMultipleObjects ( wait_object_count , & wait_objects , 0 , windows . INFINITE ) ;
if ( status == windows . WAIT_FAILED ) {
switch ( windows . kernel32 . GetLastError ( ) ) {
else = > | err | return windows . unexpectedError ( err ) ,
}
2020-10-20 08:51:21 +02:00
}
2021-06-17 17:36:42 -06:00
if ( status < windows . WAIT_OBJECT_0 or status > windows . WAIT_OBJECT_0 + wait_object_count - 1 )
unreachable ;
const wait_idx = status - windows . WAIT_OBJECT_0 ;
// this extra `i` index is needed to map the wait handle back to the stdout or stderr
// values since the wait_idx can change which handle it corresponds with
const i : u1 = if ( wait_objects [ wait_idx ] == handles [ 0 ] ) 0 else 1 ;
// remove completed event from the wait list
wait_object_count -= 1 ;
if ( wait_idx == 0 )
wait_objects [ 0 ] = wait_objects [ 1 ] ;
var read_bytes : u32 = undefined ;
if ( windows . kernel32 . GetOverlappedResult ( handles [ i ] , & overlapped [ i ] , & read_bytes , 0 ) == 0 ) {
switch ( windows . kernel32 . GetLastError ( ) ) {
. BROKEN_PIPE = > {
if ( wait_object_count == 0 )
break ;
continue ;
} ,
else = > | err | return windows . unexpectedError ( err ) ,
}
}
outs [ i ] . items . len += read_bytes ;
const new_capacity = std . math . min ( outs [ i ] . items . len + bump_amt , max_output_bytes ) ;
2021-09-16 18:22:04 -07:00
try outs [ i ] . ensureTotalCapacity ( new_capacity ) ;
2021-06-17 17:36:42 -06:00
const buf = outs [ i ] . unusedCapacitySlice ( ) ;
if ( buf . len == 0 ) return if ( i == 0 ) error . StdoutStreamTooLong else error . StderrStreamTooLong ;
_ = windows . kernel32 . ReadFile ( handles [ i ] , buf . ptr , math . cast ( u32 , buf . len ) catch maxInt ( u32 ) , null , & overlapped [ i ] ) ;
wait_objects [ wait_object_count ] = handles [ i ] ;
wait_object_count += 1 ;
2020-10-20 08:51:21 +02:00
}
}
2020-02-17 00:58:30 -05:00
/// Spawns a child process, waits for it, collecting stdout and stderr, and then returns.
/// If it succeeds, the caller owns result.stdout and result.stderr memory.
2020-03-30 14:23:22 -04:00
pub fn exec ( args : struct {
2021-10-29 00:37:25 +01:00
allocator : mem . Allocator ,
2020-02-17 00:58:30 -05:00
argv : [ ] const [ ] const u8 ,
cwd : ? [ ] const u8 = null ,
2020-04-27 18:26:59 -04:00
cwd_dir : ? fs . Dir = null ,
2020-02-17 00:58:30 -05:00
env_map : ? * const BufMap = null ,
max_output_bytes : usize = 50 * 1024 ,
expand_arg0 : Arg0Expand = . no_expand ,
} ) ! ExecResult {
const child = try ChildProcess . init ( args . argv , args . allocator ) ;
2017-12-12 14:35:53 -05:00
defer child . deinit ( ) ;
2020-02-17 00:58:30 -05:00
child . stdin_behavior = . Ignore ;
child . stdout_behavior = . Pipe ;
child . stderr_behavior = . Pipe ;
child . cwd = args . cwd ;
2020-04-27 18:26:59 -04:00
child . cwd_dir = args . cwd_dir ;
2020-02-17 00:58:30 -05:00
child . env_map = args . env_map ;
child . expand_arg0 = args . expand_arg0 ;
2017-12-12 14:35:53 -05:00
2018-01-07 16:51:46 -05:00
try child . spawn ( ) ;
2017-12-12 14:35:53 -05:00
2020-12-29 11:13:00 -07:00
// TODO collect output in a deadlock-avoiding way on Windows.
// https://github.com/ziglang/zig/issues/6343
2021-10-16 18:51:50 -06:00
if ( builtin . os . tag == . haiku ) {
2020-12-29 11:13:00 -07:00
const stdout_in = child . stdout .? . reader ( ) ;
const stderr_in = child . stderr .? . reader ( ) ;
const stdout = try stdout_in . readAllAlloc ( args . allocator , args . max_output_bytes ) ;
errdefer args . allocator . free ( stdout ) ;
const stderr = try stderr_in . readAllAlloc ( args . allocator , args . max_output_bytes ) ;
errdefer args . allocator . free ( stderr ) ;
return ExecResult {
. term = try child . wait ( ) ,
. stdout = stdout ,
. stderr = stderr ,
} ;
}
2020-10-20 08:51:21 +02:00
var stdout = std . ArrayList ( u8 ) . init ( args . allocator ) ;
var stderr = std . ArrayList ( u8 ) . init ( args . allocator ) ;
2021-06-05 20:26:59 +02:00
errdefer {
stdout . deinit ( ) ;
stderr . deinit ( ) ;
}
2017-12-12 14:35:53 -05:00
2020-10-20 08:51:21 +02:00
if ( builtin . os . tag == . windows ) {
2021-06-17 17:36:42 -06:00
try collectOutputWindows ( child , [ _ ] * std . ArrayList ( u8 ) { & stdout , & stderr } , args . max_output_bytes ) ;
2020-10-20 08:51:21 +02:00
} else {
try collectOutputPosix ( child , & stdout , & stderr , args . max_output_bytes ) ;
}
2018-11-13 05:08:37 -08:00
return ExecResult {
2018-01-07 16:51:46 -05:00
. term = try child . wait ( ) ,
2020-10-20 08:51:21 +02:00
. stdout = stdout . toOwnedSlice ( ) ,
. stderr = stderr . toOwnedSlice ( ) ,
2017-12-12 14:35:53 -05:00
} ;
}
2018-05-31 10:56:59 -04:00
fn waitWindows ( self : * ChildProcess ) ! Term {
2017-10-13 09:31:03 -04:00
if ( self . term ) | term | {
self . cleanupStreams ( ) ;
return term ;
}
2018-01-07 16:51:46 -05:00
try self . waitUnwrappedWindows ( ) ;
2018-06-09 23:42:14 -04:00
return self . term .? ;
2017-10-13 09:31:03 -04:00
}
2018-05-31 10:56:59 -04:00
fn waitPosix ( self : * ChildProcess ) ! Term {
2017-09-07 23:10:23 -04:00
if ( self . term ) | term | {
2017-09-16 21:07:02 -04:00
self . cleanupStreams ( ) ;
2017-09-07 23:10:23 -04:00
return term ;
}
self . waitUnwrapped ( ) ;
2018-06-09 23:42:14 -04:00
return self . term .? ;
2017-09-07 23:10:23 -04:00
}
2018-05-31 10:56:59 -04:00
pub fn deinit ( self : * ChildProcess ) void {
2017-09-26 01:01:49 -04:00
self . allocator . destroy ( self ) ;
}
2018-05-31 10:56:59 -04:00
fn waitUnwrappedWindows ( self : * ChildProcess ) ! void {
2019-11-19 16:31:24 +11:00
const result = windows . WaitForSingleObjectEx ( self . handle , windows . INFINITE , false ) ;
2017-10-13 09:31:03 -04:00
2019-11-08 17:05:20 -05:00
self . term = @as ( SpawnError ! Term , x : {
2017-10-13 09:31:03 -04:00
var exit_code : windows . DWORD = undefined ;
2019-05-27 02:00:39 -04:00
if ( windows . kernel32 . GetExitCodeProcess ( self . handle , & exit_code ) == 0 ) {
2018-11-13 05:08:37 -08:00
break : x Term { . Unknown = 0 } ;
2017-10-13 09:31:03 -04:00
} else {
2021-03-24 18:27:56 -07:00
break : x Term { . Exited = @truncate ( u8 , exit_code ) } ;
2017-10-13 09:31:03 -04:00
}
} ) ;
2017-10-31 22:24:02 -04:00
os . close ( self . handle ) ;
os . close ( self . thread_handle ) ;
2017-10-13 09:31:03 -04:00
self . cleanupStreams ( ) ;
return result ;
}
2018-05-31 10:56:59 -04:00
fn waitUnwrapped ( self : * ChildProcess ) void {
2020-10-16 18:14:39 -07:00
const status = os . waitpid ( self . pid , 0 ) . status ;
2019-05-24 22:52:07 -04:00
self . cleanupStreams ( ) ;
2020-10-16 18:14:39 -07:00
self . handleWaitResult ( status ) ;
2017-09-07 23:10:23 -04:00
}
2019-05-26 23:35:26 -04:00
fn handleWaitResult ( self : * ChildProcess , status : u32 ) void {
2020-09-03 18:09:55 +03:00
self . term = self . cleanupAfterWait ( status ) ;
2017-09-07 23:10:23 -04:00
}
2017-04-04 00:17:24 -04:00
2018-05-31 10:56:59 -04:00
fn cleanupStreams ( self : * ChildProcess ) void {
2018-05-10 00:29:49 -04:00
if ( self . stdin ) | * stdin | {
stdin . close ( ) ;
self . stdin = null ;
}
if ( self . stdout ) | * stdout | {
stdout . close ( ) ;
self . stdout = null ;
}
if ( self . stderr ) | * stderr | {
stderr . close ( ) ;
self . stderr = null ;
}
2017-09-07 23:10:23 -04:00
}
2019-05-26 23:35:26 -04:00
fn cleanupAfterWait ( self : * ChildProcess , status : u32 ) ! Term {
2019-12-16 10:56:53 +01:00
defer destroyPipe ( self . err_pipe ) ;
2017-04-04 00:17:24 -04:00
2020-02-25 01:52:27 -05:00
if ( builtin . os . tag == . linux ) {
2019-12-16 10:57:29 +01:00
var fd = [ 1 ] std . os . pollfd { std . os . pollfd {
. fd = self . err_pipe [ 0 ] ,
2021-08-25 00:00:39 -07:00
. events = std . os . POLL . IN ,
2019-12-16 10:57:29 +01:00
. revents = undefined ,
} } ;
// Check if the eventfd buffer stores a non-zero value by polling
// it, that's the error code returned by the child process.
_ = std . os . poll ( & fd , 0 ) catch unreachable ;
// According to eventfd(2) the descriptro is readable if the counter
// has a value greater than 0
2021-08-25 00:00:39 -07:00
if ( ( fd [ 0 ] . revents & std . os . POLL . IN ) != 0 ) {
2019-12-16 10:57:29 +01:00
const err_int = try readIntFd ( self . err_pipe [ 0 ] ) ;
return @errSetCast ( SpawnError , @intToError ( err_int ) ) ;
}
} else {
// Write maxInt(ErrInt) to the write end of the err_pipe. This is after
// waitpid, so this write is guaranteed to be after the child
// pid potentially wrote an error. This way we can do a blocking
// read on the error pipe and either get maxInt(ErrInt) (no error) or
// an error code.
try writeIntFd ( self . err_pipe [ 1 ] , maxInt ( ErrInt ) ) ;
const err_int = try readIntFd ( self . err_pipe [ 0 ] ) ;
// Here we potentially return the fork child's error from the parent
// pid.
if ( err_int != maxInt ( ErrInt ) ) {
return @errSetCast ( SpawnError , @intToError ( err_int ) ) ;
}
2017-04-04 00:17:24 -04:00
}
return statusToTerm ( status ) ;
}
2019-05-26 23:35:26 -04:00
fn statusToTerm ( status : u32 ) Term {
2021-08-25 00:00:39 -07:00
return if ( os . W . IFEXITED ( status ) )
Term { . Exited = os . W . EXITSTATUS ( status ) }
else if ( os . W . IFSIGNALED ( status ) )
Term { . Signal = os . W . TERMSIG ( status ) }
else if ( os . W . IFSTOPPED ( status ) )
Term { . Stopped = os . W . STOPSIG ( status ) }
2017-12-22 00:50:30 -05:00
else
2018-11-13 05:08:37 -08:00
Term { . Unknown = status } ;
2017-04-04 00:17:24 -04:00
}
2019-11-10 14:58:24 -05:00
fn spawnPosix ( self : * ChildProcess ) SpawnError ! void {
2021-08-25 00:00:39 -07:00
const pipe_flags = if ( io . is_async ) os . O . NONBLOCK else 0 ;
2020-02-06 17:56:40 -05:00
const stdin_pipe = if ( self . stdin_behavior == StdIo . Pipe ) try os . pipe2 ( pipe_flags ) else undefined ;
2018-05-10 00:29:49 -04:00
errdefer if ( self . stdin_behavior == StdIo . Pipe ) {
destroyPipe ( stdin_pipe ) ;
} ;
2017-04-04 00:17:24 -04:00
2020-02-06 17:56:40 -05:00
const stdout_pipe = if ( self . stdout_behavior == StdIo . Pipe ) try os . pipe2 ( pipe_flags ) else undefined ;
2018-05-10 00:29:49 -04:00
errdefer if ( self . stdout_behavior == StdIo . Pipe ) {
destroyPipe ( stdout_pipe ) ;
} ;
2017-04-04 00:17:24 -04:00
2020-02-06 17:56:40 -05:00
const stderr_pipe = if ( self . stderr_behavior == StdIo . Pipe ) try os . pipe2 ( pipe_flags ) else undefined ;
2018-05-10 00:29:49 -04:00
errdefer if ( self . stderr_behavior == StdIo . Pipe ) {
destroyPipe ( stderr_pipe ) ;
} ;
2017-04-04 00:17:24 -04:00
2017-09-26 01:01:49 -04:00
const any_ignore = ( self . stdin_behavior == StdIo . Ignore or self . stdout_behavior == StdIo . Ignore or self . stderr_behavior == StdIo . Ignore ) ;
2019-11-10 14:58:24 -05:00
const dev_null_fd = if ( any_ignore )
2021-08-25 00:00:39 -07:00
os . openZ ( " /dev/null " , os . O . RDWR , 0 ) catch | err | switch ( err ) {
2019-11-10 14:58:24 -05:00
error . PathAlreadyExists = > unreachable ,
error . NoSpaceLeft = > unreachable ,
error . FileTooBig = > unreachable ,
error . DeviceBusy = > unreachable ,
2020-04-02 23:39:25 -06:00
error . FileLocksNotSupported = > unreachable ,
2020-07-31 00:54:33 +02:00
error . BadPathName = > unreachable , // Windows-only
2020-11-22 23:28:40 +01:00
error . WouldBlock = > unreachable ,
2019-11-10 14:58:24 -05:00
else = > | e | return e ,
}
else
undefined ;
2018-05-10 00:29:49 -04:00
defer {
if ( any_ignore ) os . close ( dev_null_fd ) ;
}
2017-09-26 01:01:49 -04:00
2020-12-26 13:50:26 -07:00
var arena_allocator = std . heap . ArenaAllocator . init ( self . allocator ) ;
defer arena_allocator . deinit ( ) ;
2021-10-29 02:08:41 +01:00
const arena = arena_allocator . allocator ( ) ;
2020-12-26 13:50:26 -07:00
// The POSIX standard does not allow malloc() between fork() and execve(),
// and `self.allocator` may be a libc allocator.
// I have personally observed the child process deadlocking when it tries
// to call malloc() due to a heap allocation between fork() and execve(),
// in musl v1.1.24.
// Additionally, we want to reduce the number of possible ways things
// can fail between fork() and execve().
// Therefore, we do all the allocation for the execve() before the fork().
// This means we must do the null-termination of argv and env vars here.
2020-12-27 13:00:35 +01:00
const argv_buf = try arena . allocSentinel ( ? [ * : 0 ] u8 , self . argv . len , null ) ;
for ( self . argv ) | arg , i | argv_buf [ i ] = ( try arena . dupeZ ( u8 , arg ) ) . ptr ;
2020-12-26 13:50:26 -07:00
const envp = m : {
if ( self . env_map ) | env_map | {
const envp_buf = try createNullDelimitedEnvMap ( arena , env_map ) ;
break : m envp_buf . ptr ;
2021-10-04 23:47:27 -07:00
} else if ( builtin . link_libc ) {
2020-12-26 13:50:26 -07:00
break : m std . c . environ ;
2021-10-04 23:47:27 -07:00
} else if ( builtin . output_mode == . Exe ) {
2020-12-26 13:50:26 -07:00
// Then we have Zig start code and this works.
// TODO type-safety for null-termination of `os.environ`.
break : m @ptrCast ( [ * : null ] ? [ * : 0 ] u8 , os . environ . ptr ) ;
} else {
// TODO come up with a solution for this.
@compileError ( " missing std lib enhancement: ChildProcess implementation has no way to collect the environment variables to forward to the child process " ) ;
}
2017-09-26 01:01:49 -04:00
} ;
2017-04-04 00:17:24 -04:00
// This pipe is used to communicate errors between the time of fork
// and execve from the child process to the parent process.
2019-12-16 10:56:53 +01:00
const err_pipe = blk : {
2020-02-25 01:52:27 -05:00
if ( builtin . os . tag == . linux ) {
2021-08-25 00:00:39 -07:00
const fd = try os . eventfd ( 0 , linux . EFD . CLOEXEC ) ;
2019-12-16 10:56:53 +01:00
// There's no distinction between the readable and the writeable
// end with eventfd
break : blk [ 2 ] os . fd_t { fd , fd } ;
} else {
2021-08-25 00:00:39 -07:00
break : blk try os . pipe2 ( os . O . CLOEXEC ) ;
2019-12-16 10:56:53 +01:00
}
} ;
2018-01-23 23:08:09 -05:00
errdefer destroyPipe ( err_pipe ) ;
2017-04-04 00:17:24 -04:00
2019-05-25 13:07:44 -04:00
const pid_result = try os . fork ( ) ;
2017-09-07 23:10:23 -04:00
if ( pid_result == 0 ) {
2017-04-04 00:17:24 -04:00
// we are the child
2019-05-25 13:07:44 -04:00
setUpChildIo ( self . stdin_behavior , stdin_pipe [ 0 ] , os . STDIN_FILENO , dev_null_fd ) catch | err | forkChildErrReport ( err_pipe [ 1 ] , err ) ;
setUpChildIo ( self . stdout_behavior , stdout_pipe [ 1 ] , os . STDOUT_FILENO , dev_null_fd ) catch | err | forkChildErrReport ( err_pipe [ 1 ] , err ) ;
setUpChildIo ( self . stderr_behavior , stderr_pipe [ 1 ] , os . STDERR_FILENO , dev_null_fd ) catch | err | forkChildErrReport ( err_pipe [ 1 ] , err ) ;
2017-04-04 00:17:24 -04:00
2019-05-25 13:07:44 -04:00
if ( self . stdin_behavior == . Pipe ) {
2018-11-28 18:33:55 -05:00
os . close ( stdin_pipe [ 0 ] ) ;
os . close ( stdin_pipe [ 1 ] ) ;
}
2019-05-25 13:07:44 -04:00
if ( self . stdout_behavior == . Pipe ) {
2018-11-28 18:33:55 -05:00
os . close ( stdout_pipe [ 0 ] ) ;
os . close ( stdout_pipe [ 1 ] ) ;
}
2019-05-25 13:07:44 -04:00
if ( self . stderr_behavior == . Pipe ) {
2018-11-28 18:33:55 -05:00
os . close ( stderr_pipe [ 0 ] ) ;
os . close ( stderr_pipe [ 1 ] ) ;
}
2020-04-27 18:26:59 -04:00
if ( self . cwd_dir ) | cwd | {
os . fchdir ( cwd . fd ) catch | err | forkChildErrReport ( err_pipe [ 1 ] , err ) ;
} else if ( self . cwd ) | cwd | {
2019-05-26 13:17:34 -04:00
os . chdir ( cwd ) catch | err | forkChildErrReport ( err_pipe [ 1 ] , err ) ;
2017-04-30 18:56:24 -04:00
}
2017-09-26 02:42:06 -04:00
if ( self . gid ) | gid | {
2019-05-26 13:17:34 -04:00
os . setregid ( gid , gid ) catch | err | forkChildErrReport ( err_pipe [ 1 ] , err ) ;
2017-09-26 01:01:49 -04:00
}
2017-09-26 03:17:35 -04:00
if ( self . uid ) | uid | {
2019-05-26 13:17:34 -04:00
os . setreuid ( uid , uid ) catch | err | forkChildErrReport ( err_pipe [ 1 ] , err ) ;
2017-09-26 03:17:35 -04:00
}
2020-12-26 13:50:26 -07:00
const err = switch ( self . expand_arg0 ) {
2020-12-27 13:00:35 +01:00
. expand = > os . execvpeZ_expandArg0 ( . expand , argv_buf . ptr [ 0 ] .? , argv_buf . ptr , envp ) ,
. no_expand = > os . execvpeZ_expandArg0 ( . no_expand , argv_buf . ptr [ 0 ] .? , argv_buf . ptr , envp ) ,
2020-12-26 13:50:26 -07:00
} ;
2019-10-16 15:10:38 -04:00
forkChildErrReport ( err_pipe [ 1 ] , err ) ;
2017-04-04 00:17:24 -04:00
}
// we are the parent
2018-06-17 02:57:07 -04:00
const pid = @intCast ( i32 , pid_result ) ;
2017-10-31 04:47:55 -04:00
if ( self . stdin_behavior == StdIo . Pipe ) {
2020-05-01 23:17:15 -04:00
self . stdin = File { . handle = stdin_pipe [ 1 ] } ;
2017-10-31 04:47:55 -04:00
} else {
self . stdin = null ;
2017-09-07 23:10:23 -04:00
}
2017-10-31 04:47:55 -04:00
if ( self . stdout_behavior == StdIo . Pipe ) {
2020-05-01 23:17:15 -04:00
self . stdout = File { . handle = stdout_pipe [ 0 ] } ;
2017-10-31 04:47:55 -04:00
} else {
self . stdout = null ;
2017-09-07 23:10:23 -04:00
}
2017-10-31 04:47:55 -04:00
if ( self . stderr_behavior == StdIo . Pipe ) {
2020-05-01 23:17:15 -04:00
self . stderr = File { . handle = stderr_pipe [ 0 ] } ;
2017-10-31 04:47:55 -04:00
} else {
self . stderr = null ;
2017-09-07 23:10:23 -04:00
}
2017-09-26 01:01:49 -04:00
self . pid = pid ;
self . err_pipe = err_pipe ;
self . term = null ;
2017-09-07 23:10:23 -04:00
2018-05-10 00:29:49 -04:00
if ( self . stdin_behavior == StdIo . Pipe ) {
os . close ( stdin_pipe [ 0 ] ) ;
}
if ( self . stdout_behavior == StdIo . Pipe ) {
os . close ( stdout_pipe [ 1 ] ) ;
}
if ( self . stderr_behavior == StdIo . Pipe ) {
os . close ( stderr_pipe [ 1 ] ) ;
}
2017-04-04 00:17:24 -04:00
}
2019-11-10 14:58:24 -05:00
fn spawnWindows ( self : * ChildProcess ) SpawnError ! void {
2018-11-13 05:08:37 -08:00
const saAttr = windows . SECURITY_ATTRIBUTES {
2017-10-15 14:01:55 -04:00
. nLength = @sizeOf ( windows . SECURITY_ATTRIBUTES ) ,
. bInheritHandle = windows . TRUE ,
. lpSecurityDescriptor = null ,
} ;
2017-10-13 09:31:03 -04:00
2018-05-10 00:29:49 -04:00
const any_ignore = ( self . stdin_behavior == StdIo . Ignore or self . stdout_behavior == StdIo . Ignore or self . stderr_behavior == StdIo . Ignore ) ;
2017-10-13 09:31:03 -04:00
2019-11-10 14:58:24 -05:00
const nul_handle = if ( any_ignore )
2020-09-28 22:17:50 -07:00
// "\Device\Null" or "\??\NUL"
2020-09-03 08:51:10 +02:00
windows . OpenFile ( & [ _ ] u16 { '\\' , 'D' , 'e' , 'v' , 'i' , 'c' , 'e' , '\\' , 'N' , 'u' , 'l' , 'l' } , . {
2020-07-31 19:16:04 +02:00
. access_mask = windows . GENERIC_READ | windows . SYNCHRONIZE ,
2020-07-30 17:00:50 +02:00
. share_access = windows . FILE_SHARE_READ ,
. creation = windows . OPEN_EXISTING ,
. io_mode = . blocking ,
} ) catch | err | switch ( err ) {
2019-11-10 14:58:24 -05:00
error . PathAlreadyExists = > unreachable , // not possible for "NUL"
error . PipeBusy = > unreachable , // not possible for "NUL"
error . FileNotFound = > unreachable , // not possible for "NUL"
error . AccessDenied = > unreachable , // not possible for "NUL"
error . NameTooLong = > unreachable , // not possible for "NUL"
2020-07-30 17:00:50 +02:00
error . WouldBlock = > unreachable , // not possible for "NUL"
2019-11-10 14:58:24 -05:00
else = > | e | return e ,
}
else
undefined ;
2018-05-10 00:29:49 -04:00
defer {
if ( any_ignore ) os . close ( nul_handle ) ;
}
2017-10-13 09:31:03 -04:00
if ( any_ignore ) {
2019-05-27 02:00:39 -04:00
try windows . SetHandleInformation ( nul_handle , windows . HANDLE_FLAG_INHERIT , 0 ) ;
2017-10-13 09:31:03 -04:00
}
var g_hChildStd_IN_Rd : ? windows . HANDLE = null ;
var g_hChildStd_IN_Wr : ? windows . HANDLE = null ;
switch ( self . stdin_behavior ) {
StdIo . Pipe = > {
2018-10-15 21:38:01 -04:00
try windowsMakePipeIn ( & g_hChildStd_IN_Rd , & g_hChildStd_IN_Wr , & saAttr ) ;
2017-10-13 09:31:03 -04:00
} ,
StdIo . Ignore = > {
g_hChildStd_IN_Rd = nul_handle ;
} ,
StdIo . Inherit = > {
2019-05-27 02:00:39 -04:00
g_hChildStd_IN_Rd = windows . GetStdHandle ( windows . STD_INPUT_HANDLE ) catch null ;
2017-10-13 09:31:03 -04:00
} ,
StdIo . Close = > {
g_hChildStd_IN_Rd = null ;
} ,
}
2018-05-10 00:29:49 -04:00
errdefer if ( self . stdin_behavior == StdIo . Pipe ) {
windowsDestroyPipe ( g_hChildStd_IN_Rd , g_hChildStd_IN_Wr ) ;
} ;
2017-10-13 09:31:03 -04:00
var g_hChildStd_OUT_Rd : ? windows . HANDLE = null ;
var g_hChildStd_OUT_Wr : ? windows . HANDLE = null ;
switch ( self . stdout_behavior ) {
StdIo . Pipe = > {
2021-06-17 17:36:42 -06:00
try windowsMakeAsyncPipe ( & g_hChildStd_OUT_Rd , & g_hChildStd_OUT_Wr , & saAttr ) ;
2017-10-13 09:31:03 -04:00
} ,
StdIo . Ignore = > {
g_hChildStd_OUT_Wr = nul_handle ;
} ,
StdIo . Inherit = > {
2019-05-27 02:00:39 -04:00
g_hChildStd_OUT_Wr = windows . GetStdHandle ( windows . STD_OUTPUT_HANDLE ) catch null ;
2017-10-13 09:31:03 -04:00
} ,
StdIo . Close = > {
g_hChildStd_OUT_Wr = null ;
} ,
}
2018-05-10 00:29:49 -04:00
errdefer if ( self . stdin_behavior == StdIo . Pipe ) {
windowsDestroyPipe ( g_hChildStd_OUT_Rd , g_hChildStd_OUT_Wr ) ;
} ;
2017-10-13 09:31:03 -04:00
var g_hChildStd_ERR_Rd : ? windows . HANDLE = null ;
var g_hChildStd_ERR_Wr : ? windows . HANDLE = null ;
switch ( self . stderr_behavior ) {
StdIo . Pipe = > {
2021-06-17 17:36:42 -06:00
try windowsMakeAsyncPipe ( & g_hChildStd_ERR_Rd , & g_hChildStd_ERR_Wr , & saAttr ) ;
2017-10-13 09:31:03 -04:00
} ,
StdIo . Ignore = > {
g_hChildStd_ERR_Wr = nul_handle ;
} ,
StdIo . Inherit = > {
2019-05-27 02:00:39 -04:00
g_hChildStd_ERR_Wr = windows . GetStdHandle ( windows . STD_ERROR_HANDLE ) catch null ;
2017-10-13 09:31:03 -04:00
} ,
StdIo . Close = > {
g_hChildStd_ERR_Wr = null ;
} ,
}
2018-05-10 00:29:49 -04:00
errdefer if ( self . stdin_behavior == StdIo . Pipe ) {
windowsDestroyPipe ( g_hChildStd_ERR_Rd , g_hChildStd_ERR_Wr ) ;
} ;
2017-10-13 09:31:03 -04:00
2018-01-07 16:51:46 -05:00
const cmd_line = try windowsCreateCommandLine ( self . allocator , self . argv ) ;
2017-10-13 09:31:03 -04:00
defer self . allocator . free ( cmd_line ) ;
2018-11-13 05:08:37 -08:00
var siStartInfo = windows . STARTUPINFOW {
2018-09-02 23:25:04 -04:00
. cb = @sizeOf ( windows . STARTUPINFOW ) ,
2017-10-13 09:31:03 -04:00
. hStdError = g_hChildStd_ERR_Wr ,
. hStdOutput = g_hChildStd_OUT_Wr ,
. hStdInput = g_hChildStd_IN_Rd ,
. dwFlags = windows . STARTF_USESTDHANDLES ,
. lpReserved = null ,
. lpDesktop = null ,
. lpTitle = null ,
. dwX = 0 ,
. dwY = 0 ,
. dwXSize = 0 ,
. dwYSize = 0 ,
. dwXCountChars = 0 ,
. dwYCountChars = 0 ,
. dwFillAttribute = 0 ,
. wShowWindow = 0 ,
. cbReserved2 = 0 ,
. lpReserved2 = null ,
} ;
var piProcInfo : windows . PROCESS_INFORMATION = undefined ;
2019-12-28 16:40:59 +11:00
const cwd_w = if ( self . cwd ) | cwd | try unicode . utf8ToUtf16LeWithNull ( self . allocator , cwd ) else null ;
2018-09-02 23:25:04 -04:00
defer if ( cwd_w ) | cwd | self . allocator . free ( cwd ) ;
const cwd_w_ptr = if ( cwd_w ) | cwd | cwd . ptr else null ;
2017-10-13 09:31:03 -04:00
2019-05-24 00:13:13 -04:00
const maybe_envp_buf = if ( self . env_map ) | env_map | try createWindowsEnvBlock ( self . allocator , env_map ) else null ;
2017-10-13 09:31:03 -04:00
defer if ( maybe_envp_buf ) | envp_buf | self . allocator . free ( envp_buf ) ;
const envp_ptr = if ( maybe_envp_buf ) | envp_buf | envp_buf . ptr else null ;
2017-10-16 02:27:51 -04:00
// the cwd set in ChildProcess is in effect when choosing the executable path
// to match posix semantics
2020-01-10 19:25:26 -05:00
const app_path = x : {
2017-12-22 02:33:39 -05:00
if ( self . cwd ) | cwd | {
2019-12-01 21:31:00 -05:00
const resolved = try fs . path . resolve ( self . allocator , & [ _ ] [ ] const u8 { cwd , self . argv [ 0 ] } ) ;
2017-12-22 02:33:39 -05:00
defer self . allocator . free ( resolved ) ;
2018-01-07 16:51:46 -05:00
break : x try cstr . addNullByte ( self . allocator , resolved ) ;
2017-12-22 02:33:39 -05:00
} else {
2018-01-07 16:51:46 -05:00
break : x try cstr . addNullByte ( self . allocator , self . argv [ 0 ] ) ;
2017-12-22 02:33:39 -05:00
}
2017-10-16 02:27:51 -04:00
} ;
2020-01-10 19:25:26 -05:00
defer self . allocator . free ( app_path ) ;
2017-10-16 02:27:51 -04:00
2020-01-10 19:25:26 -05:00
const app_path_w = try unicode . utf8ToUtf16LeWithNull ( self . allocator , app_path ) ;
defer self . allocator . free ( app_path_w ) ;
2018-09-02 23:25:04 -04:00
const cmd_line_w = try unicode . utf8ToUtf16LeWithNull ( self . allocator , cmd_line ) ;
defer self . allocator . free ( cmd_line_w ) ;
2020-01-10 19:25:26 -05:00
windowsCreateProcess ( app_path_w . ptr , cmd_line_w . ptr , envp_ptr , cwd_w_ptr , & siStartInfo , & piProcInfo ) catch | no_path_err | {
2018-05-10 00:29:49 -04:00
if ( no_path_err != error . FileNotFound ) return no_path_err ;
2017-10-16 02:27:51 -04:00
2019-11-10 14:58:24 -05:00
var free_path = true ;
const PATH = process . getEnvVarOwned ( self . allocator , " PATH " ) catch | err | switch ( err ) {
error . EnvironmentVariableNotFound = > blk : {
free_path = false ;
break : blk " " ;
} ,
else = > | e | return e ,
} ;
defer if ( free_path ) self . allocator . free ( PATH ) ;
var free_path_ext = true ;
const PATHEXT = process . getEnvVarOwned ( self . allocator , " PATHEXT " ) catch | err | switch ( err ) {
error . EnvironmentVariableNotFound = > blk : {
free_path_ext = false ;
break : blk " " ;
} ,
else = > | e | return e ,
} ;
defer if ( free_path_ext ) self . allocator . free ( PATHEXT ) ;
2017-10-16 02:27:51 -04:00
2020-01-10 19:25:26 -05:00
const app_name = self . argv [ 0 ] ;
2021-08-06 02:01:47 -07:00
var it = mem . tokenize ( u8 , PATH , " ; " ) ;
2019-06-18 01:40:37 -06:00
retry : while ( it . next ( ) ) | search_path | {
2020-01-10 15:03:51 +11:00
const path_no_ext = try fs . path . join ( self . allocator , & [ _ ] [ ] const u8 { search_path , app_name } ) ;
defer self . allocator . free ( path_no_ext ) ;
2021-08-06 02:01:47 -07:00
var ext_it = mem . tokenize ( u8 , PATHEXT , " ; " ) ;
2019-06-18 01:40:37 -06:00
while ( ext_it . next ( ) ) | app_ext | {
2020-01-10 15:03:51 +11:00
const joined_path = try mem . concat ( self . allocator , u8 , & [ _ ] [ ] const u8 { path_no_ext , app_ext } ) ;
2019-06-18 01:40:37 -06:00
defer self . allocator . free ( joined_path ) ;
const joined_path_w = try unicode . utf8ToUtf16LeWithNull ( self . allocator , joined_path ) ;
defer self . allocator . free ( joined_path_w ) ;
if ( windowsCreateProcess ( joined_path_w . ptr , cmd_line_w . ptr , envp_ptr , cwd_w_ptr , & siStartInfo , & piProcInfo ) ) | _ | {
break : retry ;
} else | err | switch ( err ) {
2019-07-05 14:14:25 -04:00
error . FileNotFound = > continue ,
error . AccessDenied = > continue ,
else = > return err ,
2019-06-18 01:40:37 -06:00
}
2017-10-16 02:27:51 -04:00
}
2019-02-05 22:44:14 +01:00
} else {
2019-06-18 01:40:37 -06:00
return no_path_err ; // return the original error
2017-10-16 02:27:51 -04:00
}
} ;
2017-10-13 09:31:03 -04:00
2017-10-31 22:24:02 -04:00
if ( g_hChildStd_IN_Wr ) | h | {
2020-05-02 04:31:26 -04:00
self . stdin = File { . handle = h } ;
2017-10-31 04:47:55 -04:00
} else {
self . stdin = null ;
2017-10-13 09:31:03 -04:00
}
2017-10-31 22:24:02 -04:00
if ( g_hChildStd_OUT_Rd ) | h | {
2020-05-02 04:31:26 -04:00
self . stdout = File { . handle = h } ;
2017-10-31 04:47:55 -04:00
} else {
self . stdout = null ;
2017-10-13 09:31:03 -04:00
}
2017-10-31 22:24:02 -04:00
if ( g_hChildStd_ERR_Rd ) | h | {
2020-05-02 04:31:26 -04:00
self . stderr = File { . handle = h } ;
2017-10-31 04:47:55 -04:00
} else {
self . stderr = null ;
2017-10-13 09:31:03 -04:00
}
self . handle = piProcInfo . hProcess ;
2017-10-15 14:01:55 -04:00
self . thread_handle = piProcInfo . hThread ;
2017-10-13 09:31:03 -04:00
self . term = null ;
2018-05-10 00:29:49 -04:00
if ( self . stdin_behavior == StdIo . Pipe ) {
2018-06-09 23:42:14 -04:00
os . close ( g_hChildStd_IN_Rd .? ) ;
2018-05-10 00:29:49 -04:00
}
if ( self . stderr_behavior == StdIo . Pipe ) {
2018-06-09 23:42:14 -04:00
os . close ( g_hChildStd_ERR_Wr .? ) ;
2018-05-10 00:29:49 -04:00
}
if ( self . stdout_behavior == StdIo . Pipe ) {
2018-06-09 23:42:14 -04:00
os . close ( g_hChildStd_OUT_Wr .? ) ;
2018-05-10 00:29:49 -04:00
}
2017-10-13 09:31:03 -04:00
}
2018-01-31 22:48:40 -05:00
fn setUpChildIo ( stdio : StdIo , pipe_fd : i32 , std_fileno : i32 , dev_null_fd : i32 ) ! void {
2017-04-04 00:17:24 -04:00
switch ( stdio ) {
2020-02-06 17:56:40 -05:00
. Pipe = > try os . dup2 ( pipe_fd , std_fileno ) ,
. Close = > os . close ( std_fileno ) ,
. Inherit = > { } ,
. Ignore = > try os . dup2 ( dev_null_fd , std_fileno ) ,
2017-04-04 00:17:24 -04:00
}
}
} ;
2019-12-28 16:40:59 +11:00
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 {
2018-09-02 23:25:04 -04:00
// TODO the docs for environment pointer say:
// > A pointer to the environment block for the new process. If this parameter
// > is NULL, the new process uses the environment of the calling process.
// > ...
// > An environment block can contain either Unicode or ANSI characters. If
// > the environment block pointed to by lpEnvironment contains Unicode
// > characters, be sure that dwCreationFlags includes CREATE_UNICODE_ENVIRONMENT.
// > If this parameter is NULL and the environment block of the parent process
// > contains Unicode characters, you must also ensure that dwCreationFlags
// > includes CREATE_UNICODE_ENVIRONMENT.
// This seems to imply that we have to somehow know whether our process parent passed
// CREATE_UNICODE_ENVIRONMENT if we want to pass NULL for the environment parameter.
// Since we do not know this information that would imply that we must not pass NULL
// for the parameter.
// However this would imply that programs compiled with -DUNICODE could not pass
// environment variables to programs that were not, which seems unlikely.
// More investigation is needed.
2019-05-26 13:37:34 -04:00
return windows . CreateProcessW (
2018-09-12 14:26:21 -04:00
app_name ,
cmd_line ,
null ,
null ,
windows . TRUE ,
windows . CREATE_UNICODE_ENVIRONMENT ,
2021-12-19 06:24:45 +01:00
@ptrCast ( ? * anyopaque , envp_ptr ) ,
2018-09-12 14:26:21 -04:00
cwd_ptr ,
lpStartupInfo ,
lpProcessInformation ,
2019-05-26 13:37:34 -04:00
) ;
2017-10-16 02:27:51 -04:00
}
2017-10-13 09:31:03 -04:00
/// Caller must dealloc.
2021-10-29 00:37:25 +01:00
fn windowsCreateCommandLine ( allocator : mem . Allocator , argv : [ ] const [ ] const u8 ) ! [ : 0 ] u8 {
2020-12-23 18:32:39 +02:00
var buf = std . ArrayList ( u8 ) . init ( allocator ) ;
2017-10-13 09:31:03 -04:00
defer buf . deinit ( ) ;
2018-05-11 23:04:41 -04:00
2017-10-13 09:31:03 -04:00
for ( argv ) | arg , arg_i | {
2020-12-23 16:24:22 +02:00
if ( arg_i != 0 ) try buf . append ( ' ' ) ;
2017-10-13 09:31:03 -04:00
if ( mem . indexOfAny ( u8 , arg , " \t \n \" " ) == null ) {
2020-12-23 16:24:22 +02:00
try buf . appendSlice ( arg ) ;
2017-10-13 09:31:03 -04:00
continue ;
}
2020-12-23 16:24:22 +02:00
try buf . append ( '"' ) ;
2017-10-13 09:31:03 -04:00
var backslash_count : usize = 0 ;
for ( arg ) | byte | {
switch ( byte ) {
'\\' = > backslash_count += 1 ,
'"' = > {
2020-12-23 16:24:22 +02:00
try buf . appendNTimes ( '\\' , backslash_count * 2 + 1 ) ;
try buf . append ( '"' ) ;
2017-10-13 09:31:03 -04:00
backslash_count = 0 ;
} ,
else = > {
2020-12-23 16:24:22 +02:00
try buf . appendNTimes ( '\\' , backslash_count ) ;
try buf . append ( byte ) ;
2017-10-13 09:31:03 -04:00
backslash_count = 0 ;
} ,
}
}
2020-12-23 16:24:22 +02:00
try buf . appendNTimes ( '\\' , backslash_count * 2 ) ;
try buf . append ( '"' ) ;
2017-10-13 09:31:03 -04:00
}
2020-12-23 16:24:22 +02:00
return buf . toOwnedSliceSentinel ( 0 ) ;
2017-10-13 09:31:03 -04:00
}
2018-01-25 04:10:11 -05:00
fn windowsDestroyPipe ( rd : ? windows . HANDLE , wr : ? windows . HANDLE ) void {
2017-10-31 22:24:02 -04:00
if ( rd ) | h | os . close ( h ) ;
if ( wr ) | h | os . close ( h ) ;
2017-10-13 09:31:03 -04:00
}
2021-06-17 17:36:42 -06:00
fn windowsMakePipeIn ( rd : * ? windows . HANDLE , wr : * ? windows . HANDLE , sattr : * const windows . SECURITY_ATTRIBUTES ) ! void {
var rd_h : windows . HANDLE = undefined ;
var wr_h : windows . HANDLE = undefined ;
try windows . CreatePipe ( & rd_h , & wr_h , sattr ) ;
errdefer windowsDestroyPipe ( rd_h , wr_h ) ;
try windows . SetHandleInformation ( wr_h , windows . HANDLE_FLAG_INHERIT , 0 ) ;
rd .* = rd_h ;
wr .* = wr_h ;
}
2020-10-20 08:51:21 +02:00
2021-06-17 17:36:42 -06:00
var pipe_name_counter = std . atomic . Atomic ( u32 ) . init ( 1 ) ;
fn windowsMakeAsyncPipe ( rd : * ? windows . HANDLE , wr : * ? windows . HANDLE , sattr : * const windows . SECURITY_ATTRIBUTES ) ! void {
var tmp_bufw : [ 128 ] u16 = undefined ;
// We must make a named pipe on windows because anonymous pipes do not support async IO
const pipe_path = blk : {
var tmp_buf : [ 128 ] u8 = undefined ;
// Forge a random path for the pipe.
const pipe_path = std . fmt . bufPrintZ (
& tmp_buf ,
" \\ \\ . \\ pipe \\ zig-childprocess-{d}-{d} " ,
. { windows . kernel32 . GetCurrentProcessId ( ) , pipe_name_counter . fetchAdd ( 1 , . Monotonic ) } ,
) catch unreachable ;
const len = std . unicode . utf8ToUtf16Le ( & tmp_bufw , pipe_path ) catch unreachable ;
tmp_bufw [ len ] = 0 ;
break : blk tmp_bufw [ 0 .. len : 0 ] ;
} ;
2020-10-20 08:51:21 +02:00
// Create the read handle that can be used with overlapped IO ops.
2021-06-17 17:36:42 -06:00
const read_handle = windows . kernel32 . CreateNamedPipeW (
pipe_path . ptr ,
2020-10-20 08:51:21 +02:00
windows . PIPE_ACCESS_INBOUND | windows . FILE_FLAG_OVERLAPPED ,
windows . PIPE_TYPE_BYTE ,
1 ,
2021-06-17 17:36:42 -06:00
4096 ,
4096 ,
2020-10-20 08:51:21 +02:00
0 ,
sattr ,
) ;
if ( read_handle == windows . INVALID_HANDLE_VALUE ) {
switch ( windows . kernel32 . GetLastError ( ) ) {
else = > | err | return windows . unexpectedError ( err ) ,
}
}
2021-06-17 17:36:42 -06:00
errdefer os . close ( read_handle ) ;
2020-10-20 08:51:21 +02:00
2021-06-17 17:36:42 -06:00
var sattr_copy = sattr .* ;
const write_handle = windows . kernel32 . CreateFileW (
pipe_path . ptr ,
2020-10-20 08:51:21 +02:00
windows . GENERIC_WRITE ,
0 ,
2021-06-17 17:36:42 -06:00
& sattr_copy ,
2020-10-20 08:51:21 +02:00
windows . OPEN_EXISTING ,
windows . FILE_ATTRIBUTE_NORMAL ,
null ,
) ;
if ( write_handle == windows . INVALID_HANDLE_VALUE ) {
switch ( windows . kernel32 . GetLastError ( ) ) {
else = > | err | return windows . unexpectedError ( err ) ,
}
}
2021-06-17 17:36:42 -06:00
errdefer os . close ( write_handle ) ;
2020-10-20 08:51:21 +02:00
try windows . SetHandleInformation ( read_handle , windows . HANDLE_FLAG_INHERIT , 0 ) ;
rd .* = read_handle ;
wr .* = write_handle ;
}
2019-05-27 14:12:50 -04:00
fn destroyPipe ( pipe : [ 2 ] os . fd_t ) void {
2018-10-15 18:23:47 -04:00
os . close ( pipe [ 0 ] ) ;
2019-12-16 10:56:53 +01:00
if ( pipe [ 0 ] != pipe [ 1 ] ) os . close ( pipe [ 1 ] ) ;
2017-04-04 00:17:24 -04:00
}
// Child of fork calls this to report an error to the fork parent.
// Then the child exits.
2018-02-08 02:08:45 -05:00
fn forkChildErrReport ( fd : i32 , err : ChildProcess . SpawnError ) noreturn {
2019-11-10 14:58:24 -05:00
writeIntFd ( fd , @as ( ErrInt , @errorToInt ( err ) ) ) catch { } ;
2020-09-28 22:17:50 -07:00
// If we're linking libc, some naughty applications may have registered atexit handlers
// which we really do not want to run in the fork child. I caught LLVM doing this and
// it caused a deadlock instead of doing an exit syscall. In the words of Avril Lavigne,
// "Why'd you have to go and make things so complicated?"
2020-12-26 22:22:43 +01:00
if ( builtin . link_libc ) {
// The _exit(2) function does nothing but make the exit syscall, unlike exit(3)
std . c . _exit ( 1 ) ;
2020-09-28 22:17:50 -07:00
}
2019-05-25 13:07:44 -04:00
os . exit ( 1 ) ;
2017-04-04 00:17:24 -04:00
}
2020-10-17 14:09:59 +02:00
const ErrInt = std . meta . Int ( . unsigned , @sizeOf ( anyerror ) * 8 ) ;
2017-08-27 00:11:09 -04:00
2018-01-31 22:48:40 -05:00
fn writeIntFd ( fd : i32 , value : ErrInt ) ! void {
2020-02-06 17:56:40 -05:00
const file = File {
. handle = fd ,
2020-05-01 23:17:15 -04:00
. capable_io_mode = . blocking ,
. intended_io_mode = . blocking ,
2020-02-06 17:56:40 -05:00
} ;
2021-01-05 20:57:18 -05:00
file . writer ( ) . writeIntNative ( u64 , @intCast ( u64 , value ) ) catch return error . SystemResources ;
2017-04-04 00:17:24 -04:00
}
2018-01-31 22:48:40 -05:00
fn readIntFd ( fd : i32 ) ! ErrInt {
2020-02-06 17:56:40 -05:00
const file = File {
. handle = fd ,
2020-05-01 23:17:15 -04:00
. capable_io_mode = . blocking ,
. intended_io_mode = . blocking ,
2020-02-06 17:56:40 -05:00
} ;
2020-10-15 23:19:19 +00:00
return @intCast ( ErrInt , file . reader ( ) . readIntNative ( u64 ) catch return error . SystemResources ) ;
2017-04-04 00:17:24 -04:00
}
2019-05-24 00:13:13 -04:00
/// Caller must free result.
2021-10-29 00:37:25 +01:00
pub fn createWindowsEnvBlock ( allocator : mem . Allocator , env_map : * const BufMap ) ! [ ] u16 {
2019-05-24 00:13:13 -04:00
// count bytes needed
const max_chars_needed = x : {
var max_chars_needed : usize = 4 ; // 4 for the final 4 null bytes
var it = env_map . iterator ( ) ;
while ( it . next ( ) ) | pair | {
// +1 for '='
// +1 for null byte
2021-06-03 15:39:26 -05:00
max_chars_needed += pair . key_ptr . len + pair . value_ptr . len + 2 ;
2019-05-24 00:13:13 -04:00
}
break : x max_chars_needed ;
} ;
const result = try allocator . alloc ( u16 , max_chars_needed ) ;
errdefer allocator . free ( result ) ;
var it = env_map . iterator ( ) ;
var i : usize = 0 ;
while ( it . next ( ) ) | pair | {
2021-06-03 15:39:26 -05:00
i += try unicode . utf8ToUtf16Le ( result [ i .. ] , pair . key_ptr .* ) ;
2019-05-24 00:13:13 -04:00
result [ i ] = '=' ;
i += 1 ;
2021-06-03 15:39:26 -05:00
i += try unicode . utf8ToUtf16Le ( result [ i .. ] , pair . value_ptr .* ) ;
2019-05-24 00:13:13 -04:00
result [ i ] = 0 ;
i += 1 ;
}
result [ i ] = 0 ;
i += 1 ;
result [ i ] = 0 ;
i += 1 ;
result [ i ] = 0 ;
i += 1 ;
result [ i ] = 0 ;
i += 1 ;
return allocator . shrink ( result , i ) ;
}
2020-12-26 13:50:26 -07:00
2021-10-29 00:37:25 +01:00
pub fn createNullDelimitedEnvMap ( arena : mem . Allocator , env_map : * const std . BufMap ) ! [ : null ] ? [ * : 0 ] u8 {
2020-12-26 13:50:26 -07:00
const envp_count = env_map . count ( ) ;
2020-12-27 13:00:35 +01:00
const envp_buf = try arena . allocSentinel ( ? [ * : 0 ] u8 , envp_count , null ) ;
2020-12-26 13:50:26 -07:00
{
var it = env_map . iterator ( ) ;
var i : usize = 0 ;
while ( it . next ( ) ) | pair | : ( i += 1 ) {
2021-06-03 15:39:26 -05:00
const env_buf = try arena . allocSentinel ( u8 , pair . key_ptr . len + pair . value_ptr . len + 1 , 0 ) ;
mem . copy ( u8 , env_buf , pair . key_ptr .* ) ;
env_buf [ pair . key_ptr . len ] = '=' ;
mem . copy ( u8 , env_buf [ pair . key_ptr . len + 1 .. ] , pair . value_ptr .* ) ;
2020-12-27 13:00:35 +01:00
envp_buf [ i ] = env_buf . ptr ;
2020-12-26 13:50:26 -07:00
}
assert ( i == envp_count ) ;
}
2020-12-27 13:00:35 +01:00
return envp_buf ;
2020-12-26 13:50:26 -07:00
}
2020-12-27 12:41:48 +01:00
test " createNullDelimitedEnvMap " {
const testing = std . testing ;
const allocator = testing . allocator ;
var envmap = BufMap . init ( allocator ) ;
defer envmap . deinit ( ) ;
2021-06-03 15:39:26 -05:00
try envmap . put ( " HOME " , " /home/ifreund " ) ;
try envmap . put ( " WAYLAND_DISPLAY " , " wayland-1 " ) ;
try envmap . put ( " DISPLAY " , " :1 " ) ;
try envmap . put ( " DEBUGINFOD_URLS " , " " ) ;
try envmap . put ( " XCURSOR_SIZE " , " 24 " ) ;
2020-12-27 12:41:48 +01:00
var arena = std . heap . ArenaAllocator . init ( allocator ) ;
defer arena . deinit ( ) ;
2021-10-29 02:08:41 +01:00
const environ = try createNullDelimitedEnvMap ( arena . allocator ( ) , & envmap ) ;
2020-12-27 12:41:48 +01:00
2021-05-04 20:47:26 +03:00
try testing . expectEqual ( @as ( usize , 5 ) , environ . len ) ;
2020-12-27 12:41:48 +01:00
inline for ( . {
" HOME=/home/ifreund " ,
" WAYLAND_DISPLAY=wayland-1 " ,
" DISPLAY=:1 " ,
" DEBUGINFOD_URLS= " ,
" XCURSOR_SIZE=24 " ,
} ) | target | {
for ( environ ) | variable | {
if ( mem . eql ( u8 , mem . span ( variable orelse continue ) , target ) ) break ;
} else {
2021-05-04 20:47:26 +03:00
try testing . expect ( false ) ; // Environment variable not found
2020-12-27 12:41:48 +01:00
}
}
2020-12-26 13:50:26 -07:00
}