Subversion Repositories DevTools

Rev

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