Subversion Repositories DevTools

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
5692 dpurdie 1
/*============================================================================
2
**  Copyright (c) VIX TECHNOLOGY (AUST) LTD
3
**============================================================================
4
**
5
**  Project/Product : 
6
**  Filename        : timeout_win32.c
7
**  Author(s)       : DDP
8
**
9
**  Description     : Windows 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
**  Information     :
34
**   Compiler       : ANSI C
35
**   Target         : WIN32 ONLY
36
**
37
**==========================================================================*/
38
 
39
#define _CRT_SECURE_NO_WARNINGS 1       // Supress warnings about use of strcpy()
40
#define _WIN32_WINNT 0x0500             // Access Job Control Functions
41
 
42
#include <stdio.h>
43
#include <windows.h>
44
#include <limits.h>
45
 
46
/*
47
** Exit Codes
48
*/
49
#define     EXIT_TIMEDOUT      124      /* job timed out*/
50
#define     EXIT_CANCELED      125      /* internal error*/
51
#define     EXIT_CANNOT_INVOKE 126      /* error executing job */
52
#define     EXIT_ENOENT        127      /* couldn't find job to exec */
53
 
54
/*
55
**  Globals
56
*/
57
 
58
CRITICAL_SECTION CriticalSection;
59
HANDLE hJobObject;
60
DWORD dwThreadIdTimer;
61
HANDLE hThreadTimer;
62
DWORD   dwChildExitCode;
63
HANDLE  hChildProcess;
64
unsigned int timeOut = 10;                   // Seconds
65
 
66
/*
67
**  Prototypes (forward declarations)
68
*/
69
HANDLE CreateChildProcess(LPTSTR);
70
VOID ErrorExit(LPTSTR);
71
void ErrorExitMsg(UINT ecode, LPTSTR lpszFunction) ;
72
DWORD WINAPI ThreadTimer( LPVOID lpParam );
73
BOOL WINAPI KillJob(DWORD dwCtrlType);
74
unsigned int parseDuration( char *ptr);
75
 
76
/*----------------------------------------------------------------------------
77
** FUNCTION           : main
78
**
79
** DESCRIPTION        : Mainentry point
80
**
81
**
82
** INPUTS             : argc            - Number of args
83
**                      argv            - Address of args
84
**
85
** RETURNS            :
86
**
87
----------------------------------------------------------------------------*/
88
 
