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