@@ -23,6 +23,26 @@ public struct ProcessResult {
2323 var output : ProcessOutput;
2424 var status : ExitStatus;
2525 var success : bool;
26+
27+ /// Get stdout as a string view (assumes UTF-8).
28+ public func stdout_str (&self ) : string_view {
29+ return string_view (self .output.stdout_data.data () as *char, self .output.stdout_data.size ())
30+ }
31+
32+ /// Get stderr as a string view (assumes UTF-8).
33+ public func stderr_str (&self ) : string_view {
34+ return string_view (self .output.stderr_data.data () as *char, self .output.stderr_data.size ())
35+ }
36+
37+ /// Get the exit code.
38+ public func exit_code (&self ) : int {
39+ return self .status.code
40+ }
41+
42+ /// Check whether the process exited successfully.
43+ public func is_success (&self ) : bool {
44+ return self .success
45+ }
2646}
2747
2848public struct ProcessConfig {
@@ -165,7 +185,7 @@ public func kill(child : *mut ChildProcess, signal : int) : UT_Result {
165185 var r = TerminateProcess (child.win.h_process, 1u32 );
166186 if (r == 0 ) {
167187 var e = ProcessError.OperationFailed (string ("TerminateProcess failed" ))
168- std:: replace (&mut ret, Result .Err< UnitTy, ProcessError> (std:: replace (&mut e, ProcessError.NotRunning ())))
188+ std:: replace (&mut ret, Result .Err< UnitTy, ProcessError> (std:: replace< ProcessError > (&mut e, ProcessError.NotRunning ())))
169189 return std:: replace< UT_Result> (&mut ret, zeroed :unsafe < UT_Result> ())
170190 } else {}
171191 child.is_running = false;
@@ -178,4 +198,147 @@ public func kill(child : *mut ChildProcess, signal : int) : UT_Result {
178198 }
179199}
180200
201+ // ---------------------------------------------------------------------------
202+ // Process status & I/O
203+ // ---------------------------------------------------------------------------
204+
205+ /// Non-blocking check whether a child process is still running.
206+ public func is_running (child : *mut ChildProcess) : bool {
207+ if (! child.is_running) {
208+ return false
209+ }
210+ comptime if (def.windows) {
211+ // WaitForSingleObject with 0 timeout = non-blocking poll.
212+ var rc = WaitForSingleObject (child.win.h_process, 0u32 )
213+ if (rc == 0u32 ) {
214+ // Process has exited.
215+ child.is_running = false
216+ return false
217+ }
218+ return true
219+ } else {
220+ var status : int = 0
221+ var ret = waitpid (child._unix .pid, &raw mut status, 1 ) // WNOHANG = 1
222+ if (ret == 0 ) {
223+ return true // still running
224+ }
225+ child.is_running = false
226+ return false
227+ }
228+ }
229+
230+ /// Write data to the child process's stdin pipe.
231+ /// Returns Err if the child has no stdin pipe or the write fails.
232+ public func write_stdin (child : *mut ChildProcess, data : *vector<u8>) : UT_Result {
233+ var ret = zeroed :unsafe < UT_Result> ()
234+ comptime if (def.windows) {
235+ var e = ProcessError.OperationFailed (string ("write_stdin not implemented on Windows" ))
236+ std:: replace (&mut ret, Result .Err< UnitTy, ProcessError> (std:: replace< ProcessError> (&mut e, ProcessError.NotRunning ())))
237+ return std:: replace< UT_Result> (&mut ret, zeroed :unsafe < UT_Result> ())
238+ } else {
239+ if (child._unix .stdin_fd < 0 ) {
240+ var e = ProcessError.InvalidArgs (string ("no stdin pipe" ))
241+ std:: replace (&mut ret, Result .Err< UnitTy, ProcessError> (std:: replace< ProcessError> (&mut e, ProcessError.NotRunning ())))
242+ return std:: replace< UT_Result> (&mut ret, zeroed :unsafe < UT_Result> ())
243+ }
244+ if (data.size () > 0 ) {
245+ var written = write (child._unix .stdin_fd, data.data () as *void, data.size ())
246+ if (written < 0 ) {
247+ var e = ProcessError.IoError (string ("write to stdin failed" ))
248+ std:: replace (&mut ret, Result .Err< UnitTy, ProcessError> (std:: replace< ProcessError> (&mut e, ProcessError.NotRunning ())))
249+ return std:: replace< UT_Result> (&mut ret, zeroed :unsafe < UT_Result> ())
250+ }
251+ }
252+ std:: replace (&mut ret, Result .Ok< UnitTy, ProcessError> (UnitTy{}))
253+ return std:: replace< UT_Result> (&mut ret, zeroed :unsafe < UT_Result> ())
254+ }
255+ }
256+
257+ /// Close the stdin pipe of a child process (sends EOF to the child).
258+ public func close_stdin (child : *mut ChildProcess) : UT_Result {
259+ var ret = zeroed :unsafe < UT_Result> ()
260+ comptime if (def.windows) {
261+ var e = ProcessError.OperationFailed (string ("close_stdin not implemented on Windows" ))
262+ std:: replace (&mut ret, Result .Err< UnitTy, ProcessError> (std:: replace< ProcessError> (&mut e, ProcessError.NotRunning ())))
263+ return std:: replace< UT_Result> (&mut ret, zeroed :unsafe < UT_Result> ())
264+ } else {
265+ if (child._unix .stdin_fd >= 0 ) {
266+ close (child._unix .stdin_fd)
267+ child._unix .stdin_fd = -1
268+ }
269+ std:: replace (&mut ret, Result .Ok< UnitTy, ProcessError> (UnitTy{}))
270+ return std:: replace< UT_Result> (&mut ret, zeroed :unsafe < UT_Result> ())
271+ }
272+ }
273+
274+ /// Reap the child process and return its exit status without blocking.
275+ /// If the process is still running, returns NotRunning error.
276+ public func try_wait (child : *mut ChildProcess) : PR_Result {
277+ var ret = zeroed :unsafe < PR_Result> ()
278+ if (! child.is_running) {
279+ var e = ProcessError.NotRunning ()
280+ pr_err (e, &mut ret)
281+ return std:: replace< PR_Result> (&mut ret, zeroed :unsafe < PR_Result> ())
282+ }
283+ comptime if (def.windows) {
284+ var e = ProcessError.OperationFailed (string ("try_wait not implemented on Windows" ))
285+ pr_err (e, &mut ret)
286+ return std:: replace< PR_Result> (&mut ret, zeroed :unsafe < PR_Result> ())
287+ } else {
288+ var status : int = 0
289+ var pid = waitpid (child._unix .pid, &raw mut status, 1 ) // WNOHANG
290+ if (pid == 0 ) {
291+ var e = ProcessError.NotRunning ()
292+ pr_err (e, &mut ret)
293+ return std:: replace< PR_Result> (&mut ret, zeroed :unsafe < PR_Result> ())
294+ }
295+ var result = zeroed :unsafe < ProcessResult> ()
296+ var exit_code : int = 0
297+ var signaled : bool = false
298+ var signal_no : int = 0
299+ if (WIFEXITED (status)) { exit_code = WEXITSTATUS (status) }
300+ else if (WIFSIGNALED (status)) { signaled = true; signal_no = WTERMSIG (status); exit_code = -1 }
301+ child.is_running = false
302+ result.status.code = exit_code
303+ result.status.signaled = signaled
304+ result.status.signal = signal_no
305+ result.success = (exit_code == 0 && ! signaled)
306+ pr_ok (&mut result, &mut ret)
307+ return std:: replace< PR_Result> (&mut ret, zeroed :unsafe < PR_Result> ())
308+ }
309+ }
310+
311+ // ---------------------------------------------------------------------------
312+ // Process-wide utilities
313+ // ---------------------------------------------------------------------------
314+
315+ /// Get the current process ID.
316+ public func current_pid () : int {
317+ comptime if (def.windows) {
318+ return GetCurrentProcessId () as int
319+ } else {
320+ return getpid ()
321+ }
322+ }
323+
324+ /// Sleep for the given number of milliseconds.
325+ public func sleep_ms (ms : int) {
326+ comptime if (def.windows) {
327+ Sleep (ms as u32)
328+ } else {
329+ usleep (ms * 1000 )
330+ }
331+ }
332+
333+ /// Get the PID of a child process.
334+ public func child_pid (child : *mut ChildProcess) : int {
335+ comptime if (def.windows) {
336+ // Windows doesn't expose a PID from the HANDLE in a portable way;
337+ // return 0 as a sentinel.
338+ return 0
339+ } else {
340+ return child._unix .pid
341+ }
342+ }
343+
181344} // end namespace process
0 commit comments