Subversion Repositories DevTools

Rev

Rev 267 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
267 dpurdie 1
/*============================================================================
2
**
3
**    ERG TRANSIT SYSTEMS      Licensed software
4
**    (C) 2008                 All rights reserved
5
**
6
**============================================================================
7
**
8
**  Project/Product : 
9
**  Filename        : pipe_win32.c
10
**  Author(s)       : DDP
11
**
12
**  Description     : Windows program to
13
**                      Create an other process
14
**                      Monitor its stdout/stderr
15
**                      Tag data with STDOUT or STDERR
16
**                      Write it out my STDOUT
17
**
18
**                    Used to help Perl monitor the status of
19
**                    complex processes. Current Perl IPC has
20
**                    problems monitoring both STDOUT and STDERR
21
**                    in cases where the output s non-trivial
22
**
23
**                    Only a problem for WIN32 under Perl as Open3
24
**                    cannot be used to read both streams - with complete
25
**                    sucess
26
**
27
** Usage            : stdmux <options>* command <command args>*
28
**                    Where options are:
29
**                      -out:<text>     - Text to prefix stdout. Default is "STDOUT:"
30
**                      -err:<text>     - Text to prefix stderr. Default is "STDERR:"
31
**                    Command           - Users command
32
**                    command args      - Arguments passed to the command
33
**
34
**  Information     :
35
**   Compiler       : ANSI C
36
**   Target         : WIN32 ONLY
37
**
38
**
39
**==========================================================================*/
40
 
41
 
42
#include <stdio.h>
43
#include <windows.h> 
44
 
45
#define BUFSIZE     4096
46
#define IBUFSIZE    4096
47
#define MAX_LINE    1024                // Limit of user lines
48
 
49
HANDLE  hChildStdinRd, hChildStdinWr, hChildStdinWrDup,
50
        hChildStdoutRd, hChildStdoutWr, hChildStdoutRdDup,
51
        hChildStderrRd, hChildStderrWr, hChildStderrRdDup,
52
        hInputFile, hSaveStdin, hSaveStdout, hSaveStderr;
53
CRITICAL_SECTION CriticalSection;
54
DWORD dwThreadIdOut, dwThreadIdErr;
55
HANDLE hThreadOut, hThreadErr;
56
DWORD   dwChildExitCode;
57
HANDLE  hChildProcess;
58
 
59
/*
60
**  Prototypes (forward declarations)
61
*/
62
HANDLE CreateChildProcess(LPTSTR);
63
VOID WriteToPipe(VOID); 
64
VOID ErrorExit(LPTSTR);
65
VOID ErrMsg(LPTSTR, BOOL); 
66
DWORD WINAPI ThreadReadStdOut( LPVOID lpParam );
67
DWORD WINAPI ThreadReadStdErr( LPVOID lpParam );
68
 
69
/*
70
**  A structure to contain the data from stdin and stdout before
71
**  it is printed to stdout, with a prefix
72
*/
73
typedef struct proc {
74
    char    *name;              /* Prefix to add */
75
    size_t  name_len;           /* Length of *name */
76
    char    *ptr;               /* Current pointer */
77
    char    *buf;               /* Buffer */
78
    size_t  size;               /* Size of above */
79
} proc;
80
 
81
char buf1[MAX_LINE];
82
char buf2[MAX_LINE];
83
 
84
proc    line_out = {"STDOUT:", 7, buf1, buf1, sizeof(buf1) } ;
85
proc    line_err = {"STDERR:", 7, buf2, buf2, sizeof(buf2) } ;
86
 
87
void proc_data( char *buf, size_t nread, proc *p );
88
void flush_data( proc *p );
89
 
90
/*----------------------------------------------------------------------------
91
** FUNCTION           : main
92
**
93
** DESCRIPTION        : Mainentry point
94
**
95
**
96
** INPUTS             : argc            - Number of args
97
**                      argv            - Address of args
98
**
99
** RETURNS            :
100
**
101
----------------------------------------------------------------------------*/
102
 