89
int main(int argc, char *argv[])
90
{
91
   SECURITY_ATTRIBUTES saAttr; 
92
   LPTSTR   command;
93
   int      argp;
94
 
95
    /*
96
    **  Process command line arguments
97
    **  Options for this program will be located first
98
    */
99
    for ( argp = 1; argp < argc ; argp++ )
100
    {
101
        /*
102
        **  Scan until first non option argument
103
        */
104
        if ( *argv[argp] != '-' )
105
            break;
106
 
107
        if ( _strnicmp( argv[argp], "-TIME:", 6 ) == 0)
108
        {
109
            timeOut = parseDuration(argv[argp] + 6);
110
        }
111
        else
112
        {
113
            fprintf(stderr, "Unknown option: %s", argv[argp] );
114
            ErrorExit( "Bad Option");
115
        }
116
    }
117
 
118
    /*
119
    **  Need at least one more argument - the program to run
120
    */
121
    if ( argp >= argc )
122
    {
123
      ErrorExit("Insufficient arguments");
124
    }
125
 
126
    /*
127
    **  Process the remainder of the command line and create a string
128
    **  For the command line. Some of the arguments will need to be quoted
129
    */
130
    {
131
        size_t length = 0;
132
        int start;
133
        char join = 0;
134
 
135
        /*
136
        **  Calc required size of the command line
137
        **  Allow for two " and 1 space per argument
138
        */
139
        for ( start = argp; start < argc ; start++ )
140
        {
141
            length += 3 + strlen ( argv[start] );
142
        }
143
        command = (LPTSTR) malloc ( length + 1);
144
        if ( !command )
145
            ErrorExit("Cannot allocate memory");
146
        *command = 0;
147
 
148
        for ( start = argp; start < argc ; start++ )
149
        {
150
            char *quote =  strrchr(argv[start], ' ' );
151
 
152
            if ( join )
153
                strcat (command, " " );
154
            join = 1;
155
 
156
            if ( quote )
157
                strcat (command, "\"" );
158
            strcat (command, argv[start] );
159
            if ( quote )
160
                strcat (command, "\"" );
161
        }
162
    }
163
 
164
    /*
165
    ** Create a JOB object to contain our job
166
    */
167
    hJobObject = CreateJobObject(NULL, NULL);
168
    if (! hJobObject)
169
            ErrorExitMsg(EXIT_CANCELED, "CreateJobObject");
170
 
171
    /*
172
    ** Register a handler for control-C
173
    */
174
    if ( !SetConsoleCtrlHandler( KillJob, TRUE))
175
    {
176
        ErrorExitMsg(EXIT_CANCELED, "SetConsoleCtrlHandler");
177
    }
178
 
179
 
180
    /*
181
    **  Create a security attribute
182
    **  Set the bInheritHandle flag so pipe handles are inherited
183
    */
184
    saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
185
    saAttr.bInheritHandle = TRUE; 
186
    saAttr.lpSecurityDescriptor = NULL; 
187
 
188
    /*
189
    **  Now create the child process.
190
    */
191
    hChildProcess = CreateChildProcess(command);
192
    if (! hChildProcess)
193
        ErrorExitMsg(EXIT_CANNOT_INVOKE,"CreateChildProcess");
194
    if (!AssignProcessToJobObject(hJobObject, hChildProcess)) {
195
        TerminateProcess(hChildProcess, 101);
196
        ErrorExitMsg(EXIT_ENOENT, "AssignProcessToJobObject");
197
    };
198
 
199
 
200
    /*
201
    **  Create a critical section to be used by pipe writers
202
    */
203
    InitializeCriticalSection(&CriticalSection);
204
 
205
    /*
206
    **  Create a thread to timeout the main process
207
    */
208
    hThreadTimer = CreateThread(
209
        NULL,                        // default security attributes 
210
        0,                           // use default stack size  
211
        ThreadTimer,                 // thread function
212
        NULL,                        // argument to thread function
213
        0,                           // use default creation flags 
214
        &dwThreadIdTimer);           // returns the thread identifier
215
 
216
    if (hThreadTimer == NULL)
217
        ErrorExitMsg(EXIT_CANCELED, "Create Timer Thread");
218
 
219
    /*
220
    **  What for the process to complete
221
    */
222
    WaitForSingleObject( hChildProcess, INFINITE );
223
 
224
    /*
225
    ** If the one process I started terminates, then kill the entire JOB
226
    */
227
    TerminateThread(hThreadTimer, 0);
228
    TerminateJobObject(hJobObject, 100);
229
 
230
 
231
    /*
232
    **  Determine the exist status of the child process
233
    */
234
    if (!GetExitCodeProcess( hChildProcess, &dwChildExitCode ))
235
        ErrorExit("Error getting child exist code");
236
 
237
    /*
238
    **  Release resources used by the critical section object.
239
    */
240
    DeleteCriticalSection(&CriticalSection);
241
 
242
    /*
243
    **  Exist with the childs exist code
244
    */
245
    ExitProcess(dwChildExitCode);
246
 
247
    /*
248
    ** Should not happen
249
    */
250
    return EXIT_FAILURE;
251
}
252
 
253
/*----------------------------------------------------------------------------
254
** FUNCTION           : apply_time_suffix
255
**
256
** DESCRIPTION        : Helper function to process time suffix
257
**                      Supports s,h,m,d
258
**
259
** INPUTS             : x           - Ref to value to modify
260
**                      suffix_char - Possible suffix char
261
**
262
** RETURNS            : True - suffix was OK
263
**
264
----------------------------------------------------------------------------*/
265
 
