@@ -68,12 +68,16 @@ public func posix_execute(cfg : *ProcessConfig, out : *mut ProcessResult) : bool
6868 close (stdin_pipe_tmp[0 ])
6969 } else {}
7070 var argv = build_argv (&raw mut cfg.args);
71- // If env vars are provided, use execve() with custom environment.
71+ // Resolve the program via PATH (execve doesn't search PATH, unlike
72+ // execvp). When env vars are provided we must use execve to replace
73+ // the environment entirely; otherwise execvp is fine.
74+ unsafe var resolved : [4096 ]char;
75+ var prog = lookup_program (argv.ptrs[0 ], &raw mut resolved[0 ], 4096 );
7276 if (cfg.env.size () > 0 ) {
7377 var envp = build_envp (&raw mut cfg.env);
74- execve (argv.ptrs[ 0 ] , &raw argv.ptrs[0 ], &raw envp.ptrs[0 ]);
78+ execve (prog , &raw argv.ptrs[0 ], &raw envp.ptrs[0 ]);
7579 } else {
76- execvp (argv.ptrs[ 0 ] , &raw argv.ptrs[0 ]);
80+ execvp (prog , &raw argv.ptrs[0 ]);
7781 }
7882 _exit (1 );
7983 } else {}
@@ -85,55 +89,44 @@ public func posix_execute(cfg : *ProcessConfig, out : *mut ProcessResult) : bool
8589 var stdout_data = vector< u8> ();
8690 var stderr_data = vector< u8> ();
8791
88- if (cfg.capture_stdout) {
89- if (! read_all_fd (stdout_pipe[0 ], &raw mut stdout_data)) {
90- close (stdout_pipe[0 ]);
91- if (cfg.capture_stderr && ! cfg.merge_stdout_stderr) { close (stderr_pipe[0 ]); } else {}
92- return false
93- } else {}
94- close (stdout_pipe[0 ]);
95- } else {}
96- // When merge_stdout_stderr is true, stderr was redirected to stdout_pipe
97- // (or stderr_pipe), so we only need one read.
98- if (cfg.capture_stderr && ! cfg.merge_stdout_stderr) {
99- if (! read_all_fd (stderr_pipe[0 ], &raw mut stderr_data)) {
100- close (stderr_pipe[0 ]);
101- return false
102- } else {}
103- close (stderr_pipe[0 ]);
104- } else if (cfg.merge_stdout_stderr && ! cfg.capture_stdout) {
105- // merge without capture_stdout: read from stderr_pipe[0] as merged
106- if (! read_all_fd (stderr_pipe[0 ], &raw mut stdout_data)) {
107- close (stderr_pipe[0 ]);
108- return false
109- } else {}
110- close (stderr_pipe[0 ]);
111- } else {}
92+ // Read from the pipes in a non-blocking fashion so we can also poll the
93+ // child and enforce the timeout (otherwise a long-running child would
94+ // block the read forever and the timeout could never fire).
95+ if (cfg.capture_stdout) { set_nonblock (stdout_pipe[0 ]); } else {}
96+ if (cfg.capture_stderr && ! cfg.merge_stdout_stderr) { set_nonblock (stderr_pipe[0 ]); } else {}
97+ if (cfg.merge_stdout_stderr && ! cfg.capture_stdout) { set_nonblock (stderr_pipe[0 ]); } else {}
11298
11399 var status : int = 0 ;
114100 var timed_out = false
115- if (cfg.timeout_ms > 0 ) {
116- // Poll with timeout: sleep in 10ms increments
117- var elapsed : int = 0
118- while (elapsed < cfg.timeout_ms) {
119- var ret = waitpid (pid, &raw mut status, 1 ) // WNOHANG
120- if (ret != 0 ) { break }
101+ var elapsed : int = 0 ;
102+ var done = false;
103+ while (! done) {
104+ if (cfg.capture_stdout) { read_available (stdout_pipe[0 ], &raw mut stdout_data); } else {}
105+ if (cfg.capture_stderr && ! cfg.merge_stdout_stderr) { read_available (stderr_pipe[0 ], &raw mut stderr_data); } else {}
106+ if (cfg.merge_stdout_stderr && ! cfg.capture_stdout) { read_available (stderr_pipe[0 ], &raw mut stdout_data); } else {}
107+
108+ var ret = waitpid (pid, &raw mut status, 1 ) // WNOHANG
109+ if (ret != 0 ) {
110+ done = true
111+ } else if (cfg.timeout_ms > 0 ) {
121112 usleep (10000 ) // 10ms
122113 elapsed += 10
123- }
124- if (elapsed >= cfg.timeout_ms) {
125- // Check one more time
126- var ret = waitpid (pid, &raw mut status, 1 )
127- if (ret == 0 ) {
114+ if (elapsed >= cfg.timeout_ms) {
128115 timed_out = true
129116 kill (pid, 9 ) // SIGKILL
130117 waitpid (pid, &raw mut status, 0 )
118+ done = true
131119 }
120+ } else {
121+ usleep (5000 )
132122 }
133- } else {
134- waitpid (pid, &raw mut status, 0 )
135123 }
136124
125+ // Final drain now that the child is dead (pipe yields remaining data then EOF).
126+ if (cfg.capture_stdout) { read_available (stdout_pipe[0 ], &raw mut stdout_data); close (stdout_pipe[0 ]); } else {}
127+ if (cfg.capture_stderr && ! cfg.merge_stdout_stderr) { read_available (stderr_pipe[0 ], &raw mut stderr_data); close (stderr_pipe[0 ]); } else {}
128+ if (cfg.merge_stdout_stderr && ! cfg.capture_stdout) { read_available (stderr_pipe[0 ], &raw mut stdout_data); close (stderr_pipe[0 ]); } else {}
129+
137130 var exit_code : int = 0 ;
138131 var signaled : bool = false;
139132 var signal_no : int = 0 ;
@@ -150,6 +143,34 @@ public func posix_execute(cfg : *ProcessConfig, out : *mut ProcessResult) : bool
150143 return true
151144}
152145
146+ // Make a file descriptor non-blocking so reads don't stall the timeout loop.
147+ func set_nonblock (fd : int) {
148+ var flags = fcntl (fd, F_GETFL, 0 );
149+ fcntl (fd, F_SETFL, flags | O_NONBLOCK);
150+ }
151+
152+ // Read whatever is currently available from a non-blocking fd, stopping on
153+ // EOF or when no more data is immediately readable (EAGAIN).
154+ func read_available (fd : int, data : *mut vector<u8>) : bool {
155+ unsafe var buf : [4096 ]u8;
156+ while (true) {
157+ var n = read (fd, &raw mut buf[0 ], 4096 );
158+ if (n > 0 ) {
159+ var i : size_t = 0 ;
160+ while (i < n as size_t) {
161+ data.push (buf[i]);
162+ i += 1 ;
163+ }
164+ } else if (n == 0 ) {
165+ return true
166+ } else {
167+ if (*__errno_location () == EAGAIN) { return true }
168+ return false
169+ }
170+ }
171+ return false
172+ }
173+
153174public func posix_spawn (cfg : *ProcessConfig, child : *mut ChildProcess) : bool {
154175 unsafe var stdout_pipe : [2 ]int;
155176 unsafe var stderr_pipe : [2 ]int;
@@ -158,7 +179,7 @@ public func posix_spawn(cfg : *ProcessConfig, child : *mut ChildProcess) : bool
158179 if (cfg.capture_stdout) {
159180 if (pipe (&raw mut stdout_pipe[0 ]) != 0 ) { return false } else {}
160181 } else {}
161- if (cfg.capture_stderr) {
182+ if (cfg.capture_stderr || cfg.merge_stdout_stderr ) {
162183 if (pipe (&raw mut stderr_pipe[0 ]) != 0 ) {
163184 if (cfg.capture_stdout) { close (stdout_pipe[0 ]); close (stdout_pipe[1 ]); } else {}
164185 return false
@@ -184,8 +205,29 @@ public func posix_spawn(cfg : *ProcessConfig, child : *mut ChildProcess) : bool
184205 if (cfg.working_dir.size () > 0 ) {
185206 chdir (cfg.working_dir.data ())
186207 } else {}
187- if (cfg.capture_stdout) { close (stdout_pipe[0 ]); dup2 (stdout_pipe[1 ], 1 ); close (stdout_pipe[1 ]); } else {}
188- if (cfg.capture_stderr) { close (stderr_pipe[0 ]); dup2 (stderr_pipe[1 ], 2 ); close (stderr_pipe[1 ]); } else {}
208+ if (cfg.capture_stdout) {
209+ close (stdout_pipe[0 ])
210+ if (cfg.merge_stdout_stderr) {
211+ // Both stdout and stderr go to stdout_pipe[1]
212+ dup2 (stdout_pipe[1 ], 1 )
213+ dup2 (stdout_pipe[1 ], 2 )
214+ close (stdout_pipe[1 ])
215+ } else {
216+ dup2 (stdout_pipe[1 ], 1 )
217+ close (stdout_pipe[1 ])
218+ }
219+ } else {}
220+ if (cfg.capture_stderr && ! cfg.merge_stdout_stderr) {
221+ close (stderr_pipe[0 ])
222+ dup2 (stderr_pipe[1 ], 2 )
223+ close (stderr_pipe[1 ])
224+ } else if (cfg.merge_stdout_stderr && ! cfg.capture_stdout) {
225+ // merge without capture_stdout: redirect both to stderr_pipe[1]
226+ close (stderr_pipe[0 ])
227+ dup2 (stderr_pipe[1 ], 1 )
228+ dup2 (stderr_pipe[1 ], 2 )
229+ close (stderr_pipe[1 ])
230+ } else {}
189231 // Child reads from stdin_pipe[0]; parent writes to stdin_pipe[1].
190232 close (stdin_pipe[1 ]); dup2 (stdin_pipe[0 ], 0 ); close (stdin_pipe[0 ]);
191233 var argv = build_argv (&raw mut cfg.args);
@@ -199,12 +241,12 @@ public func posix_spawn(cfg : *ProcessConfig, child : *mut ChildProcess) : bool
199241 } else {}
200242
201243 if (cfg.capture_stdout) { close (stdout_pipe[1 ]); } else {}
202- if (cfg.capture_stderr) { close (stderr_pipe[1 ]); } else {}
244+ if (cfg.capture_stderr || cfg.merge_stdout_stderr ) { close (stderr_pipe[1 ]); } else {}
203245 close (stdin_pipe[0 ]); // parent writes to stdin_pipe[1]
204246
205247 child._unix .pid = pid;
206248 child._unix .stdout_fd = if (cfg.capture_stdout) stdout_pipe[0 ] else -1 ;
207- child._unix .stderr_fd = if (cfg.capture_stderr) stderr_pipe[0 ] else -1 ;
249+ child._unix .stderr_fd = if (cfg.capture_stderr) stderr_pipe[0 ] else if (cfg.merge_stdout_stderr && ! cfg.capture_stdout) stderr_pipe[ 0 ] else -1 ;
208250 child._unix .stdin_fd = stdin_pipe[1 ];
209251 child.is_running = true;
210252 return true
@@ -247,6 +289,7 @@ public func posix_wait(child : *mut ChildProcess, out : *mut ProcessResult) : bo
247289 else if (WIFSIGNALED (status)) { signaled = true; signal_no = WTERMSIG (status); exit_code = -1 ; } else {}
248290
249291 child.is_running = false;
292+ child._unix .pid = 0 ;
250293 out.output.stdout_data = stdout_data;
251294 out.output.stderr_data = stderr_data;
252295 out.status.code = exit_code;
@@ -317,6 +360,57 @@ func read_all_fd(fd : int, data : *mut vector<u8>) : bool {
317360@ extern public func usleep (usec : int) : int
318361@ extern public func chdir (path : *char) : int
319362@ extern public func kill (pid : int, sig : int) : int
363+ @ extern public func fcntl (fd : int, cmd : int, arg : int) : int
364+ @ extern public func __errno_location () : *mut int
365+ @ extern public func access (path : *char, mode : int) : int
366+
367+ const F_GETFL = 3
368+ const F_SETFL = 4
369+ const O_NONBLOCK = 2048
370+ const EAGAIN = 11
371+ const X_OK = 1
372+
373+ // Resolve an executable name to a path usable by execve, searching PATH when
374+ // the name doesn't already contain a '/'. `out` is filled with the result and
375+ // its pointer is returned (must remain valid until exec).
376+ func lookup_program (prog : *char, out : *mut char, out_size : size_t) : *mut char {
377+ var i : size_t = 0 ;
378+ while (prog[i] != 0 ) {
379+ if (prog[i] == '/' ) {
380+ var k : size_t = 0 ;
381+ while (prog[k] != 0 && k < out_size) { out[k] = prog[k]; k += 1 }
382+ if(k < out_size) { out[k] = 0 }
383+ return out
384+ }
385+ i += 1;
386+ }
387+ var path = getenv("PATH");
388+ if(path == null) {
389+ var k : size_t = 0;
390+ while(prog[k] != 0 && k < out_size) { out[k] = prog[k]; k += 1 }
391+ if(k < out_size) { out[k] = 0 }
392+ return out
393+ }
394+ var seg_start : size_t = 0;
395+ while(true) {
396+ var seg_end : size_t = seg_start;
397+ while(path[seg_end] != 0 && path[seg_end] != ':') { seg_end += 1 }
398+ var len : size_t = 0;
399+ var k = seg_start;
400+ while(k < seg_end && len + 1 < out_size) { out[len] = path[k]; len += 1; k += 1 }
401+ if(len < out_size) { out[len] = '/'; len += 1 }
402+ var pi : size_t = 0;
403+ while(prog[pi] != 0 && len < out_size) { out[len] = prog[pi]; len += 1; pi += 1 }
404+ if(len < out_size) { out[len] = 0 }
405+ if(access(out, X_OK) == 0) { return out }
406+ if(path[seg_end] == 0) { break }
407+ seg_start = seg_end + 1;
408+ }
409+ var k : size_t = 0;
410+ while(prog[k] != 0 && k < out_size) { out[k] = prog[k]; k += 1 }
411+ if(k < out_size) { out[k] = 0 }
412+ return out
413+ }
320414
321415const _WIFEXITED_MASK = 0x7f;
322416func WIFEXITED(status : int) : bool { return (status & _WIFEXITED_MASK) == 0; }
0 commit comments