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);
5700 dpurdie 74
unsigned int parseDuration(const char* str);
5692 dpurdie 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;
5710 dpurdie 134
//        #define JOB_OBJECT_LIMIT_BREAKAWAY_OK 0x00000800
135
        JOBOBJECT_BASIC_LIMIT_INFORMATION basicLimits = {0, 0, JOB_OBJECT_LIMIT_BREAKAWAY_OK};
136
        JOBOBJECT_EXTENDED_LIMIT_INFORMATION extendedLimits = {basicLimits};
5692 dpurdie 137
 
138
        /*
139
        **  Calc required size of the command line
140
        **  Allow for two " and 1 space per argument
141
        */
142
        for ( start = argp; start < argc ; start++ )
143
        {
144
            length += 3 + strlen ( argv[start] );
145
        }
146
        command = (LPTSTR) malloc ( length + 1);
147
        if ( !command )
148
            ErrorExit("Cannot allocate memory");
149
        *command = 0;
150
 
151
        for ( start = argp; start < argc ; start++ )
152
        {
153
            char *quote =  strrchr(argv[start], ' ' );
154
 
155
            if ( join )
156
                strcat (command, " " );
157
            join = 1;
158
 
159
            if ( quote )
160
                strcat (command, "\"" );
161
            strcat (command, argv[start] );
162
            if ( quote )
163
                strcat (command, "\"" );
164
        }
165
    }
166
 
167
    /*
168
    ** Create a JOB object to contain our job
169
    */
170
    hJobObject = CreateJobObject(NULL, NULL);
171
    if (! hJobObject)
172
            ErrorExitMsg(EXIT_CANCELED, "CreateJobObject");
173
 
174
    /*
5710 dpurdie 175
    **  Set extended information to allow job breakaway
176
    **  Required so that we can have a timeout job within a timeout job
177
    */
178
    if (!SetInformationJobObject(hJobObject, JobObjectExtendedLimitInformation, &extendedLimits, sizeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION)))
179
        ErrorExitMsg(EXIT_CANCELED, "Error setting job object information.");
180
 
181
    /*
5692 dpurdie 182
    ** Register a handler for control-C
183
    */
184
    if ( !SetConsoleCtrlHandler( KillJob, TRUE))
185
    {
186
        ErrorExitMsg(EXIT_CANCELED, "SetConsoleCtrlHandler");
187
    }
188
 
189
 
190
    /*
191
    **  Create a security attribute
192
    **  Set the bInheritHandle flag so pipe handles are inherited
193
    */
194
    saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
195
    saAttr.bInheritHandle = TRUE; 
196
    saAttr.lpSecurityDescriptor = NULL; 
197
 
198
    /*
199
    **  Now create the child process.
200
    */
201
    hChildProcess = CreateChildProcess(command);
202
    if (! hChildProcess)
203
        ErrorExitMsg(EXIT_CANNOT_INVOKE,"CreateChildProcess");
204
    if (!AssignProcessToJobObject(hJobObject, hChildProcess)) {
205
        TerminateProcess(hChildProcess, 101);
206
        ErrorExitMsg(EXIT_ENOENT, "AssignProcessToJobObject");
207
    };
208
 
209
 
210
    /*
211
    **  Create a critical section to be used by pipe writers
212
    */
213
    InitializeCriticalSection(&CriticalSection);
214
 
215
    /*
216
    **  Create a thread to timeout the main process
217
    */
218
    hThreadTimer = CreateThread(
219
        NULL,                        // default security attributes 
220
        0,                           // use default stack size  
221
        ThreadTimer,                 // thread function
222
        NULL,                        // argument to thread function
223
        0,                           // use default creation flags 
224
        &dwThreadIdTimer);           // returns the thread identifier
225
 
226
    if (hThreadTimer == NULL)
227
        ErrorExitMsg(EXIT_CANCELED, "Create Timer Thread");
228
 
229
    /*
230
    **  What for the process to complete
231
    */
232
    WaitForSingleObject( hChildProcess, INFINITE );
233
 
234
    /*
235
    ** If the one process I started terminates, then kill the entire JOB
236
    */
237
    TerminateThread(hThreadTimer, 0);
238
    TerminateJobObject(hJobObject, 100);
239
 
240
 
241
    /*
242
    **  Determine the exist status of the child process
243
    */
244
    if (!GetExitCodeProcess( hChildProcess, &dwChildExitCode ))
245
        ErrorExit("Error getting child exist code");
246
 