266
static int apply_time_suffix (double *x, char suffix_char)
267
{
268
    int multiplier;
269
 
270
    switch (suffix_char)
271
    {
272
    case 0:
273
    case 's':
274
        multiplier = 1;
275
        break;
276
    case 'm':
277
        multiplier = 60;"Invalid duration\n"
278
        break;
279
    case 'h':
280
        multiplier = 60 * 60;
281
        break;
282
    case 'd':
283
        multiplier = 60 * 60 * 24;
284
        break;
285
    default:
286
        return 0;
287
    }
288
    *x *= multiplier;
289
    return 1;
290
}
291
 
292
/*----------------------------------------------------------------------------
293
** FUNCTION           : parseDuration 
294
**
295
** DESCRIPTION        : Parse the timeout duration
296
**                      Allowed - number + 1char suffix
297
**                          1 Char suffix may be:
298
**                          'm' - Minutes
299
**                          'h' - Hours
300
**                          'd' - Days
301
**                      The number is an unsigned integer
302
**
303
** INPUTS             : ptr     - Input string (Floating point)
304
**
305
** RETURNS            : Unsigned integer
306
**                      May not return. May exit with error message
307
**
308
----------------------------------------------------------------------------*/
309
 
310
static unsigned int parseDuration (const char* str)
311
{
312
    double duration;
313
    unsigned int intDuration;
314
    unsigned int duration_floor;
315
    char *ep;
316
    int ok = 1;
317
 
318
    errno = 0;
319
    duration = strtod(str, &ep);
320
    if (duration < 0 )
321
        ok = 0;
322
    if ( ep == str )
323
        ok = 0;
324
    if (errno != 0)
325
        ok = 0;
326
    if (*ep && *(ep + 1))
327
        ok = 0;
328
 
329
    if(ok)
330
        ok = apply_time_suffix (&duration, *ep);
331
 
332
    if (UINT_MAX <= duration)
333
        ok = 0;
334
 
335
    if(!ok)
336
    {
337
        ErrorExit( "Invalid duration");
338
    }
339
 
340
    /*
341
    ** Round up - unless exact
342
    */
343
    duration_floor = (unsigned int)duration;
344
    intDuration = duration_floor + (duration_floor < duration);
345
 
346
    /*fprintf(stderr, "parseDuration: %f (%u)\n", duration,  intDuration);*/
347
 
348
  return intDuration;
349
}
350
 
351
/*----------------------------------------------------------------------------
352
** FUNCTION           : CreateChildProcess
353
**
354
** DESCRIPTION        : Create the child process
355
**                      It will be created in its own process group , so that all members
356
**                      of the same process can be killed.
357
**
358
** INPUTS             : command         - Command string
359
**
360
** RETURNS            : False if can't create
361
**
362
----------------------------------------------------------------------------*/
363
 
364
HANDLE CreateChildProcess( LPTSTR command)
365
{ 
366
   PROCESS_INFORMATION piProcInfo; 
367
   STARTUPINFO siStartInfo; 
368
 
369
    /*
370
    **  Set up members of STARTUPINFO structure.
371
    */
372
    ZeroMemory( &siStartInfo, sizeof(STARTUPINFO) );
373
    siStartInfo.cb = sizeof(STARTUPINFO);
374
 
375
    /*
376
    **  Create the child process.
377
    */
378
    if (! CreateProcess(
379
            NULL,                       // Use from command line
380
            command,                    // command line
381
            NULL,                       // process security attributes
382
            NULL,                       // primary thread security attributes 
383
            TRUE,                       // handles are inherited 
384
            CREATE_NEW_PROCESS_GROUP ,  // creation flags 
385
            NULL,                       // use parent's environment 
386
            NULL,                       // use parent's current directory 
387
            &siStartInfo,               // STARTUPINFO pointer 
388
            &piProcInfo                 // receives PROCESS_INFORMATION
389
            )) return (NULL);
390
 
391
    return piProcInfo.hProcess;
392
}
393
 
