Subversion Repositories DevTools

Rev

Rev 5742 | Go to most recent revision | Details | Compare with Previous | 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
 
5847 dpurdie 58
HANDLE          hJobObject;             // Job to contain the child process
59
DWORD           dwChildExitCode;        // Child process exit code
60
unsigned int    timeOut = 10;           // Seconds
5741 dpurdie 61
 
62
/*
63
**  Prototypes (forward declarations)
64
*/
65
VOID ErrorExit(LPTSTR);
66
void ErrorExitMsg(UINT ecode, LPTSTR lpszFunction) ;
67
BOOL WINAPI KillJob(DWORD dwCtrlType);
68
unsigned int parseDuration(const char* str);
69
 
70
/*----------------------------------------------------------------------------
71
** FUNCTION           : main
72
**
5847 dpurdie 73
** DESCRIPTION        : Main entry point
74
**                      timeout [-time:nnn] program ......
5741 dpurdie 75
**
76
**
77
** INPUTS             : argc            - Number of args
78
**                      argv            - Address of args
79
**
5847 dpurdie 80
** RETURNS            : 0   - Program exited OK
81
**                      ... - Program exit code
82
**                      124 - Timeout
83
**                      125 - Internal error
84
**                      126 - Could not find program
85
**                      127 - Could not create Job
5741 dpurdie 86
**
87
----------------------------------------------------------------------------*/
88
 