103
int main(int argc, char *argv[])
104
{
105
   SECURITY_ATTRIBUTES saAttr; 
106
   BOOL     fSuccess;
107
   LPTSTR   command;
108
   int      argp;
109
 
110
    /*
111
    **  Process command line arguments
112
    **  Options for this program will be located first
113
    */
114
    for ( argp = 1; argp < argc ; argp++ )
115
    {
116
        /*
117
        **  Scan until first non option argument
118
        */
119
        if ( *argv[argp] != '-' )
120
            break;
121
 
122
        if ( _strnicmp( argv[argp], "-OUT:", 5 ) == 0)
123
        {
124
            line_out.name = argv[argp] + 5;
125
        }
126
        else if ( _strnicmp( argv[argp], "-ERR:", 5 ) == 0)
127
        {
128
            line_err.name = argv[argp] + 5;
129
        }
130
        else
131
        {
132
            fprintf(stderr, "Unknown option: %s\n", argv[argp] );
133
            ErrorExit( "Bad Option\n");
134
        }
135
    }
136
 
137
    /*
138
    **  Fixup the string lenghts
139
    */
140
    line_out.name_len = strlen( line_out.name);
141
    line_err.name_len = strlen( line_err.name);
142
 
143
    /*
144
    **  Need at least one more argument - the programto run
145
    */
146
    if ( argp >= argc )
147
    {
148
      ErrorExit("Insufficient arguments\n");
149
    }
150
 
151
    /*
152
    **  Process the remainder of the command line and create a string
153
    **  For the command line. Some of the arguments will need to be quoted
154
    */
155
    {
156
        int length = 0;
157
        int start;
158
        char join = 0;
159
 
160
        /*
161
        **  Calc required size of the command line
162
        **  Allow for two " and 1 space per argument
163
        */
164
        for ( start = argp; start < argc ; start++ )
165
        {
166
            length += 3 + strlen ( argv[start] );
167
        }
168
        command = malloc ( length + 1);
169
        if ( !command )
170
            ErrorExit("Cannot allocate memory\n");
171
        *command = 0;
172
 
173
        for ( start = argp; start < argc ; start++ )
174
        {
175
            char *quote =  strrchr(argv[start], ' ' );
176
 
177
            if ( join )
178
                strcat (command, " " );
179
            join = 1;
180
 
181
            if ( quote )
182
                strcat (command, "\"" );
183
            strcat (command, argv[start] );
184
            if ( quote )
185
                strcat (command, "\"" );
186
        }
187
    }
188
 
189
    /*
190
    **  Create a security attribute
191
    **  Set the bInheritHandle flag so pipe handles are inherited
192
    */
193
    saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
194
    saAttr.bInheritHandle = TRUE; 
195
    saAttr.lpSecurityDescriptor = NULL; 
196
 
197
   // The steps for redirecting child process's STDOUT and STDERR:
198
   //     1. Save current STDOUT, to be restored later. 
199
   //     2. Create anonymous pipe to be STDOUT for child process. 
200
   //     3. Set STDOUT of the parent process to be write handle to 
201
   //        the pipe, so it is inherited by the child process. 
202
   //     4. Create a noninheritable duplicate of the read handle and
203
   //        close the inheritable read handle. 
204
 
205
// Save the handle to the current STDOUT. 
206
 
207
   hSaveStdout = GetStdHandle(STD_OUTPUT_HANDLE);
208
   hSaveStderr = GetStdHandle(STD_ERROR_HANDLE);
209
 
210
// Create a pipe for the child process's STDOUT. 
211
 
212
   if (! CreatePipe(&hChildStdoutRd, &hChildStdoutWr, &saAttr, 0)) 
213
      ErrorExit("Stdout pipe creation failed\n");
214
 
215
   if (! CreatePipe(&hChildStderrRd, &hChildStderrWr, &saAttr, 0))
216
      ErrorExit("Stderr pipe creation failed\n");
217
 
218
 
219
// Set a write handle to the pipe to be STDOUT. 
220
 
221
   if (! SetStdHandle(STD_OUTPUT_HANDLE, hChildStdoutWr)) 
222
      ErrorExit("Redirecting STDOUT failed"); 
223
 
224
   if (! SetStdHandle(STD_ERROR_HANDLE, hChildStderrWr))
225
      ErrorExit("Redirecting STDERR failed");
226
 
227
// Create noninheritable read handle and close the inheritable read 
228
// handle. 
229
 
230
    fSuccess = DuplicateHandle(GetCurrentProcess(), hChildStdoutRd,
231
        GetCurrentProcess(), &hChildStdoutRdDup , 0,
232
        FALSE,
233
        DUPLICATE_SAME_ACCESS);
234
    if( !fSuccess )
235
        ErrorExit("DuplicateHandle failed");
236
    CloseHandle(hChildStdoutRd);
237
 
238
    fSuccess = DuplicateHandle(GetCurrentProcess(), hChildStderrRd,
239
        GetCurrentProcess(), &hChildStderrRdDup , 0,
240
        FALSE,
241
        DUPLICATE_SAME_ACCESS);
242
    if( !fSuccess )
243
        ErrorExit("DuplicateHandle failed");
244
    CloseHandle(hChildStderrRd);
245
 
246
#if 0
247
   // The steps for redirecting child process's STDIN: 
248
   //     1.  Save current STDIN, to be restored later. 
249
   //     2.  Create anonymous pipe to be STDIN for child process. 
250
   //     3.  Set STDIN of the parent to be the read handle to the 
251
   //         pipe, so it is inherited by the child process. 
252
   //     4.  Create a noninheritable duplicate of the write handle, 
253
   //         and close the inheritable write handle. 
254
 
255
// Save the handle to the current STDIN. 
256
 
257
   hSaveStdin = GetStdHandle(STD_INPUT_HANDLE); 
258
 
259
// Create a pipe for the child process's STDIN. 
260
 
261
   if (! CreatePipe(&hChildStdinRd, &hChildStdinWr, &saAttr, 0)) 
262
      ErrorExit("Stdin pipe creation failed\n"); 
263
 
264
// Set a read handle to the pipe to be STDIN. 
265
 
266
   if (! SetStdHandle(STD_INPUT_HANDLE, hChildStdinRd)) 
267
      ErrorExit("Redirecting Stdin failed"); 
268
 
269
// Duplicate the write handle to the pipe so it is not inherited. 
270
 
271
   fSuccess = DuplicateHandle(GetCurrentProcess(), hChildStdinWr, 
272
      GetCurrentProcess(), &hChildStdinWrDup, 0, 
273
      FALSE,                  // not inherited 
274
      DUPLICATE_SAME_ACCESS); 
275
   if (! fSuccess) 
276
      ErrorExit("DuplicateHandle failed"); 
277
 
278
   CloseHandle(hChildStdinWr);
279
#endif
280
 
281
    /*
282
    **  Now create the child process.
283
    */
284
    hChildProcess = CreateChildProcess(command);
285
    if (! hChildProcess)
286
        ErrorExit("Create process failed");
287
 
288
    /*
289
    **  Restore the saved STDIN and STDOUT and STDERR.
290
    */
291
    if (! SetStdHandle(STD_INPUT_HANDLE, hSaveStdin))
292
        ErrorExit("Re-redirecting Stdin failed\n");
293
 
294
   if (! SetStdHandle(STD_OUTPUT_HANDLE, hSaveStdout)) 
295
      ErrorExit("Re-redirecting Stdout failed\n");
296
 
297
   if (! SetStdHandle(STD_ERROR_HANDLE, hSaveStderr))
298
      ErrorExit("Re-redirecting Stderr failed\n");
299
 
300
 
301
//// Get a handle to the parent's input file.
302
//
303
//   if (argc > 1)
304
//      hInputFile = CreateFile(argv[1], GENERIC_READ, 0, NULL,
305
//         OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL);
306
//   else
307
//      hInputFile = hSaveStdin;
308
//
309
//   if (hInputFile == INVALID_HANDLE_VALUE)
310
//      ErrorExit("no input file\n");
311
//
312
//// Write to pipe that is the standard input for a child process.
313
//
314
//   WriteToPipe();
315
 
316
// Read from pipe that is the standard output for child process. 
317
 
318
 
319
    /*
320
    **  Create a criyical section to be used by pipe writers
321
    */
322
    InitializeCriticalSection(&CriticalSection);
323
 
324
    /*
325
    **  Create two thread to read the processes stdout and stderr
326
    **  pipes
327
    */
328
    hThreadOut = CreateThread(
329
        NULL,                        // default security attributes 
330
        0,                           // use default stack size  
331
        ThreadReadStdOut,            // thread function
332
        NULL,                        // argument to thread function
333
        0,                           // use default creation flags 
334
        &dwThreadIdOut);             // returns the thread identifier
335
 
336
    if (hThreadOut == NULL)
337
        ErrorExit("Error Creating thread ReadOut\n");
338
 
339
    hThreadErr = CreateThread(
340
        NULL,                        // default security attributes 
341
        0,                           // use default stack size  
342
        ThreadReadStdErr,            // thread function
343
        NULL,                        // argument to thread function
344
        0,                           // use default creation flags 
345
        &dwThreadIdErr);                // returns the thread identifier
346
 
347
    if (hThreadErr == NULL)
348
        ErrorExit("Error Creating thread ReadErr\n");
349
 
350
    /*
351
    **  What for the threads to complete
352
    */
353
    WaitForSingleObject ( hThreadOut, INFINITE );
354
    WaitForSingleObject ( hThreadErr, INFINITE );
355
 
356
    /*
357
    **  Determine the exist status of the child process
358
    */
359
    if (!GetExitCodeProcess( hChildProcess, &dwChildExitCode ))
360
        ErrorExit("Error getting child exist coder\n");
361
 
362
    /*
363
    **  Release resources used by the critical section object.
364
    */
365
    DeleteCriticalSection(&CriticalSection);
366
 
367
    /*
368
    **  Exist with the childs exist code
369
    */
370
    ExitProcess(dwChildExitCode);
371
    return 1;
372
}
373
 
