clearer names for actions, and infer actions better
[monitor.git] / tools / timeout.pl
1 #!/usr/bin/perl
2
3 ## timeout
4 ##
5 ## (c) 2004-2007 Piotr Roszatycki <dexter@debian.org>, GPL
6 ##
7 ## $Id: timeout.pl 4 2007-06-19 11:58:08Z piotr.roszatycki $
8
9 =head1 NAME
10
11 timeout - Run command with bounded time.
12
13 =head1 SYNOPSIS
14
15 B<timeout> S<B<-h>>
16
17 B<timeout>
18 S<[-I<signal>]>
19 I<time>
20 I<command>
21 ...
22
23 =head1 README
24
25 B<timeout> executes a command and imposes an elapsed time limit.  When the
26 time limit is reached, B<timeout> sends a predefined signal to the target
27 process.
28
29 =cut
30
31
32 use 5.006;
33 use strict;
34
35 use Config;
36 use POSIX ();
37
38
39 ##############################################################################
40
41 ## Default values for constant variables
42 ##
43
44 ## Program name
45 my $NAME = 'timeout';
46
47 ## Program version
48 my $VERSION = '0.11';
49
50
51 ##############################################################################
52
53 ## Signals to handle
54 ##
55 my @signals = qw< HUP INT QUIT TERM SEGV PIPE XCPU XFSZ ALRM >;
56
57
58 ##############################################################################
59
60 ## Signal to send after timeout. Default is KILL.
61 my $signal = 'KILL';
62
63 ## Time to wait
64 my $time = 0;
65
66 ## Command to execute as array of arguments
67 my @command = ();
68
69 ## PID for fork function
70 my $child_pid;
71
72 ## PID for wait function
73 my $pid;
74
75
76 ##############################################################################
77
78 ## usage()
79 ##
80 ## Prints usage message.
81 ##
82 sub usage() {
83     # Lazy loading for Pod::Usage
84     eval 'use Pod::Usage;';
85     die $@ if $@;
86
87     pod2usage(2);
88 }
89
90
91 ## help()
92 ##
93 ## Prints help message.
94 ##
95 sub help() {
96     # Lazy loading for Pod::Usage
97     eval 'use Pod::Usage;';
98     die $@ if $@;
99
100     pod2usage(-verbose=>1, -message=>"$NAME $VERSION\n");
101 }
102
103
104 ## signal_handler($sig)
105 ##
106 ## Handler for signals to clean up child processes
107 ##
108 sub signal_handler($) {
109     my ($sig) = @_;
110     if ($sig eq 'ALRM') {
111         printf STDERR "Timeout: aborting ``%s'' with SIG%s\n", join(' ', @command), $signal;
112     } else {
113         printf STDERR "Got signal SIG%s: aborting command ``%s'' with signal SIG%s\n", $sig, join(' ', @command), $signal;
114     }
115     kill $signal, -$child_pid;
116     exit -1;
117 }
118
119
120 ##############################################################################
121
122 ## Main subroutine
123 ##
124
125
126 ## Parse command line arguments
127 my $arg = $ARGV[0];
128 if ($arg =~ /^-(.*)$/) {
129     my $opt = $1;
130     if ($arg eq '-h' || $arg eq '--help') {
131         help();
132     } elsif ($opt =~ /^[A-Z0-9]+$/) {
133         if ($opt =~ /^\d+/) {
134             # Convert numeric signal to name by using the perl interpreter's
135             # configuration:
136             usage() unless defined $Config{sig_name};
137             $signal = (split(' ', $Config{sig_name}))[$opt];
138         } else {
139             $opt =~ s/^SIG//;
140             $signal = $opt;
141         }
142         shift @ARGV;
143     } else {
144         usage();
145     }
146 }
147
148 usage() if @ARGV < 2;
149
150 $arg = $ARGV[0];
151
152 usage() unless $arg =~ /^\d+$/;
153
154 $time = $arg;
155
156 shift @ARGV;
157
158 @command = @ARGV;
159
160
161 ## Fork for exec
162 if (! defined($child_pid = fork)) {
163     die "Could not fork: $!\n";
164     exit 1;
165 } elsif ($child_pid == 0) {
166     ## child
167
168     ## Set new process group
169     POSIX::setsid;
170     
171     ## Execute command
172     exec @command or die "Can not run command `" . join(' ', @command) . "': $!\n";
173 }
174
175 ## parent
176
177 ## Set the handle for signals
178 foreach my $sig (@signals) {
179     $SIG{$sig} = \&signal_handler;
180 }
181
182 ## Set the alarm
183 alarm $time;
184
185 ## Wait for child
186 while (($pid = wait) != -1 && $pid != $child_pid) {}
187
188 ## Clean exit
189 exit ($pid == $child_pid ? $? >> 8 : -1);
190
191
192 =head1 DESCRIPTION
193
194 B<timeout> executes a command and imposes an elapsed time limit.
195 The command is run in a separate POSIX process group so that the
196 right thing happens with commands that spawn child processes.
197
198 =head1 OPTIONS
199
200 =over 8
201
202 =item -I<signal>
203
204 Specify an optional signal name to send to the controlled process. By default,
205 B<timeout> sends B<KILL>, which cannot be caught or ignored.
206
207 =item I<time>
208
209 The elapsed time limit after which the command is terminated.
210
211 =item I<command>
212
213 The command to be executed.
214
215 =back
216
217 =head1 RETURN CODES
218
219 =over 8
220
221 =item 0..253
222
223 Return code from called command.
224
225 =item 254
226
227 Internal error. No return code could be fetched.
228
229 =item 255
230
231 The timeout was occured.
232
233 =back
234
235 =head1 PREREQUISITES
236
237 =over
238
239 =item *
240
241 L<perl> >= 5.006
242
243 =item *
244
245 L<POSIX>
246
247 =back
248
249 =head1 COREQUISITES
250
251 =over
252
253 =item
254
255 L<Pod::Usage>
256
257 =back
258
259 =head1 SCRIPT CATEGORIES
260
261 UNIX/System_administration
262
263 =head1 AUTHORS
264
265 Piotr Roszatycki E<lt>dexter@debian.orgE<gt>
266
267 =head1 LICENSE
268
269 Copyright 2004-2007 by Piotr Roszatycki E<lt>dexter@debian.orgE<gt>.
270
271 Inspired by timeout.c that is part of The Coroner's Toolkit.
272
273 All rights reserved.  This program is free software; you can redistribute it
274 and/or modify it under the terms of the GNU General Public License, the
275 latest version.