89
int main(int argc, char *argv[])
90
{
91
   LPTSTR   command;
92
   int      argp;
93
 
94
    /*
95
    **  Process command line arguments
96
    **  Options for this program will be located first
97
    */
98
    for ( argp = 1; argp < argc ; argp++ )
99
    {
100
        /*
101
        **  Scan until first non option argument
102
        */
103
        if ( *argv[argp] != '-' )
104
            break;
105
 
106
        if ( _strnicmp( argv[argp], "-TIME:", 6 ) == 0)
107
        {
108
            timeOut = parseDuration(argv[argp] + 6);
109
        }
110
        else
111
        {
112
            fprintf(stderr, "Unknown option: %s", argv[argp] );
113
            ErrorExit( "Bad Option");
114
        }
115
    }
116
 
117
    /*
118
    **  Need at least one more argument - the program to run
119
    */
120
    if ( argp >= argc )
121
    {
122
      ErrorExit("Insufficient arguments");
123
    }
124
 
125
    /*
126
    **  Process the remainder of the command line and create a string
127
    **  For the command line. Some of the arguments will need to be quoted
128
    */
129
    {
130
        size_t length = 0;
131
        int start;
132
        char join = 0;
133
 
134
        /*
135
        **  Calc required size of the command line
136
        **  Allow for two " and 1 space per argument
137
        */
138
        for ( start = argp; start < argc ; start++ )
139
        {
140
            length += 3 + strlen ( argv[start] );
141
        }
142
        command = (LPTSTR) malloc ( length + 1);
143
        if ( !command )
144
            ErrorExit("Cannot allocate memory");
145
        *command = 0;
146
 
147
        for ( start = argp; start < argc ; start++ )
148
        {
149
            char *quote =  strrchr(argv[start], ' ' );
150
 
151
            if ( join )
152
                strcat (command, " " );
153
            join = 1;
154
 
155
            if ( quote )
156
                strcat (command, "\"" );
157
            strcat (command, argv[start] );
158
            if ( quote )
159
                strcat (command, "\"" );
160
        }
161
    }
162
 
163
    /*
164
    ** Create a JOB object to contain our job
165
    */
166
    hJobObject = CreateJobObject(NULL, NULL);
167
    if (! hJobObject)
168
            ErrorExitMsg(EXIT_CANCELED, "CreateJobObject");
169
 
170
    /*
171
    **  Set extended information to allow job breakaway
172
    **  Required so that we can have a timeout job within a timeout job
5742 dpurdie 173
    **  Note: Not sure this works to well on all systems (Windows-7)
5741 dpurdie 174
    */
175
     JOBOBJECT_EXTENDED_LIMIT_INFORMATION JBLI;
176
     if(!QueryInformationJobObject(hJobObject, JobObjectExtendedLimitInformation, &JBLI, sizeof(JBLI), NULL)){
177
        ErrorExitMsg(EXIT_CANCELED, "QueryInformationJobObject");
178
     }
179
 
180
     JBLI.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_BREAKAWAY_OK;
181
     if(!SetInformationJobObject(hJobObject, JobObjectExtendedLimitInformation, &JBLI, sizeof(JBLI))) {
182
        ErrorExitMsg(EXIT_CANCELED, "SetInformationJobObject");      
183
     }
184
 
185
    /*
186
    ** Register a handler for control-C
187
    */
5742 dpurdie 188
    if ( !SetConsoleCtrlHandler( KillJob, TRUE)) {
5741 dpurdie 189
        ErrorExitMsg(EXIT_CANCELED, "SetConsoleCtrlHandler");
190
    }
191
 
192
    /*
5847 dpurdie 193
    **  Create the child process.
5741 dpurdie 194
    */
5742 dpurdie 195
    SetEnvironmentVariable( "GBE_JOBDEBUG", "CreateChild" );
196
    PROCESS_INFORMATION piProcInfo;
197
 
5741 dpurdie 198
    /*
199
    **  Set up members of STARTUPINFO structure.
200
    */
5742 dpurdie 201
    STARTUPINFO siStartInfo; 
5741 dpurdie 202
    ZeroMemory( &siStartInfo, sizeof(STARTUPINFO) );
203
    siStartInfo.cb = sizeof(STARTUPINFO);
204
 
205
    /*
206
    **  Create the child process.
207
    */
208
    if (! CreateProcess(
209
            NULL,                       // Use from command line
210
            command,                    // command line
211
            NULL,                       // process security attributes
212
            NULL,                       // primary thread security attributes 
213
            TRUE,                       // handles are inherited 
214
            CREATE_SUSPENDED | CREATE_NEW_PROCESS_GROUP | CREATE_BREAKAWAY_FROM_JOB,  // creation flags 
215
            //CREATE_BREAKAWAY_FROM_JOB | CREATE_NEW_PROCESS_GROUP ,  // creation flags 
216
            NULL,                       // use parent's environment 
217
            NULL,                       // use parent's current directory 
218
            &siStartInfo,               // STARTUPINFO pointer 
219
            &piProcInfo                 // receives PROCESS_INFORMATION
220
            )) {
221
        ErrorExitMsg(EXIT_CANNOT_INVOKE,"CreateChildProcess");
222
    }
223
 
5742 dpurdie 224
    if (!AssignProcessToJobObject(hJobObject, piProcInfo.hProcess)) {
225
        TerminateProcess(piProcInfo.hProcess, 101);
5741 dpurdie 226
        ErrorExitMsg(EXIT_ENOENT, "AssignProcessToJobObject");
227
    };
228
 
229
    /*
5742 dpurdie 230
    **  Start(Resume) the master thread for the comamnd
5741 dpurdie 231
    **  What for the process to complete
232
    */
5742 dpurdie 233
    SetEnvironmentVariable( "GBE_JOBDEBUG", "ResumeChild" );
234
    if (ResumeThread (piProcInfo.hThread) == -1 ){
235
         ErrorExitMsg(EXIT_ENOENT, "ResumeThread - did not resume");
236
    }
237
 
5847 dpurdie 238
    /*
239
    ** Body of the wating process
240
    */
5742 dpurdie 241
    SetEnvironmentVariable( "GBE_JOBDEBUG", "WaitChild" );
5847 dpurdie 242
    {
243
        char lBuf[30];
244
        unsigned int secs;
245
        DWORD dwWaitResult;
246
        bool  bChildDone = false;
247
 
248
        _snprintf(lBuf, sizeof(lBuf), "%u", timeOut);
249
        SetEnvironmentVariable( "GBE_JOBLIMIT", lBuf );
250
 
251
        /*
252
        ** Wait for the Job to complete, one second at a time 
253
        ** This allows us to indicate progress via an EnvVar 
254
        */
255
        for (secs = timeOut; secs > 0; secs--) 
256
        { 
257
            /*
258
            ** Update ENV with time remaining
259
            ** Simply for diagnostic purposes
260
            */
261
            _snprintf(lBuf, sizeof(lBuf), "%u", secs);
262
            SetEnvironmentVariable( "GBE_JOBTIME", lBuf );
263
 
264
            /*
265
            **  Wait one second
266
            */
267
            dwWaitResult  = WaitForSingleObject( piProcInfo.hProcess, 1000 );
268
            switch (dwWaitResult) 
269
            {
270
                /* 
271
                **  Event object was signaled
272
                **      The process has completed 
273
                */ 
274
                case WAIT_OBJECT_0: 
275
                    SetEnvironmentVariable( "GBE_JOBDEBUG", "ChildDone" );
276
                    bChildDone = true;
277
                    break;
278
 
279
                /* 
280
                **  Timeout
281
                **      Update EnvVar and do again 
282
                */ 
283
                case WAIT_TIMEOUT:
284
                    break;
285
 
286
                /* 
287
                **  An error occurred 
288
                */ 
289
                default: 
290
                    ErrorExitMsg(EXIT_CANCELED, "WaitForSingleObject");
291
                    break;
292
            }
293
        }
294
 
295
        /*
296
        ** Timer loop complete 
297
        ** May be a timeout or the Child has completed 
298
        */
299
        if (!bChildDone)
300
        {
301
            /*
302
            ** Timeout 
303
            **  Terminate the Job 
304
            */
305
            fprintf(stderr, "Process exceeded time limit (%u Secs)\n", timeOut); 
306
            fflush(stderr) ;
307
 
308
            SetEnvironmentVariable( "GBE_JOBDEBUG", "TimeOut" );
309
            TerminateJobObject(hJobObject, EXIT_TIMEDOUT);
310
        }
311
    }
5741 dpurdie 312
 
5847 dpurdie 313
    SetEnvironmentVariable( "GBE_JOBDEBUG", "TimerDone" );
5741 dpurdie 314
    TerminateJobObject(hJobObject, 100);
315
 
316
    /*
317
    **  Determine the exist status of the child process
318
    */
5847 dpurdie 319
    SetEnvironmentVariable( "GBE_JOBDEBUG", "GetChildExitCode" );
5742 dpurdie 320
    if (!GetExitCodeProcess( piProcInfo.hProcess, &dwChildExitCode ))
5741 dpurdie 321
        ErrorExit("Error getting child exist code");
322
 
323
    /*
5742 dpurdie 324
    **  Exit with the childs exit code
5847 dpurdie 325
    **      Have tried to use ExitProcess, but it hangs from time to time
5741 dpurdie 326
    */
5742 dpurdie 327
    SetEnvironmentVariable( "GBE_JOBDEBUG", "Exit" );
5741 dpurdie 328
    ExitProcess(dwChildExitCode);
329
 
330
    /*
331
    ** Should not happen
332
    */
5742 dpurdie 333
    SetEnvironmentVariable( "GBE_JOBDEBUG", "ExitReturn" );
5847 dpurdie 334
    return EXIT_CANCELED;
5741 dpurdie 335
}
336
 
