@@ -34,8 +34,10 @@ use crate::utils::{
3434use crate :: wordexp:: { expand_word, expand_word_to_string, word_to_pattern} ;
3535use nix:: errno:: Errno ;
3636use nix:: libc;
37+ use nix:: sys:: signal:: kill;
38+ use nix:: sys:: signal:: Signal as NixSignal ;
3739use nix:: sys:: wait:: { WaitPidFlag , WaitStatus } ;
38- use nix:: unistd:: { getcwd, getpgrp, getpid, getppid, setpgid, ForkResult , Pid } ;
40+ use nix:: unistd:: { getcwd, getpgid , getpgrp, getpid, getppid, setpgid, tcsetpgrp , ForkResult , Pid } ;
3941use std:: collections:: HashMap ;
4042use std:: ffi:: { CString , OsString } ;
4143use std:: fmt:: { Display , Formatter } ;
@@ -217,15 +219,15 @@ impl Shell {
217219 return Ok ( signal_to_exit_status ( signal) ) ;
218220 }
219221 WaitStatus :: StillAlive => {
220- self . update_global_state ( ) ;
222+ self . handle_async_events ( ) ;
221223 std:: thread:: sleep ( Duration :: from_millis ( 16 ) ) ;
222224 }
223225 _ => unreachable ! ( ) ,
224226 }
225227 }
226228 }
227229
228- pub fn update_global_state ( & mut self ) {
230+ pub fn handle_async_events ( & mut self ) {
229231 self . process_signals ( ) ;
230232 if self . set_options . monitor {
231233 if let Err ( err) = self . background_jobs . update_jobs ( ) {
@@ -262,8 +264,8 @@ impl Shell {
262264 }
263265
264266 pub fn process_signals ( & mut self ) {
265- while let Some ( action) = self . signal_manager . get_pending_action ( ) {
266- self . execute_action ( action. clone ( ) )
267+ while let Some ( action) = self . signal_manager . get_pending_action ( ) . cloned ( ) {
268+ self . execute_action ( action)
267269 }
268270 }
269271
@@ -729,17 +731,22 @@ impl Shell {
729731 match fork ( ) ? {
730732 ForkResult :: Child => {
731733 self . become_subshell ( ) ;
732- setpgid ( Pid :: from_raw ( 0 ) , Pid :: from_raw ( 0 ) )
733- . expect ( "failed to create new process group for pipeline" ) ;
734+ // this should never fail as both arguments are valid
735+ setpgid ( Pid :: from_raw ( 0 ) , Pid :: from_raw ( 0 ) ) . unwrap ( ) ;
734736 let pipeline_pgid = getpgrp ( ) ;
737+ // wait for the parent process to put the subshell in the foreground
738+ if let Err ( err) = kill ( Pid :: from_raw ( 0 ) , NixSignal :: SIGTSTP ) {
739+ self . eprint ( & format ! ( "sh: internal call to kill failed ({err})" ) ) ;
740+ self . exit ( 1 ) ;
741+ }
735742
736743 let mut current_stdin = libc:: STDIN_FILENO ;
737744 for command in pipeline. commands . head ( ) {
738745 let ( read_pipe, write_pipe) = pipe ( ) ?;
739746 match fork ( ) ? {
740747 ForkResult :: Child => {
741- setpgid ( Pid :: from_raw ( 0 ) , pipeline_pgid)
742- . expect ( "failed to set pipeline pgid" ) ;
748+ // should never fail as ` pipeline_pgid` is a valid process group
749+ setpgid ( Pid :: from_raw ( 0 ) , pipeline_pgid ) . unwrap ( ) ;
743750 drop ( read_pipe) ;
744751 dup2 ( current_stdin, libc:: STDIN_FILENO ) ?;
745752 dup2 ( write_pipe. as_raw_fd ( ) , libc:: STDOUT_FILENO ) ?;
@@ -763,12 +770,46 @@ impl Shell {
763770 self . exit ( return_status) ;
764771 }
765772 ForkResult :: Parent { child } => {
766- if is_process_in_foreground ( ) {
767- nix:: unistd:: tcsetpgrp ( io:: stdin ( ) . as_fd ( ) , child) . unwrap ( ) ;
768- pipeline_exit_status = self . wait_child_process ( child) ?;
769- nix:: unistd:: tcsetpgrp ( io:: stdin ( ) . as_fd ( ) , getpgrp ( ) ) . unwrap ( ) ;
770- } else {
771- pipeline_exit_status = self . wait_child_process ( child) ?;
773+ loop {
774+ match waitpid ( child, Some ( WaitPidFlag :: WNOHANG | WaitPidFlag :: WUNTRACED ) ) ? {
775+ WaitStatus :: Exited ( _, _) => {
776+ // the only way this happened is if there was an error before going
777+ // the child went to sleep
778+ return Ok ( 1 ) ;
779+ }
780+ WaitStatus :: Signaled ( _, _, _) => {
781+ self . eprint ( "sh: unsynchronised pipeline was terminated by another process\n " ) ;
782+ return Ok ( 1 ) ;
783+ }
784+ WaitStatus :: Continued ( _) => {
785+ self . eprint ( "sh: unsynchronised pipeline was restarted by another process\n " ) ;
786+ return Ok ( 1 ) ;
787+ }
788+ WaitStatus :: Stopped ( _, _) => {
789+ if is_process_in_foreground ( ) {
790+ // should never fail as child is a valid process id and
791+ // in the same session as the current shell
792+ let child_gpid = getpgid ( Some ( child) ) . unwrap ( ) ;
793+ // should never fail as stdin is a valid file descriptor and
794+ // child gpid is valid and in the same session
795+ tcsetpgrp ( io:: stdin ( ) . as_fd ( ) , child_gpid) . unwrap ( ) ;
796+ kill ( child, NixSignal :: SIGCONT ) . unwrap ( ) ;
797+ pipeline_exit_status = self . wait_child_process ( child) ?;
798+ // should never fail
799+ tcsetpgrp ( io:: stdin ( ) . as_fd ( ) , getpgrp ( ) ) . unwrap ( ) ;
800+ break ;
801+ } else {
802+ kill ( child, NixSignal :: SIGCONT ) . unwrap ( ) ;
803+ pipeline_exit_status = self . wait_child_process ( child) ?;
804+ break ;
805+ }
806+ }
807+ WaitStatus :: StillAlive => {
808+ self . handle_async_events ( ) ;
809+ std:: thread:: sleep ( Duration :: from_millis ( 16 ) ) ;
810+ }
811+ _ => unreachable ! ( ) ,
812+ }
772813 }
773814 }
774815 }
@@ -958,6 +999,7 @@ impl Shell {
958999 history,
9591000 set_options,
9601001 is_interactive,
1002+ signal_manager : SignalManager :: new ( is_interactive) ,
9611003 ..Default :: default ( )
9621004 }
9631005 }
@@ -1019,7 +1061,7 @@ impl Default for Shell {
10191061 is_interactive : false ,
10201062 last_lineno : 0 ,
10211063 exit_action : TrapAction :: Default ,
1022- signal_manager : SignalManager :: default ( ) ,
1064+ signal_manager : SignalManager :: new ( false ) ,
10231065 background_jobs : JobManager :: default ( ) ,
10241066 history : History :: new ( 32767 ) ,
10251067 umask : !0o022 & 0o777 ,
0 commit comments