@@ -8,7 +8,7 @@ mod status;
88
99use crate :: status:: ExitStatus ;
1010use clap:: { Arg , ArgAction , Command } ;
11- use std:: io:: ErrorKind ;
11+ use std:: io:: { ErrorKind , Write } ;
1212use std:: os:: unix:: process:: ExitStatusExt ;
1313use std:: process:: { self , Child , Stdio } ;
1414use std:: sync:: atomic:: { self , AtomicBool } ;
@@ -19,16 +19,15 @@ use uucore::parser::parse_time;
1919use uucore:: process:: ChildExt ;
2020use uucore:: translate;
2121
22- #[ cfg( unix) ]
23- use uucore:: signals:: enable_pipe_errors;
24-
2522use uucore:: {
26- format_usage, show_error ,
23+ format_usage,
2724 signals:: { signal_by_name_or_value, signal_name_by_value} ,
2825} ;
2926
30- use nix:: sys:: signal:: { Signal , kill} ;
27+ use nix:: sys:: signal:: { SigHandler , Signal , kill} ;
3128use nix:: unistd:: { Pid , getpid, setpgid} ;
29+ #[ cfg( unix) ]
30+ use std:: os:: unix:: process:: CommandExt ;
3231
3332pub mod options {
3433 pub static FOREGROUND : & str = "foreground" ;
@@ -105,8 +104,16 @@ impl Config {
105104 }
106105}
107106
107+ #[ cfg( unix) ]
108+ uucore:: init_sigpipe_capture!( ) ;
109+
108110#[ uucore:: main]
109111pub fn uumain ( args : impl uucore:: Args ) -> UResult < ( ) > {
112+ #[ cfg( unix) ]
113+ if !uucore:: signals:: sigpipe_was_ignored ( ) {
114+ uucore:: signals:: enable_pipe_errors ( ) ?;
115+ }
116+
110117 let matches =
111118 uucore:: clap_localization:: handle_clap_result_with_exit_code ( uu_app ( ) , args, 125 ) ?;
112119
@@ -179,58 +186,62 @@ pub fn uu_app() -> Command {
179186 . after_help ( translate ! ( "timeout-after-help" ) )
180187}
181188
182- /// Remove pre-existing SIGCHLD handlers that would make waiting for the child's exit code fail.
183- fn unblock_sigchld ( ) {
184- unsafe {
185- nix:: sys:: signal:: signal (
186- nix:: sys:: signal:: Signal :: SIGCHLD ,
187- nix:: sys:: signal:: SigHandler :: SigDfl ,
188- )
189- . unwrap ( ) ;
190- }
189+ /// Install SIGCHLD handler to ensure waiting for child works even if parent ignored SIGCHLD.
190+ fn install_sigchld ( ) {
191+ extern "C" fn chld ( _: libc:: c_int ) { }
192+ let _ = unsafe { nix:: sys:: signal:: signal ( Signal :: SIGCHLD , SigHandler :: Handler ( chld) ) } ;
191193}
192194
193- /// We should terminate child process when receiving TERM signal .
195+ /// We should terminate child process when receiving termination signals .
194196static SIGNALED : AtomicBool = AtomicBool :: new ( false ) ;
197+ /// Track which signal was received (0 = none/timeout expired naturally).
198+ static RECEIVED_SIGNAL : std:: sync:: atomic:: AtomicI32 = std:: sync:: atomic:: AtomicI32 :: new ( 0 ) ;
195199
196- fn catch_sigterm ( ) {
197- use nix:: sys:: signal;
200+ /// Install signal handlers for termination signals.
201+ fn install_signal_handlers ( term_signal : usize ) {
202+ extern "C" fn handle_signal ( sig : libc:: c_int ) {
203+ SIGNALED . store ( true , atomic:: Ordering :: Relaxed ) ;
204+ RECEIVED_SIGNAL . store ( sig, atomic:: Ordering :: Relaxed ) ;
205+ }
198206
199- extern "C" fn handle_sigterm ( signal : libc:: c_int ) {
200- let signal = signal:: Signal :: try_from ( signal) . unwrap ( ) ;
201- if signal == signal:: Signal :: SIGTERM {
202- SIGNALED . store ( true , atomic:: Ordering :: Relaxed ) ;
207+ let handler = SigHandler :: Handler ( handle_signal) ;
208+ let sigpipe_ignored = uucore:: signals:: sigpipe_was_ignored ( ) ;
209+
210+ for sig in [
211+ Signal :: SIGALRM , Signal :: SIGINT , Signal :: SIGQUIT , Signal :: SIGHUP ,
212+ Signal :: SIGTERM , Signal :: SIGPIPE , Signal :: SIGUSR1 , Signal :: SIGUSR2 ,
213+ ] {
214+ if sig != Signal :: SIGPIPE || !sigpipe_ignored {
215+ let _ = unsafe { nix:: sys:: signal:: signal ( sig, handler) } ;
203216 }
204217 }
205218
206- let handler = signal:: SigHandler :: Handler ( handle_sigterm) ;
207- unsafe { signal:: signal ( signal:: Signal :: SIGTERM , handler) } . unwrap ( ) ;
219+ if let Ok ( sig) = Signal :: try_from ( term_signal as i32 ) {
220+ let _ = unsafe { nix:: sys:: signal:: signal ( sig, handler) } ;
221+ }
208222}
209223
210224/// Report that a signal is being sent if the verbose flag is set.
211225fn report_if_verbose ( signal : usize , cmd : & str , verbose : bool ) {
212226 if verbose {
213- let s = signal_name_by_value ( signal) . unwrap ( ) ;
214- show_error ! (
215- "{}" ,
216- translate!( "timeout-verbose-sending-signal" , "signal" => s, "command" => cmd. quote( ) )
217- ) ;
227+ let s = if signal == 0 { "0" . to_string ( ) } else { signal_name_by_value ( signal) . unwrap ( ) . to_string ( ) } ;
228+ let _ = writeln ! ( std:: io:: stderr( ) , "timeout: sending signal {} to command {}" , s, cmd. quote( ) ) ;
218229 }
219230}
220231
221232fn send_signal ( process : & mut Child , signal : usize , foreground : bool ) {
222233 // NOTE: GNU timeout doesn't check for errors of signal.
223234 // The subprocess might have exited just after the timeout.
224- // Sending a signal now would return "No such process", but we should still try to kill the children.
225- if foreground {
226- let _ = process . send_signal ( signal ) ;
227- } else {
228- let _ = process. send_signal_group ( signal) ;
229- let kill_signal = signal_by_name_or_value ( "KILL" ) . unwrap ( ) ;
230- let continued_signal = signal_by_name_or_value ( "CONT" ) . unwrap ( ) ;
231- if signal != kill_signal && signal != continued_signal {
232- _ = process. send_signal_group ( continued_signal ) ;
233- }
235+ let _ = process. send_signal ( signal ) ;
236+ if signal == 0 || foreground {
237+ return ;
238+ }
239+ let _ = process. send_signal_group ( signal) ;
240+ let kill_sig = signal_by_name_or_value ( "KILL" ) . unwrap ( ) ;
241+ let cont_sig = signal_by_name_or_value ( "CONT" ) . unwrap ( ) ;
242+ if signal != kill_sig && signal != cont_sig {
243+ let _ = process. send_signal ( cont_sig ) ;
244+ let _ = process . send_signal_group ( cont_sig ) ;
234245 }
235246}
236247
@@ -320,85 +331,85 @@ fn timeout(
320331 if !foreground {
321332 let _ = setpgid ( Pid :: from_raw ( 0 ) , Pid :: from_raw ( 0 ) ) ;
322333 }
323- #[ cfg( unix) ]
324- enable_pipe_errors ( ) ?;
325334
326- let process = & mut process:: Command :: new ( & cmd[ 0 ] )
335+ let mut cmd_builder = process:: Command :: new ( & cmd[ 0 ] ) ;
336+ cmd_builder
327337 . args ( & cmd[ 1 ..] )
328338 . stdin ( Stdio :: inherit ( ) )
329339 . stdout ( Stdio :: inherit ( ) )
330- . stderr ( Stdio :: inherit ( ) )
331- . spawn ( )
332- . map_err ( |err| {
333- let status_code = match err. kind ( ) {
334- ErrorKind :: NotFound => ExitStatus :: CommandNotFound . into ( ) ,
335- ErrorKind :: PermissionDenied => ExitStatus :: CannotInvoke . into ( ) ,
336- _ => ExitStatus :: CannotInvoke . into ( ) ,
337- } ;
338- USimpleError :: new (
339- status_code,
340- translate ! ( "timeout-error-failed-to-execute-process" , "error" => err) ,
341- )
342- } ) ?;
343- unblock_sigchld ( ) ;
344- catch_sigterm ( ) ;
345- // Wait for the child process for the specified time period.
346- //
347- // If the process exits within the specified time period (the
348- // `Ok(Some(_))` arm), then return the appropriate status code.
349- //
350- // If the process does not exit within that time (the `Ok(None)`
351- // arm) and `kill_after` is specified, then try sending `SIGKILL`.
352- //
353- // TODO The structure of this block is extremely similar to the
354- // structure of `wait_or_kill_process()`. They can probably be
355- // refactored into some common function.
340+ . stderr ( Stdio :: inherit ( ) ) ;
341+
342+ #[ cfg( unix) ]
343+ {
344+ #[ cfg( target_os = "linux" ) ]
345+ let death_sig = Signal :: try_from ( signal as i32 ) . ok ( ) ;
346+ let sigpipe_was_ignored = uucore:: signals:: sigpipe_was_ignored ( ) ;
347+
348+ unsafe {
349+ cmd_builder. pre_exec ( move || {
350+ let _ = nix:: sys:: signal:: signal ( Signal :: SIGTTIN , SigHandler :: SigDfl ) ;
351+ let _ = nix:: sys:: signal:: signal ( Signal :: SIGTTOU , SigHandler :: SigDfl ) ;
352+ if sigpipe_was_ignored {
353+ let _ = nix:: sys:: signal:: signal ( Signal :: SIGPIPE , SigHandler :: SigIgn ) ;
354+ }
355+ #[ cfg( target_os = "linux" ) ]
356+ if let Some ( sig) = death_sig {
357+ let _ = nix:: sys:: prctl:: set_pdeathsig ( sig) ;
358+ }
359+ Ok ( ( ) )
360+ } ) ;
361+ }
362+ }
363+
364+ let process = & mut cmd_builder. spawn ( ) . map_err ( |err| {
365+ let status_code = match err. kind ( ) {
366+ ErrorKind :: NotFound => ExitStatus :: CommandNotFound . into ( ) ,
367+ ErrorKind :: PermissionDenied => ExitStatus :: CannotInvoke . into ( ) ,
368+ _ => ExitStatus :: CannotInvoke . into ( ) ,
369+ } ;
370+ USimpleError :: new (
371+ status_code,
372+ translate ! ( "timeout-error-failed-to-execute-process" , "error" => err) ,
373+ )
374+ } ) ?;
375+ install_sigchld ( ) ;
376+ install_signal_handlers ( signal) ;
377+
356378 match process. wait_or_timeout ( duration, Some ( & SIGNALED ) ) {
357379 Ok ( Some ( status) ) => Err ( status
358380 . code ( )
359381 . unwrap_or_else ( || preserve_signal_info ( status. signal ( ) . unwrap ( ) ) )
360382 . into ( ) ) ,
361383 Ok ( None ) => {
362- report_if_verbose ( signal, & cmd[ 0 ] , verbose) ;
363- send_signal ( process, signal, foreground) ;
364- match kill_after {
365- None => {
366- let status = process. wait ( ) ?;
367- if SIGNALED . load ( atomic:: Ordering :: Relaxed ) {
368- Err ( ExitStatus :: Terminated . into ( ) )
369- } else if preserve_status {
370- if let Some ( ec) = status. code ( ) {
371- Err ( ec. into ( ) )
372- } else if let Some ( sc) = status. signal ( ) {
373- Err ( ExitStatus :: SignalSent ( sc. try_into ( ) . unwrap ( ) ) . into ( ) )
374- } else {
375- Err ( ExitStatus :: CommandTimedOut . into ( ) )
376- }
377- } else {
378- Err ( ExitStatus :: CommandTimedOut . into ( ) )
379- }
380- }
381- Some ( kill_after) => {
382- match wait_or_kill_process (
383- process,
384- & cmd[ 0 ] ,
385- kill_after,
386- preserve_status,
387- foreground,
388- verbose,
389- ) {
390- Ok ( status) => Err ( status. into ( ) ) ,
391- Err ( e) => Err ( USimpleError :: new (
392- ExitStatus :: TimeoutFailed . into ( ) ,
393- e. to_string ( ) ,
394- ) ) ,
395- }
396- }
384+ let received_sig = RECEIVED_SIGNAL . load ( atomic:: Ordering :: Relaxed ) ;
385+ let is_external_signal = received_sig > 0 && received_sig != libc:: SIGALRM ;
386+ let signal_to_send = if is_external_signal { received_sig as usize } else { signal } ;
387+
388+ report_if_verbose ( signal_to_send, & cmd[ 0 ] , verbose) ;
389+ send_signal ( process, signal_to_send, foreground) ;
390+
391+ if let Some ( kill_after) = kill_after {
392+ return match wait_or_kill_process ( process, & cmd[ 0 ] , kill_after, preserve_status, foreground, verbose) {
393+ Ok ( status) => Err ( status. into ( ) ) ,
394+ Err ( e) => Err ( USimpleError :: new ( ExitStatus :: TimeoutFailed . into ( ) , e. to_string ( ) ) ) ,
395+ } ;
396+ }
397+
398+ let status = process. wait ( ) ?;
399+ if is_external_signal {
400+ Err ( ExitStatus :: SignalSent ( received_sig as usize ) . into ( ) )
401+ } else if SIGNALED . load ( atomic:: Ordering :: Relaxed ) {
402+ Err ( ExitStatus :: CommandTimedOut . into ( ) )
403+ } else if preserve_status {
404+ Err ( status. code ( )
405+ . or_else ( || status. signal ( ) . map ( |s| ExitStatus :: SignalSent ( s as usize ) . into ( ) ) )
406+ . unwrap_or ( ExitStatus :: CommandTimedOut . into ( ) )
407+ . into ( ) )
408+ } else {
409+ Err ( ExitStatus :: CommandTimedOut . into ( ) )
397410 }
398411 }
399412 Err ( _) => {
400- // We're going to return ERR_EXIT_STATUS regardless of
401- // whether `send_signal()` succeeds or fails
402413 send_signal ( process, signal, foreground) ;
403414 Err ( ExitStatus :: TimeoutFailed . into ( ) )
404415 }
0 commit comments