[GNU Manual] [No POSIX requirement] [Linux man] [FreeBSD man]
Summary
timeout - run a command with a time limit
Lines of code: 578
Principal syscalls: fork(), execvp()
Support syscall: None
Options: 10 (3 short, 7 long)
Added to Coreutils in June 2008 [First version]
Number of revisions: 79 [Code Evolution]
The problem behind timeout is to execute a separate command while retaining external control. This problem is distinct from other 'loader' utilities such as nice, chroot, and env. The solution is to keep both processes alive using fork()
with custom signal handlers for interprocess communication.
apply_time_suffix()
- Processes an input time with a character multiplier suffixblock_cleanup_and_chld()
- Stops the new signal handlers from firingchld()
- New (trivial) signal handler for SIGCHLD when child returnscleanup()
- New signal handler for both parent (significant) and child (trivial exit)disable_core_dumps()
- Attempts to prevent core dumps (unsets PR_SET_DUMPABLE)install_cleanup()
- Installs the new cleaup handlerinstall_sigchld()
- Installs the SIGCHLD handlerparse_duration()
- Handles time strings with suffixsend_sig()
- Wrapper for parent to send signal to a processsettimeout()
- Sets the timer alarm for the parent processunblock_signal()
- Ensures that the provided signal can be handled
die()
- Exit with mandatory non-zero error and message to stderrerror()
- Outputs error message to standard error with possible process terminationoperand2sig()
- Converts operand (number/name) to a number and fills in the name
Setup
timeout declares operating parameters as globals, including:
command
- The target command to executeforeground
- Flag set if the command runs in the foregroundkill_after
- The duration after which to kill the commandmonitored_pid
- The pid of the command to executepreserve_status
- Flag set if we should pass on the command return valueterm_signal
- The signal to send the command at timeouttimed_out
- Flag set if the command ran beyond the timeout periodverbose
- Flag set if the user wants a timeout notification
main() initializes the following:
c
- The letter of the next option to processsigname
- The string name of the signal requested by the usertimeout
- The timeout value from the user after post-processing (time suffix, etc)
Parsing
Parsing builds the operating parameters from the following questions:
- What signal should be sent to the child process at termination?
- How long should we wait before execution terminates?
- Should the target command run in the background or foreground?
- How much feedback should be provide the user?
The only parsing failure is if the user inputs some non-sensical or poorly formatted duration. All other options have a default value if not provided. All parsing failures result in a short error message followed by the usage instructions.
Execution
The timeout utility forks a child process to run the desired command and sets an alarm to stop the child after a time has passed. The flow looks like this:
- Prepare signal handlers used by parent or child, including:
- The chosen termination signal
- The timeout alarm signal (SIGALRM) for cleanup
- The usual littany of exit signals to cleanup
- tty signal handler blocks so the child can continue work
- The handler for SIGCHLD for the parent to take action
- Fork a child process
- The parent:
- Ensure SIGALARM is available
- Set a timer to trigger killing child process (
timer_settime()
oralarm()
) - Prevent failure cleanups
- Wait for child
- Handle various results (exit, failure, timer kill, etc)
- The child:
- Restore default tty signal handlers (not done during
execvp()
) - Execute target command (
execvp()
) - Handle execution failure return values
- Restore default tty signal handlers (not done during
Forks, Signals, and execvp()
It's useful to know how fork, signals, and child execution relate in order to follow the timeout utility.
- After
fork()
, both processes execute the same subsequent code (copied/shared code segment and IP):- The parent process will see a positive (pid_t) value returned from
fork()
- The child process will see a 0 from
fork()
under normal operation
- The parent process will see a positive (pid_t) value returned from
- The child inherits all signal handlers from the parent, but not pending signals (different PIDs)
- After
execvp()
, custom signal handlers are lost (code segment was overwritten) - A child process signals the parent via SIGCHLD when it exits (or interrupted)
*While these rules are generally true, you should confirm actual behavior for your platform.
Failure cases:
- Unable to change signal masks
- Failure to disable core dumps
- Fork fails
- Child could not execute target command
- The parent detects a child core dump
- Unknown error status returned
waitpid()
fails
All failures at this stage output an error message to STDERR and return without displaying usage help