374
/*----------------------------------------------------------------------------
375
** FUNCTION           : CreateChildProcess
376
**
377
** DESCRIPTION        : Craete the child process
378
**
379
**
380
** INPUTS             : command         - Command string
381
**
382
** RETURNS            : False if can't create
383
**
384
----------------------------------------------------------------------------*/
385
 
386
HANDLE CreateChildProcess( LPTSTR command)
387
{ 
388
   PROCESS_INFORMATION piProcInfo; 
389
   STARTUPINFO siStartInfo; 
390
 
391
    /*
392
    **  Set up members of STARTUPINFO structure.
393
    */
394
    ZeroMemory( &siStartInfo, sizeof(STARTUPINFO) );
395
    siStartInfo.cb = sizeof(STARTUPINFO);
396
 
397
    /*
398
    **  Create the child process.
399
    */
400
    if (! CreateProcess(
401
            NULL,          // Use from command line
402
            command,       // command line
403
            NULL,          // process security attributes
404
            NULL,          // primary thread security attributes 
405
            TRUE,          // handles are inherited 
406
            0,             // creation flags 
407
            NULL,          // use parent's environment 
408
            NULL,          // use parent's current directory 
409
            &siStartInfo,  // STARTUPINFO pointer 
410
            &piProcInfo   // receives PROCESS_INFORMATION
411
            )) return (NULL);
412
 
413
    return piProcInfo.hProcess;
414
}
415
 