337
/*----------------------------------------------------------------------------
338
** FUNCTION           : apply_time_suffix
339
**
340
** DESCRIPTION        : Helper function to process time suffix
341
**                      Supports s,h,m,d
342
**
343
** INPUTS             : x           - Ref to value to modify
344
**                      suffix_char - Possible suffix char
345
**
346
** RETURNS            : True - suffix was OK
347
**
348
----------------------------------------------------------------------------*/
349
 
350
static int apply_time_suffix (double *x, char suffix_char)
351
{
352
    int multiplier;
353
 
354
    switch (suffix_char)
355
    {
356
    case 0:
357
    case 's':
358
        multiplier = 1;
359
        break;
360
    case 'm':
361
        multiplier = 60;
362
        break;
363
    case 'h':
364
        multiplier = 60 * 60;
365
        break;
366
    case 'd':
367
        multiplier = 60 * 60 * 24;
368
        break;
369
    default:
370
        return 0;
371
    }
372
    *x *= multiplier;
373
    return 1;
374
}
375
 
376
/*----------------------------------------------------------------------------
377
** FUNCTION           : parseDuration 
378
**
379
** DESCRIPTION        : Parse the timeout duration
380
**                      Allowed - number + 1char suffix
381
**                          1 Char suffix may be:
382
**                          'm' - Minutes
383
**                          'h' - Hours
384
**                          'd' - Days
385
**                      The number is an unsigned integer
386
**
387
** INPUTS             : ptr     - Input string (Floating point)
388
**
389
** RETURNS            : Unsigned integer
390
**                      May not return. May exit with error message
391
**
392
----------------------------------------------------------------------------*/
393
 
394
static unsigned int parseDuration (const char* str)
395
{
396
    double duration;
397
    unsigned int intDuration;
398
    unsigned int duration_floor;
399
    char *ep;
400
    int ok = 1;
401
 
402
    errno = 0;
403
    duration = strtod(str, &ep);
404
    if (duration < 0 )
405
        ok = 0;
406
    if ( ep == str )
407
        ok = 0;
408
    if (errno != 0)
409
        ok = 0;
410
    if (*ep && *(ep + 1))
411
        ok = 0;
412
 
413
    if(ok)
414
        ok = apply_time_suffix (&duration, *ep);
415
 
416
    if (UINT_MAX <= duration)
417
        ok = 0;
418
 
419
    if(!ok)
420
    {
421
        ErrorExit( "Invalid duration");
422
    }
423
 
424
    /*
425
    ** Round up - unless exact
426
    */
427
    duration_floor = (unsigned int)duration;
428
    intDuration = duration_floor + (duration_floor < duration);
429
 
430
    /*fprintf(stderr, "parseDuration: %f (%u)\n", duration,  intDuration);*/
431
 
432
  return intDuration;
433
}
434
 