247
    /*
248
    **  Release resources used by the critical section object.
249
    */
250
    DeleteCriticalSection(&CriticalSection);
251
 
252
    /*
253
    **  Exist with the childs exist code
254
    */
255
    ExitProcess(dwChildExitCode);
256
 
257
    /*
258
    ** Should not happen
259
    */
260
    return EXIT_FAILURE;
261
}
262
 
263
/*----------------------------------------------------------------------------
264
** FUNCTION           : apply_time_suffix
265
**
266
** DESCRIPTION        : Helper function to process time suffix
267
**                      Supports s,h,m,d
268
**
269
** INPUTS             : x           - Ref to value to modify
270
**                      suffix_char - Possible suffix char
271
**
272
** RETURNS            : True - suffix was OK
273
**
274
----------------------------------------------------------------------------*/
275
 
276
static int apply_time_suffix (double *x, char suffix_char)
277
{
278
    int multiplier;
279
 
280
    switch (suffix_char)
281
    {
282
    case 0:
283
    case 's':
284
        multiplier = 1;
285
        break;
286
    case 'm':
5700 dpurdie 287
        multiplier = 60;
5692 dpurdie 288
        break;
289
    case 'h':
290
        multiplier = 60 * 60;
291
        break;
292
    case 'd':
293
        multiplier = 60 * 60 * 24;
294
        break;
295
    default:
296
        return 0;
297
    }
298
    *x *= multiplier;
299
    return 1;
300
}
301
 
302
/*----------------------------------------------------------------------------
303
** FUNCTION           : parseDuration 
304
**
305
** DESCRIPTION        : Parse the timeout duration
306
**                      Allowed - number + 1char suffix
307
**                          1 Char suffix may be:
308
**                          'm' - Minutes
309
**                          'h' - Hours
310
**                          'd' - Days
311
**                      The number is an unsigned integer
312
**
313
** INPUTS             : ptr     - Input string (Floating point)
314
**
315
** RETURNS            : Unsigned integer
316
**                      May not return. May exit with error message
317
**
318
----------------------------------------------------------------------------*/
319
 
320
static unsigned int parseDuration (const char* str)
321
{
322
    double duration;
323
    unsigned int intDuration;
324
    unsigned int duration_floor;
325
    char *ep;
326
    int ok = 1;
327
 
328
    errno = 0;
329
    duration = strtod(str, &ep);
330
    if (duration < 0 )
331
        ok = 0;
332
    if ( ep == str )
333
        ok = 0;
334
    if (errno != 0)
335
        ok = 0;
336
    if (*ep && *(ep + 1))
337
        ok = 0;
338
 
339
    if(ok)
340
        ok = apply_time_suffix (&duration, *ep);
341
 
342
    if (UINT_MAX <= duration)
343
        ok = 0;
344
 
345
    if(!ok)
346
    {
347
        ErrorExit( "Invalid duration");
348
    }
349
 
350
    /*
351
    ** Round up - unless exact
352
    */
353
    duration_floor = (unsigned int)duration;
354
    intDuration = duration_floor + (duration_floor < duration);
355
 
356
    /*fprintf(stderr, "parseDuration: %f (%u)\n", duration,  intDuration);*/
357
 
358
  return intDuration;
359
}
360
 
361
/*----------------------------------------------------------------------------
362
** FUNCTION           : CreateChildProcess
363
**
364
** DESCRIPTION        : Create the child process
365
**                      It will be created in its own process group , so that all members
366
**                      of the same process can be killed.
367
**
368
** INPUTS             : command         - Command string
369
**
370
** RETURNS            : False if can't create
371
**
372
----------------------------------------------------------------------------*/
373
 
374
HANDLE CreateChildProcess( LPTSTR command)
375
{ 
376
   PROCESS_INFORMATION piProcInfo; 
377
   STARTUPINFO siStartInfo; 
378
 
379
    /*
380
    **  Set up members of STARTUPINFO structure.
381
    */
382
    ZeroMemory( &siStartInfo, sizeof(STARTUPINFO) );
383
    siStartInfo.cb = sizeof(STARTUPINFO);
384
 
385
    /*
386
    **  Create the child process.
387
    */
5700 dpurdie 388
    #define LOCAL_CREATE_BREAKAWAY_FROM_JOB 0x01000000
389
    #define CREATION_FLAGS LOCAL_CREATE_BREAKAWAY_FROM_JOB | CREATE_NEW_PROCESS_GROUP 
5692 dpurdie 390
    if (! CreateProcess(
391
            NULL,                       // Use from command line
392
            command,                    // command line
393
            NULL,                       // process security attributes
394
            NULL,                       // primary thread security attributes 
395
            TRUE,                       // handles are inherited 
5700 dpurdie 396
            CREATION_FLAGS ,            // creation flags 
5692 dpurdie 397
            NULL,                       // use parent's environment 
398
            NULL,                       // use parent's current directory 
399
            &siStartInfo,               // STARTUPINFO pointer 
400
            &piProcInfo                 // receives PROCESS_INFORMATION
401
            )) return (NULL);
402
 
403
    return piProcInfo.hProcess;
404
}
405
 