416
/*----------------------------------------------------------------------------
417
** FUNCTION           : WriteToPipe
418
**
419
** DESCRIPTION        : Write data to processes stdin
420
**                      Not used (yet)
421
**
422
**
423
** INPUTS             :
424
**
425
** RETURNS            :
426
**
427
----------------------------------------------------------------------------*/
428
 
429
VOID WriteToPipe(VOID)
430
{ 
431
   DWORD dwRead, dwWritten; 
432
   CHAR chBuf[BUFSIZE]; 
433
 
434
// Read from a file and write its contents to a pipe. 
435
 
436
   for (;;) 
437
   { 
438
      if (! ReadFile(hInputFile, chBuf, BUFSIZE, &dwRead, NULL) || 
439
         dwRead == 0) break; 
440
      if (! WriteFile(hChildStdinWrDup, chBuf, dwRead, 
441
         &dwWritten, NULL)) break; 
442
   } 
443
 
444
// Close the pipe handle so the child process stops reading. 
445
 
446
   if (! CloseHandle(hChildStdinWrDup)) 
447
      ErrorExit("Close pipe failed\n"); 
448
} 
449
 
450
 
451
/*----------------------------------------------------------------------------
452
** FUNCTION           : ErrorExit
453
**
454
** DESCRIPTION        : Error processing
455
**                      REport an error and terminate process
456
**
457
**
458
** INPUTS             : lpszMessage     - Message to display
459
**
460
** RETURNS            : Does't return
461
**                      Will exit with bad code
462
**
463
----------------------------------------------------------------------------*/
464
 
465
VOID ErrorExit (LPTSTR lpszMessage)
466
{ 
467
   fprintf(stderr, "%s\n", lpszMessage);
468
   fflush(stderr) ;
469
   ExitProcess(-1);
470
} 
471
 
