Subversion Repositories DevTools

Rev

Rev 7299 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
5692 dpurdie 1
/*============================================================================
7300 dpurdie 2
**  COPYRIGHT - VIX IP PTY LTD ("VIX"). ALL RIGHTS RESERVED.
5692 dpurdie 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.  */
5934 dpurdie 429
        signal (SIGTTIN, SIG_IGN);
430
        signal (SIGTTOU, SIG_IGN);
5692 dpurdie 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
}