11use std:: fs:: File ;
2+ use std:: io:: { ErrorKind , Read , Write } ;
23use std:: os:: fd:: FromRawFd ;
34use std:: os:: unix:: process:: CommandExt ;
45use std:: process:: Command ;
@@ -20,19 +21,65 @@ fn main() {
2021 if pid == 0 {
2122 // We are the child. Spawn the subprocess based off our arguments.
2223 let mut args = std:: env:: args_os ( ) . skip ( 1 ) ;
23- Command :: new ( args. next ( ) . unwrap ( ) ) . args ( args) . exec ( ) ;
24- unreachable ! ( "exec should not return" ) ;
24+ let program = args. next ( ) . unwrap ( ) ;
25+ // exec only returns if it failed, in which case it hands back the reason why.
26+ let err = Command :: new ( & program) . args ( args) . exec ( ) ;
27+ eprintln ! ( "inapty: failed to exec {}: {}" , program. display( ) , err) ;
28+ // Same codes a shell reports for a command it could not run.
29+ std:: process:: exit ( if err. kind ( ) == ErrorKind :: NotFound {
30+ 127
31+ } else {
32+ 126
33+ } ) ;
2534 } else {
2635 // We are the originating process. Copy from the pty to output.
2736 // SAFETY: master is open and valid, it was just opened by forkpty
2837 let mut pty = unsafe { File :: from_raw_fd ( pty) } ;
29- // Copy all the output from the child pty to our stdout
30- while std:: io:: copy ( & mut pty, & mut std:: io:: stdout ( ) ) . is_ok ( ) { }
38+
39+ let mut buf = [ 0u8 ; 8192 ] ;
40+ let mut stdout = std:: io:: stdout ( ) . lock ( ) ;
41+ // Once our own output is gone there is nothing useful left to do with the child's bytes,
42+ // but we must keep draining the pty anyway. If we stop reading, the child blocks forever
43+ // writing into a full pty buffer and the waitpid below never returns.
44+ let mut stdout_broken = false ;
45+ loop {
46+ let n = match pty. read ( & mut buf) {
47+ // The child closed the pty. On Linux this is reported as EIO below instead, but
48+ // handle a real EOF too so that we cannot spin here.
49+ Ok ( 0 ) => break ,
50+ Ok ( n) => n,
51+ Err ( e) if e. kind ( ) == ErrorKind :: Interrupted => continue ,
52+ // Any other error means the pty is done; the child has exited or is about to.
53+ Err ( _) => break ,
54+ } ;
55+ if !stdout_broken && stdout. write_all ( & buf[ ..n] ) . is_err ( ) {
56+ stdout_broken = true ;
57+ }
58+ }
59+ let _ = stdout. flush ( ) ;
60+
3161 // Exit according to our child's status
62+ let mut status = 0 ;
3263 // SAFETY: No preconditions
33- let status = unsafe {
34- let mut status = 0 ;
35- libc:: waitpid ( pid, & mut status, 0 ) ;
64+ while unsafe { libc:: waitpid ( pid, & mut status, 0 ) } == -1 {
65+ let err = std:: io:: Error :: last_os_error ( ) ;
66+ // A signal can interrupt the wait before the child is reaped; retrying is the only
67+ // way to still learn its status.
68+ if err. kind ( ) == ErrorKind :: Interrupted {
69+ continue ;
70+ }
71+ // Anything else means we will never learn it. `status` is still untouched here, so
72+ // using it would report a clean exit for a child we know nothing about.
73+ eprintln ! ( "inapty: waitpid failed: {err}" ) ;
74+ std:: process:: exit ( 125 ) ;
75+ }
76+
77+ let status = if libc:: WIFSIGNALED ( status) {
78+ // WEXITSTATUS is only meaningful for a child that exited normally; for one killed
79+ // by a signal it reports 0, which would make a segfault or an OOM kill look like
80+ // a clean run. Report it the way a shell does instead.
81+ 128 + libc:: WTERMSIG ( status)
82+ } else {
3683 libc:: WEXITSTATUS ( status)
3784 } ;
3885 std:: process:: exit ( status) ;
0 commit comments