472
/*----------------------------------------------------------------------------
473
** FUNCTION           : ThreadReadStdOut
474
**
475
** DESCRIPTION        : Thread to read child's stdout and re-badge the data
476
**                      Done on a thread so that we can handle both stdout
477
**                      and stderr
478
**
479
** INPUTS             : lpParam                 - Not used
480
**
481
** RETURNS            : 0
482
**
483
----------------------------------------------------------------------------*/
484
 
485
DWORD WINAPI ThreadReadStdOut( LPVOID lpParam )
486
{
487
   DWORD dwRead;
488
   CHAR chBuf[IBUFSIZE];
489
   HANDLE hStdout = GetStdHandle(STD_OUTPUT_HANDLE); 
490
 
491
   /*
492
   **   Close the write end of the pipe before reading from the
493
   **   read end of the pipe.
494
   */
495
   if (!CloseHandle(hChildStdoutWr))
496
      ErrorExit("Closing handle failed"); 
497
 
498
    for (;;) 
499
    { 
500
        if( !ReadFile( hChildStdoutRdDup, chBuf, IBUFSIZE, &dwRead,
501
            NULL) || dwRead == 0)
502
                break;
503
 
504
        proc_data( chBuf, dwRead, &line_out);
505
   }
506
    flush_data( &line_out);
507
    return 0;
508
}
509
 
510
DWORD WINAPI ThreadReadStdErr( LPVOID lpParam )
511
{
512
   DWORD dwRead;
513
   CHAR chBuf[IBUFSIZE];
514
   HANDLE hStderr = GetStdHandle(STD_ERROR_HANDLE);
515
 
516
   /*
517
   **   Close the write end of the pipe before reading from the
518
   **   read end of the pipe.
519
   */
520
   if (!CloseHandle(hChildStderrWr))
521
      ErrorExit("Closing err handle failed");
522
 
523
   for (;;)
524
   { 
525
      if( !ReadFile( hChildStderrRdDup, chBuf, IBUFSIZE, &dwRead,
526
         NULL) || dwRead == 0) break;
527
        proc_data( chBuf, dwRead, &line_err);
528
   } 
529
    flush_data( &line_err);
530
    return 0;
531
}
532
 
533
/*----------------------------------------------------------------------------
534
** FUNCTION           : proc_data
535
**
536
** DESCRIPTION        : Multiplex data to stdout
537
**                      Prefix each line with a 'TAG' that indicates where
538
**                      it came from.
539
**
540
**
541
** INPUTS             : buf                     - Data to process
542
**                      nread                   - Bytes of data
543
**                      p                       - p structure
544
**
545
** RETURNS            : Nothing
546
**
547
----------------------------------------------------------------------------*/
548
 
549
void proc_data( char *buf, size_t nread, proc *p )
550
{
551
    while ( nread-- )
552
    {
553
        char ch = *buf++;
554
 
555
        if ( ch == '\r' )
556
            continue;
557
 
558
        if ( ch == '\n' )
559
        {
560
            flush_data(p);
561
            continue;
562
        }
563
 
564
        *p->ptr++ = ch;
565
        if ( (size_t)(p->ptr - p->buf) >= p->size - 1  )
566
        {
567
            flush_data(p);
568
            continue;
569
        }
570
    }
571
}
572
 
573
/*----------------------------------------------------------------------------
574
** FUNCTION           : flush_data
575
**
576
** DESCRIPTION        : Write a complete line to the stdout
577
**                      Needs to be atomic write
578
**
579
**
580
** INPUTS             : p               - Ref to proc structure
581
**
582
** RETURNS            : Nothing
583
**
584
----------------------------------------------------------------------------*/
585
void flush_data( proc *p )
586
{
587
    DWORD dwWritten;
588
    if ( p->ptr != p->buf )
589
    {
590
        *p->ptr++ = '\n';
591
 
592
        EnterCriticalSection(&CriticalSection);
593
        WriteFile(hSaveStdout, p->name, p->name_len, &dwWritten, NULL);
594
        WriteFile(hSaveStdout, p->buf, p->ptr - p->buf, &dwWritten, NULL);
595
        LeaveCriticalSection(&CriticalSection);
596
 
597
        p->ptr = p->buf;
598
    }
599
}
600