435
/*----------------------------------------------------------------------------
436
** FUNCTION           : ErrorExit
437
**                      ErrorExitMsg - report system messae
438
**
439
** DESCRIPTION        : Error processing
440
**                      REport an error and terminate process
441
**
442
**
443
** INPUTS             : lpszMessage     - Message to display
444
**
445
** RETURNS            : Does't return
446
**                      Will exit with bad code
447
**
448
----------------------------------------------------------------------------*/
449
 
450
VOID ErrorExit (LPTSTR lpszMessage)
451
{ 
5847 dpurdie 452
   SetEnvironmentVariable( "GBE_JOBDEBUG", lpszMessage );
5741 dpurdie 453
   fprintf(stderr, "%s\n", lpszMessage);
454
   fflush(stderr) ;
455
   ExitProcess(EXIT_CANCELED);
456
}
457
 
458
void ErrorExitMsg(UINT ecode, LPTSTR lpszFunction) 
459
{ 
460
    // Retrieve the system error message for the last-error code
461
 
462
    LPVOID lpMsgBuf;
5847 dpurdie 463
    DWORD dw = GetLastError();
464
    char msgBuf[300]; 
5741 dpurdie 465
 
466
    FormatMessage(
467
        FORMAT_MESSAGE_ALLOCATE_BUFFER | 
468
        FORMAT_MESSAGE_FROM_SYSTEM |
469
        FORMAT_MESSAGE_IGNORE_INSERTS,
470
        NULL,
471
        dw,
472
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
473
        (LPTSTR) &lpMsgBuf,
474
        0, NULL );
475
 
476
    // Display the error message and exit the process
477
 
5847 dpurdie 478
    _snprintf(msgBuf, sizeof(msgBuf),  "%s failed with error %d: %s\n", lpszFunction, dw, lpMsgBuf);
479
    SetEnvironmentVariable( "GBE_JOBDEBUG", msgBuf );
480
    fprintf(stderr, msgBuf);
5741 dpurdie 481
    fflush(stderr) ;
482
    LocalFree(lpMsgBuf);
483
 
484
    ExitProcess(ecode); 
485
}
486
 
487
/*----------------------------------------------------------------------------
488
** FUNCTION           : ThreadTimer
489
**
490
** DESCRIPTION        : Thread to timeout the created process
491
**                      Done on a thread so that we can sleep
492
**
493
** INPUTS             : lpParam                 - Not used
494
**
495
** RETURNS            : 0
496
**
497
----------------------------------------------------------------------------*/
498
 
499
DWORD WINAPI ThreadTimer( LPVOID lpParam )
500
{
501
    char lBuf[30];
502
    unsigned int secs;
503
 
504
    _snprintf(lBuf, sizeof(lBuf), "%u", timeOut);
505
    SetEnvironmentVariable( "GBE_JOBLIMIT", lBuf );
506
 
507
    for (secs = timeOut; secs > 0; secs--) 
508
    { 
509
 
510
        /*
511
        ** Update ENV with time remaining
512
        ** Simply for diagnostic purposes
513
        */
514
        _snprintf(lBuf, sizeof(lBuf), "%u", secs);
515
        SetEnvironmentVariable( "GBE_JOBTIME", lBuf );
516
 
517
        /*
518
        **  Wait one second
519
        */
520
        Sleep(1000);
521
    }
522
 
523
    fprintf(stderr, "Process exceeded time limit (%u Secs)\n", timeOut);
524
    fflush(stderr) ;
525
 
526
    TerminateJobObject(hJobObject, EXIT_TIMEDOUT);
527
    return 0;
528
}
529
 
530
/*----------------------------------------------------------------------------
531
** FUNCTION           : KillJob
532
**
533
** DESCRIPTION        : Kill the one job controlled by this program
534
**
535
** INPUTS             : dwCtrlType                 - Not used
536
**
537
** RETURNS            : TRUE - Signal has been processed
538
**
539
----------------------------------------------------------------------------*/
540
 
541
BOOL WINAPI KillJob(DWORD dwCtrlType)
542
{
543
    fprintf(stderr, "Process killed with Control-C\n");
544
    fflush(stderr) ;
545
 
546
    TerminateJobObject(hJobObject, EXIT_TIMEDOUT);
547
    return TRUE;
548
}
549