| 5692 |
dpurdie |
1 |
/*============================================================================
|
|
|
2 |
** Copyright (c) VIX TECHNOLOGY (AUST) LTD
|
|
|
3 |
**============================================================================
|
|
|
4 |
**
|
|
|
5 |
** Project/Product :
|
|
|
6 |
** Filename : timeout_unix.c
|
|
|
7 |
** Author(s) : DDP
|
|
|
8 |
**
|
|
|
9 |
** Description : Unix program to limit the wall-clock time that an program can run
|
|
|
10 |
**
|
|
|
11 |
** Create an other process
|
|
|
12 |
** Kill it after it has run for a specified length of time
|
|
|
13 |
**
|
|
|
14 |
** Used to help JATS to:
|
|
|
15 |
** Limit compilation times
|
|
|
16 |
** Handle silly compilers that pop up error messages with an OK
|
|
|
17 |
** Limit Unit Test run times
|
|
|
18 |
**
|
|
|
19 |
** Usage : timeout <options>* command <command args>*
|
|
|
20 |
** Where options are:
|
|
|
21 |
** -time:nnn[.nnn][s] - Timeout in seconds
|
|
|
22 |
** Floating point number with suffix
|
|
|
23 |
** Suffix - s - seconds
|
|
|
24 |
** m - minutes
|
|
|
25 |
** h - hours
|
|
|
26 |
** d - days
|
|
|
27 |
** Command - Users command
|
|
|
28 |
** command args - Arguments passed to the command
|
|
|
29 |
**
|
|
|
30 |
** Note: This code MUST be kept in sync with code for all platforms supported
|
|
|
31 |
** under JATS
|
|
|
32 |
**
|
|
|
33 |
** Note: Large chunks lifted from the Linux command of the same name
|
|
|
34 |
** See copyright notice below
|
|
|
35 |
**
|
|
|
36 |
** Information :
|
|
|
37 |
** Compiler : ANSI C
|
|
|
38 |
** Target : UNIX ONLY
|
|
|
39 |
**
|
|
|
40 |
**==========================================================================*/
|
|
|
41 |
|
|
|
42 |
/* timeout -- run a command with bounded time
|
|
|
43 |
Copyright (C) 2008-2015 Free Software Foundation, Inc.
|
|
|
44 |
|
|
|
45 |
This program is free software: you can redistribute it and/or modify
|
|
|
46 |
it under the terms of the GNU General Public License as published by
|
|
|
47 |
the Free Software Foundation, either version 3 of the License, or
|
|
|
48 |
(at your option) any later version.
|
|
|
49 |
|
|
|
50 |
This program is distributed in the hope that it will be useful,
|
|
|
51 |
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
52 |
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
53 |
GNU General Public License for more details.
|
|
|
54 |
|
|
|
55 |
You should have received a copy of the GNU General Public License
|
|
|
56 |
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
57 |
*/
|
|
|
58 |
|
|
|
59 |
|
|
|
60 |
|
|
|
61 |
|
|
|
62 |
#include <stdio.h>
|
|
|
63 |
#include <unistd.h>
|
|
|
64 |
#include <fcntl.h>
|
|
|
65 |
#include <stdlib.h>
|
|
|
66 |
#include <sys/wait.h>
|
|
|
67 |
#if ! defined(HOST_SOLARIS)
|
|
|
68 |
#include <error.h>
|
|
|
69 |
#endif
|
|
|
70 |
#include <errno.h>
|
|
|
71 |
#include <limits.h>
|
|
|
72 |
#include <signal.h>
|
|
|
73 |
#include <stdarg.h>
|
|
|
74 |
#include "string.h"
|
|
|
75 |
#include "strings.h"
|
|
|
76 |
|
|
|
77 |
/*
|
|
|
78 |
** Exit Codes
|
|
|
79 |
*/
|
|
|
80 |
#define EXIT_TIMEDOUT 124 /* job timed out*/
|
|
|
81 |
#define EXIT_CANCELED 125 /* internal error*/
|
|
|
82 |
#define EXIT_CANNOT_INVOKE 126 /* error executing job */
|
|
|
83 |
#define EXIT_ENOENT 127 /* couldn't find job to exec */
|
|
|
84 |
|
|
|
85 |
static unsigned int timeOut = 10; /* Seconds */
|
|
|
86 |
static int monitored_pid;
|
|
|
87 |
static int timed_out = 0;
|
|
|
88 |
static int term_signal = SIGTERM; /* same default as kill command. */
|
|
|
89 |
static unsigned int kill_after = 5;
|
|
|
90 |
|
|
|
91 |
typedef int bool;
|
|
|
92 |
#define true 1
|
|
|
93 |
#define false 0
|
|
|
94 |
|
|
|
95 |
/*
|
|
|
96 |
** Prototypes (forward declarations)
|
|
|
97 |
*/
|
|
|
98 |
|
|
|
99 |
/*----------------------------------------------------------------------------
|
|
|
100 |
** FUNCTION : apply_time_suffix
|
|
|
101 |
**
|
|
|
102 |
** DESCRIPTION : Helper function to process time suffix
|
|
|
103 |
** Supports s,h,m,d
|
|
|
104 |
**
|
|
|
105 |
** INPUTS : x - Ref to value to modify
|
|
|
106 |
** suffix_char - Possible suffix char
|
|
|
107 |
**
|
|
|
108 |
** RETURNS : True - suffix was OK
|
|
|
109 |
**
|
|
|
110 |
----------------------------------------------------------------------------*/
|
|
|
111 |
|
|
|
112 |
static int apply_time_suffix (double *x, char suffix_char)
|
|
|
113 |
{
|
|
|
114 |
int multiplier;
|
|
|
115 |
|
|
|
116 |
switch (suffix_char)
|
|
|
117 |
{
|
|
|
118 |
case 0:
|
|
|
119 |
case 's':
|
|
|
120 |
multiplier = 1;
|
|
|
121 |
break;
|
|
|
122 |
case 'm':
|
|
|
123 |
multiplier = 60;
|
|
|
124 |
break;
|
|
|
125 |
case 'h':
|
|
|
126 |
multiplier = 60 * 60;
|
|
|
127 |
break;
|
|
|
128 |
case 'd':
|
|
|
129 |
multiplier = 60 * 60 * 24;
|
|
|
130 |
break;
|
|
|
131 |
default:
|
|
|
132 |
return 0;
|
|
|
133 |
}
|
|
|
134 |
*x *= multiplier;
|
|
|
135 |
return 1;
|
|
|
136 |
}
|
|
|
137 |
|
|
|
138 |
/*----------------------------------------------------------------------------
|
|
|
139 |
** FUNCTION : parseDuration
|
|
|
140 |
**
|
|
|
141 |
** DESCRIPTION : Parse the timeout duration
|
|
|
142 |
** Allowed - number + 1char suffix
|
|
|
143 |
** 1 Char suffix may be:
|
|
|
144 |
** 'm' - Minutes
|
|
|
145 |
** 'h' - Hours
|
|
|
146 |
** 'd' - Days
|
|
|
147 |
** The number is an unsigned integer
|
|
|
148 |
**
|
|
|
149 |
** INPUTS : ptr - Input string (Floating point)
|
|
|
150 |
**
|
|
|
151 |
** RETURNS : Unsigned integer
|
|
|
152 |
** May not return. May exit with error message
|
|
|
153 |
**
|
|
|
154 |
----------------------------------------------------------------------------*/
|
|
|
155 |
|
|
|
156 |
static unsigned int parseDuration (const char* str)
|
|
|
157 |
{
|
|
|
158 |
double duration;
|
|
|
159 |
unsigned int intDuration;
|
|
|
160 |
unsigned int duration_floor;
|
|
|
161 |
char *ep;
|
|
|
162 |
int ok = 1;
|
|
|
163 |
|
|
|
164 |
errno = 0;
|
|
|
165 |
duration = strtod(str, &ep);
|
|
|
166 |
if (duration < 0 )
|
|
|
167 |
ok = 0;
|
|
|
168 |
if ( ep == str )
|
|
|
169 |
ok = 0;
|
|
|
170 |
if (errno != 0)
|
|
|
171 |
ok = 0;
|
|
|
172 |
if (*ep && *(ep + 1))
|
|
|
173 |
ok = 0;
|
|
|
174 |
|
|
|
175 |
if(ok)
|
|
|
176 |
ok = apply_time_suffix (&duration, *ep);
|
|
|
177 |
|
|
|
178 |
if (UINT_MAX <= duration)
|
|
|
179 |
ok = 0;
|
|
|
180 |
|
|
|
181 |
if(!ok)
|
|
|
182 |
{
|
|
|
183 |
fprintf(stderr, "Invalid duration");
|
|
|
184 |
exit (EXIT_CANCELED) ;
|
|
|
185 |
}
|
|
|
186 |
|
|
|
187 |
/*
|
|
|
188 |
** Round up - unless exact
|
|
|
189 |
*/
|
|
|
190 |
duration_floor = (unsigned int)duration;
|
|
|
191 |
intDuration = duration_floor + (duration_floor < duration);
|
|
|
192 |
|
|
|
193 |
/*fprintf(stderr, "parseDuration: %f (%u)\n", duration, intDuration);*/
|
|
|
194 |
|
|
|
195 |
return intDuration;
|
|
|
196 |
}
|
|
|
197 |
|
|
|
198 |
#if defined(HOST_SOLARIS)
|
|
|
199 |
/*----------------------------------------------------------------------------
|
|
|
200 |
** FUNCTION : error
|
|
|
201 |
**
|
|
|
202 |
** DESCRIPTION : Poor mans emulation of the Linux function 'error'
|
|
|
203 |
**
|
|
|
204 |
**
|
|
|
205 |
** INPUTS : status - not used
|
|
|
206 |
** errnum - Error number
|
|
|
207 |
** format - Format string
|
|
|
208 |
** ... - Arguments for format
|
|
|
209 |
**
|
|
|
210 |
** RETURNS : Nothing
|
|
|
211 |
**
|
|
|
212 |
----------------------------------------------------------------------------*/
|
|
|
213 |
|
|
|
214 |
static void error(int status, int errnum, const char *format, ...)
|
|
|
215 |
{
|
|
|
216 |
va_list params;
|
|
|
217 |
|
|
|
218 |
va_start(params, format);
|
|
|
219 |
vfprintf (stderr, format, params);
|
|
|
220 |
|
|
|
221 |
errno = errnum;
|
|
|
222 |
perror (" ");
|
|
|
223 |
|
|
|
224 |
va_end(params);
|
|
|
225 |
}
|
|
|
226 |
#endif
|
|
|
227 |
|
|
|
228 |
/* Try to disable core dumps for this process.
|
|
|
229 |
Return TRUE if successful, FALSE otherwise. */
|
|
|
230 |
static bool disable_core_dumps (void)
|
|
|
231 |
{
|
|
|
232 |
#if HAVE_PRCTL && defined PR_SET_DUMPABLE
|
|
|
233 |
if (prctl (PR_SET_DUMPABLE, 0) == 0)
|
|
|
234 |
return true;
|
|
|
235 |
error (0, errno, "warning: disabling core dumps failed");
|
|
|
236 |
return false;
|
|
|
237 |
|
|
|
238 |
#elif HAVE_SETRLIMIT && defined RLIMIT_CORE
|
|
|
239 |
/* Note this doesn't disable processing by a filter in
|
|
|
240 |
/proc/sys/kernel/core_pattern on Linux. */
|
|
|
241 |
if (setrlimit (RLIMIT_CORE, &(struct rlimit) {0,0}) == 0)
|
|
|
242 |
return true;
|
|
|
243 |
error (0, errno, "warning: disabling core dumps failed");
|
|
|
244 |
return false;
|
|
|
245 |
|
|
|
246 |
#else
|
|
|
247 |
return false;
|
|
|
248 |
#endif
|
|
|
249 |
}
|
|
|
250 |
|
|
|
251 |
|
|
|
252 |
static void unblock_signal (int sig)
|
|
|
253 |
{
|
|
|
254 |
sigset_t unblock_set;
|
|
|
255 |
sigemptyset (&unblock_set);
|
|
|
256 |
sigaddset (&unblock_set, sig);
|
|
|
257 |
if (sigprocmask (SIG_UNBLOCK, &unblock_set, NULL) != 0)
|
|
|
258 |
error (0, errno, "warning: sigprocmask");
|
|
|
259 |
}
|
|
|
260 |
|
|
|
261 |
/* Start the timeout after which we'll receive a SIGALRM.
|
|
|
262 |
Round DURATION up to the next representable value.
|
|
|
263 |
Treat out-of-range values as if they were maximal,
|
|
|
264 |
as that's more useful in practice than reporting an error.
|
|
|
265 |
'0' means don't timeout. */
|
|
|
266 |
static void settimeout (unsigned int duration, bool warn)
|
|
|
267 |
{
|
|
|
268 |
/* We configure timers below so that SIGALRM is sent on expiry.
|
|
|
269 |
Therefore ensure we don't inherit a mask blocking SIGALRM. */
|
|
|
270 |
unblock_signal (SIGALRM);
|
|
|
271 |
|
|
|
272 |
alarm (duration);
|
|
|
273 |
}
|
|
|
274 |
|
|
|
275 |
/* send SIG avoiding the current process. */
|
|
|
276 |
|
|
|
277 |
static int send_sig (int where, int sig)
|
|
|
278 |
{
|
|
|
279 |
/* If sending to the group, then ignore the signal,
|
|
|
280 |
so we don't go into a signal loop. Note that this will ignore any of the
|
|
|
281 |
signals registered in install_signal_handlers(), that are sent after we
|
|
|
282 |
propagate the first one, which hopefully won't be an issue. Note this
|
|
|
283 |
process can be implicitly multithreaded due to some timer_settime()
|
|
|
284 |
implementations, therefore a signal sent to the group, can be sent
|
|
|
285 |
multiple times to this process. */
|
|
|
286 |
if (where == 0)
|
|
|
287 |
signal (sig, SIG_IGN);
|
|
|
288 |
return kill (where, sig);
|
|
|
289 |
}
|
|
|
290 |
|
|
|
291 |
static void cleanup (int sig)
|
|
|
292 |
{
|
|
|
293 |
if (sig == SIGALRM)
|
|
|
294 |
{
|
|
|
295 |
timed_out = 1;
|
|
|
296 |
sig = term_signal;
|
|
|
297 |
}
|
|
|
298 |
if (monitored_pid)
|
|
|
299 |
{
|
|
|
300 |
if (kill_after)
|
|
|
301 |
{
|
|
|
302 |
int saved_errno = errno; /* settimeout may reset. */
|
|
|
303 |
/* Start a new timeout after which we'll send SIGKILL. */
|
|
|
304 |
term_signal = SIGKILL;
|
|
|
305 |
settimeout (kill_after, false);
|
|
|
306 |
kill_after = 0; /* Don't let later signals reset kill alarm. */
|
|
|
307 |
errno = saved_errno;
|
|
|
308 |
}
|
|
|
309 |
|
|
|
310 |
/* Send the signal directly to the monitored child,
|
|
|
311 |
in case it has itself become group leader,
|
|
|
312 |
or is not running in a separate group. */
|
|
|
313 |
send_sig (monitored_pid, sig);
|
|
|
314 |
|
|
|
315 |
/* The normal case is the job has remained in our
|
|
|
316 |
newly created process group, so send to all processes in that. */
|
|
|
317 |
send_sig (0, sig);
|
|
|
318 |
if (sig != SIGKILL && sig != SIGCONT)
|
|
|
319 |
{
|
|
|
320 |
send_sig (monitored_pid, SIGCONT);
|
|
|
321 |
send_sig (0, SIGCONT);
|
|
|
322 |
}
|
|
|
323 |
}
|
|
|
324 |
else /* we're the child or the child is not exec'd yet. */
|
|
|
325 |
_exit (128 + sig);
|
|
|
326 |
}
|
|
|
327 |
|
|
|
328 |
static void install_signal_handlers (int sigterm)
|
|
|
329 |
{
|
|
|
330 |
struct sigaction sa;
|
|
|
331 |
sigemptyset (&sa.sa_mask); /* Allow concurrent calls to handler */
|
|
|
332 |
sa.sa_handler = cleanup;
|
|
|
333 |
sa.sa_flags = SA_RESTART; /* Restart syscalls if possible, as that's
|
|
|
334 |
more likely to work cleanly. */
|
|
|
335 |
|
|
|
336 |
sigaction (SIGALRM, &sa, NULL); /* our timeout. */
|
|
|
337 |
sigaction (SIGINT, &sa, NULL); /* Ctrl-C at terminal for example. */
|
|
|
338 |
sigaction (SIGQUIT, &sa, NULL); /* Ctrl-\ at terminal for example. */
|
|
|
339 |
sigaction (SIGHUP, &sa, NULL); /* terminal closed for example. */
|
|
|
340 |
sigaction (SIGTERM, &sa, NULL); /* if we're killed, stop monitored proc. */
|
|
|
341 |
sigaction (sigterm, &sa, NULL); /* user specified termination signal. */
|
|
|
342 |
}
|
|
|
343 |
|
|
|
344 |
/*----------------------------------------------------------------------------
|
|
|
345 |
** FUNCTION : main
|
|
|
346 |
**
|
|
|
347 |
** DESCRIPTION : Main entry point
|
|
|
348 |
**
|
|
|
349 |
**
|
|
|
350 |
** INPUTS : argc - Number of args
|
|
|
351 |
** argv - Address of args
|
|
|
352 |
**
|
|
|
353 |
** RETURNS :
|
|
|
354 |
**
|
|
|
355 |
----------------------------------------------------------------------------*/
|
|
|
356 |
|
|
|
357 |
int main (int argc, char **argv)
|
|
|
358 |
{
|
|
|
359 |
int argp;
|
|
|
360 |
|
|
|
361 |
/*
|
|
|
362 |
** Process command line arguments
|
|
|
363 |
** Options for this program will be located first
|
|
|
364 |
*/
|
|
|
365 |
for ( argp = 1; argp < argc ; argp++ )
|
|
|
366 |
{
|
|
|
367 |
/*
|
|
|
368 |
** Scan until first non option argument
|
|
|
369 |
*/
|
|
|
370 |
if ( *argv[argp] != '-' )
|
|
|
371 |
break;
|
|
|
372 |
|
|
|
373 |
if ( strncasecmp( argv[argp], "-TIME:", 6 ) == 0)
|
|
|
374 |
{
|
|
|
375 |
timeOut = parseDuration(argv[argp] + 6);
|
|
|
376 |
/* fprintf(stderr, "Timeout: %d\n", timeOut); */
|
|
|
377 |
}
|
|
|
378 |
else
|
|
|
379 |
{
|
|
|
380 |
fprintf(stderr, "Error: Unknown option: %s\n", argv[argp] );
|
|
|
381 |
exit(EXIT_CANCELED);
|
|
|
382 |
}
|
|
|
383 |
}
|
|
|
384 |
|
|
|
385 |
/*
|
|
|
386 |
** Need at least one more argument - the program to run
|
|
|
387 |
*/
|
|
|
388 |
if ( argp >= argc || argc <= 1 )
|
|
|
389 |
{
|
|
|
390 |
fputs("Error: Insufficient number of arguments given\n", stderr);
|
|
|
391 |
fputs(" Need at least the name of the program\n", stderr);
|
|
|
392 |
exit(EXIT_CANCELED);
|
|
|
393 |
}
|
|
|
394 |
|
|
|
395 |
/* Ensure we're in our own group so all subprocesses can be killed.
|
|
|
396 |
Note we don't just put the child in a separate group as
|
|
|
397 |
then we would need to worry about foreground and background groups
|
|
|
398 |
and propagating signals between them. */
|
|
|
399 |
setpgid (0, 0);
|
|
|
400 |
|
|
|
401 |
/* Setup handlers before fork() so that we
|
|
|
402 |
handle any signals caused by child, without races. */
|
|
|
403 |
install_signal_handlers (term_signal);
|
|
|
404 |
signal (SIGTTIN, SIG_IGN); /* Don't stop if background child needs tty. */
|
|
|
405 |
signal (SIGTTOU, SIG_IGN); /* Don't stop if background child needs tty. */
|
|
|
406 |
signal (SIGCHLD, SIG_DFL); /* Don't inherit CHLD handling from parent. */
|
|
|
407 |
|
|
|
408 |
monitored_pid = fork ();
|
|
|
409 |
if (monitored_pid == -1)
|
|
|
410 |
{
|
|
|
411 |
error (0, errno, "fork system call failed");
|
|
|
412 |
return EXIT_CANCELED;
|
|
|
413 |
}
|
|
|
414 |
else if (monitored_pid == 0)
|
|
|
415 |
{
|
|
|
416 |
/* Child */
|
|
|
417 |
int exit_status;
|
|
|
418 |
char lBuf[30];
|
|
|
419 |
|
|
|
420 |
/* Expose the Timeout as an EnvVar */
|
|
|
421 |
snprintf(lBuf, sizeof(lBuf), "GBE_JOBLIMIT=%u", timeOut);
|
|
|
422 |
if (putenv( lBuf ))
|
|
|
423 |
{
|
|
|
424 |
error (0, errno, "putenv call failed");
|
|
|
425 |
return EXIT_CANCELED;
|
|
|
426 |
}
|
|
|
427 |
|
|
|
428 |
/* exec doesn't reset SIG_IGN -> SIG_DFL. */
|
|
|
429 |
signal (SIGTTIN, SIG_DFL);
|
|
|
430 |
signal (SIGTTOU, SIG_DFL);
|
|
|
431 |
|
|
|
432 |
execvp (argv[argp], &argv[argp]);
|
|
|
433 |
|
|
|
434 |
/* exit like sh, env, nohup, ... */
|
|
|
435 |
exit_status = errno == ENOENT ? EXIT_ENOENT : EXIT_CANNOT_INVOKE;
|
|
|
436 |
error (0, errno, "failed to run command \"%s\"", argv[argp]);
|
|
|
437 |
return exit_status;
|
|
|
438 |
}
|
|
|
439 |
else
|
|
|
440 |
{
|
|
|
441 |
/* Parent */
|
|
|
442 |
pid_t wait_result;
|
|
|
443 |
int status;
|
|
|
444 |
|
|
|
445 |
settimeout (timeOut, true);
|
|
|
446 |
|
|
|
447 |
while ((wait_result = waitpid (monitored_pid, &status, 0)) < 0
|
|
|
448 |
&& errno == EINTR)
|
|
|
449 |
continue;
|
|
|
450 |
|
|
|
451 |
if (wait_result < 0)
|
|
|
452 |
{
|
|
|
453 |
/* shouldn't happen. */
|
|
|
454 |
error (0, errno, "error waiting for command");
|
|
|
455 |
status = EXIT_CANCELED;
|
|
|
456 |
}
|
|
|
457 |
else
|
|
|
458 |
{
|
|
|
459 |
if (WIFEXITED (status))
|
|
|
460 |
status = WEXITSTATUS (status);
|
|
|
461 |
else if (WIFSIGNALED (status))
|
|
|
462 |
{
|
|
|
463 |
int sig = WTERMSIG (status);
|
|
|
464 |
if (WCOREDUMP (status))
|
|
|
465 |
error (0, 0, "the monitored command dumped core");
|
|
|
466 |
if (!timed_out && disable_core_dumps ())
|
|
|
467 |
{
|
|
|
468 |
/* exit with the signal flag set. */
|
|
|
469 |
signal (sig, SIG_DFL);
|
|
|
470 |
raise (sig);
|
|
|
471 |
}
|
|
|
472 |
status = sig + 128; /* what sh returns for signaled processes. */
|
|
|
473 |
}
|
|
|
474 |
else
|
|
|
475 |
{
|
|
|
476 |
/* shouldn't happen. */
|
|
|
477 |
error (0, 0, "unknown status from command (%d)", status);
|
|
|
478 |
status = EXIT_FAILURE;
|
|
|
479 |
}
|
|
|
480 |
}
|
|
|
481 |
|
|
|
482 |
if (timed_out)
|
|
|
483 |
{
|
|
|
484 |
fprintf(stderr, "Process exceeded time limit (%d Secs)\n", timeOut);
|
|
|
485 |
status = EXIT_TIMEDOUT;
|
|
|
486 |
}
|
|
|
487 |
return status;
|
|
|
488 |
}
|
|
|
489 |
}
|