394
 
395
/*----------------------------------------------------------------------------
396
** FUNCTION           : ErrorExit
397
**                      ErrorExitMsg - report system messae
398
**
399
** DESCRIPTION        : Error processing
400
**                      REport an error and terminate process
401
**
402
**
403
** INPUTS             : lpszMessage     - Message to display
404
**
405
** RETURNS            : Does't return
406
**                      Will exit with bad code
407
**
408
----------------------------------------------------------------------------*/
409
 
410
VOID ErrorExit (LPTSTR lpszMessage)
411
{ 
412
   fprintf(stderr, "%s\n", lpszMessage);
413
   fflush(stderr) ;
414
   ExitProcess(EXIT_CANCELED);
415
}
416
 
417
void ErrorExitMsg(UINT ecode, LPTSTR lpszFunction) 
418
{ 
419
    // Retrieve the system error message for the last-error code
420
 
421
    LPVOID lpMsgBuf;
422
    DWORD dw = GetLastError(); 
423
 
424
    FormatMessage(
425
        FORMAT_MESSAGE_ALLOCATE_BUFFER | 
426
        FORMAT_MESSAGE_FROM_SYSTEM |
427
        FORMAT_MESSAGE_IGNORE_INSERTS,
428
        NULL,
429
        dw,
430
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
431
        (LPTSTR) &lpMsgBuf,
432
        0, NULL );
433
 
434
    // Display the error message and exit the process
435
 
436
    fprintf(stderr, "%s failed with error %d: %s\n", lpszFunction, dw, lpMsgBuf);
437
    fflush(stderr) ;
438
    LocalFree(lpMsgBuf);
439
 
440
    ExitProcess(ecode); 
441
}
442
 
443
/*----------------------------------------------------------------------------
444
** FUNCTION           : ThreadTimer
445
**
446
** DESCRIPTION        : Thread to timeout the created process
447
**                      Done on a thread so that we can sleep
448
**
449
** INPUTS             : lpParam                 - Not used
450
**
451
** RETURNS            : 0
452
**
453
----------------------------------------------------------------------------*/
454
 
455
DWORD WINAPI ThreadTimer( LPVOID lpParam )
456
{
457
    char lBuf[30];
458
    unsigned int secs;
459
 
460
    _snprintf(lBuf, sizeof(lBuf), "%u", timeOut);
461
    SetEnvironmentVariable( "GBE_JOBLIMIT", lBuf );
462
 
463
    for (secs = timeOut; secs > 0; secs--) 
464
    { 
465
 
466
        /*
467
        ** Update ENV with time remaining
468
        ** Simply for diagnostic purposes
469
        */
470
        _snprintf(lBuf, sizeof(lBuf), "%u", secs);
471
        SetEnvironmentVariable( "GBE_JOBTIME", lBuf );
472
 
473
        /*
474
        **  Wait one second
475
        */
476
        Sleep(1000);
477
    }
478
 
479
    fprintf(stderr, "Process exceeded time limit (%u Secs)\n", timeOut);
480
    fflush(stderr) ;
481
 
482
    TerminateJobObject(hJobObject, EXIT_TIMEDOUT);
483
    return 0;
484
}
485
 
486
/*----------------------------------------------------------------------------
487
** FUNCTION           : KillJob
488
**
489
** DESCRIPTION        : Kill the one job controlled by this program
490
**
491
** INPUTS             : dwCtrlType                 - Not used
492
**
493
** RETURNS            : TRUE - Signal has been processed
494
**
495
----------------------------------------------------------------------------*/
496
 
497
BOOL WINAPI KillJob(DWORD dwCtrlType)
498
{
499
    fprintf(stderr, "Process killed with Control-C\n");
500
    fflush(stderr) ;
501
 
502
    TerminateJobObject(hJobObject, EXIT_TIMEDOUT);
503
    return TRUE;
504
}
505