Subversion Repositories DevTools

Rev

Rev 5742 | Go to most recent revision | Details | Last modification | View Log | RSS feed

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