406
 
407
/*----------------------------------------------------------------------------
408
** FUNCTION           : ErrorExit
409
**                      ErrorExitMsg - report system messae
410
**
411
** DESCRIPTION        : Error processing
412
**                      REport an error and terminate process
413
**
414
**
415
** INPUTS             : lpszMessage     - Message to display
416
**
417
** RETURNS            : Does't return
418
**                      Will exit with bad code
419
**
420
----------------------------------------------------------------------------*/
421
 
422
VOID ErrorExit (LPTSTR lpszMessage)
423
{ 
424
   fprintf(stderr, "%s\n", lpszMessage);
425
   fflush(stderr) ;
426
   ExitProcess(EXIT_CANCELED);
427
}
428
 
429
void ErrorExitMsg(UINT ecode, LPTSTR lpszFunction) 
430
{ 
431
    // Retrieve the system error message for the last-error code
432
 
433
    LPVOID lpMsgBuf;
434
    DWORD dw = GetLastError(); 
435
 
436
    FormatMessage(
437
        FORMAT_MESSAGE_ALLOCATE_BUFFER | 
438
        FORMAT_MESSAGE_FROM_SYSTEM |
439
        FORMAT_MESSAGE_IGNORE_INSERTS,
440
        NULL,
441
        dw,
442
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
443
        (LPTSTR) &lpMsgBuf,
444
        0, NULL );
445
 
446
    // Display the error message and exit the process
447
 
448
    fprintf(stderr, "%s failed with error %d: %s\n", lpszFunction, dw, lpMsgBuf);
449
    fflush(stderr) ;
450
    LocalFree(lpMsgBuf);
451
 
452
    ExitProcess(ecode); 
453
}
454
 
455
/*----------------------------------------------------------------------------
456
** FUNCTION           : ThreadTimer
457
**
458
** DESCRIPTION        : Thread to timeout the created process
459
**                      Done on a thread so that we can sleep
460
**
461
** INPUTS             : lpParam                 - Not used
462
**
463
** RETURNS            : 0
464
**
465
----------------------------------------------------------------------------*/
466
 
467
DWORD WINAPI ThreadTimer( LPVOID lpParam )
468
{
469
    char lBuf[30];
470
    unsigned int secs;
471
 
472
    _snprintf(lBuf, sizeof(lBuf), "%u", timeOut);
473
    SetEnvironmentVariable( "GBE_JOBLIMIT", lBuf );
474
 
475
    for (secs = timeOut; secs > 0; secs--) 
476
    { 
477
 
478
        /*
479
        ** Update ENV with time remaining
480
        ** Simply for diagnostic purposes
481
        */
482
        _snprintf(lBuf, sizeof(lBuf), "%u", secs);
483
        SetEnvironmentVariable( "GBE_JOBTIME", lBuf );
484
 
485
        /*
486
        **  Wait one second
487
        */
488
        Sleep(1000);
489
    }
490
 
491
    fprintf(stderr, "Process exceeded time limit (%u Secs)\n", timeOut);
492
    fflush(stderr) ;
493
 
494
    TerminateJobObject(hJobObject, EXIT_TIMEDOUT);
495
    return 0;
496
}
497
 
498
/*----------------------------------------------------------------------------
499
** FUNCTION           : KillJob
500
**
501
** DESCRIPTION        : Kill the one job controlled by this program
502
**
503
** INPUTS             : dwCtrlType                 - Not used
504
**
505
** RETURNS            : TRUE - Signal has been processed
506
**
507
----------------------------------------------------------------------------*/
508
 
509
BOOL WINAPI KillJob(DWORD dwCtrlType)
510
{
511
    fprintf(stderr, "Process killed with Control-C\n");
512
    fflush(stderr) ;
513
 
514
    TerminateJobObject(hJobObject, EXIT_TIMEDOUT);
515
    return TRUE;
516
}
517