Subversion Repositories DevTools

Rev

Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

/*============================================================================
**
**    ERG TRANSIT SYSTEMS      Licensed software
**    (C) 2008                 All rights reserved
**
**============================================================================
**
**  Project/Product : 
**  Filename        : pipe.c
**  Author(s)       : DDP
**
**  Description     : Unix program to
**                      Create an other process
**                      Monitor its stdout/stderr
**                      Tag data with STDOUT or STDERR
**                      Write it out my STDOUT
**
**                    Used to help Perl monitor the status of
**                    complex processes. Current Perl IPC has
**                    problems monitoring both STDOUT and STDERR
**                    in cases where the output s non-trivial
**
**                    Only a problem for WIN32 under Perl as Open3
**                    cannot be used to read both streams - with complete
**                    sucess
**
** Usage            : stdmux <options>* command <command args>*
**                    Where options are:
**                      -out:<text>     - Text to prefix stdout. Default is "STDOUT:"
**                      -err:<text>     - Text to prefix stderr. Default is "STDERR:"
**                    Command           - Users command
**                    command args      - Arguments passed to the command
**
**  Information     :
**   Compiler       : ANSI C
**   Target         : UNIX ONLY
**
**
**==========================================================================*/

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/wait.h>
#include "string.h"
#include "strings.h"

#define MAX_LINE    1024                /* Limit of user lines */

/*
**  A structure to contain the data from stdin and stdout before
**  it is printed to stdout, with a prefix
*/
typedef struct proc {
    char    *name;              /* Prefix to add */
    size_t  name_len;           /* Length of *name */
    char    *ptr;               /* Current pointer */
    char    *buf;               /* Buffer */
    size_t  size;               /* Sise of above */
} proc;

char buf[MAX_LINE];
char buf1[MAX_LINE];
char buf2[MAX_LINE];

proc    line_out = {"STDOUT:", 7, buf1, buf1, sizeof(buf1) } ;
proc    line_err = {"STDERR:", 7, buf2, buf2, sizeof(buf2) } ;

void proc_data( char *buf, size_t nread, proc *p );
void flush_data( proc *p );

int main (int argc, char **argv)
{
    int     filedes1[2], filedes2[2], filedes3[2];
    int     pid;
    fd_set  fds;
    int     maxfd;
    int     nready;
    int     stderr_isopen;
    int     stdout_isopen;
    int     argp;

    /*
    **  Process command line arguments
    **  Options for this program will be located first
    */
    for ( argp = 1; argp < argc ; argp++ )
    {
        /*
        **  Scan until first non option argument
        */
        if ( *argv[argp] != '-' )
            break;

        if ( strncasecmp( argv[argp], "-OUT:", 5 ) == 0)
        {
            line_out.name = argv[argp] + 5;
        }
        else if ( strncasecmp( argv[argp], "-ERR:", 5 ) == 0)
        {
            line_err.name = argv[argp] + 5;
        }
        else
        {
            fprintf(stderr, "Unknown option: %s\n", argv[argp] );
            exit(-1);
        }
    }

    /*
    **  Fixup the string lenghts
    */
    line_out.name_len = strlen( line_out.name);
    line_err.name_len = strlen( line_err.name);

    /*
    **  Need at least one more argument - the programto run
    */
    if ( argp >= argc )
    {
      fputs("Insufficient number of arguments given\n", stderr);
      fputs("Need at least the name of the program\n", stderr);
      exit(-1);
    }

  
  /* Argument checking. */

  if (argc <= 1)
    {
      fputs("Insufficient number of arguments given\n", stderr);
      fputs("Need at least the name of the program\n", stderr);
      exit(-1);
    }

  /* Make our pipes. */

  if (pipe(filedes1) == -1)
    {
      perror ("pipe");
      exit(-1);
    }

  if (pipe(filedes2) == -1)
    {
      perror ("pipe");
      exit(-1);
    }

  if (pipe(filedes3) == -1)
    {
      perror ("pipe");
      exit(-1);
    }
    

  /* Fork a child */

    if ((pid = fork()) == 0)
    {
        /*
        **    Child
        */
        dup2(filedes1[0], fileno(stdin));     /* Copy the reading end of the pipe. */
        dup2(filedes2[1], fileno(stdout));    /* Copy the writing end of the pipe */
        dup2(filedes3[1], fileno(stderr));    /* Copy the writing end of the pipe */

        close( filedes1[1] );
        close( filedes2[0] );
        close( filedes3[0] );

         /* If execv() returns at all, there was an error. */

        if (execv(argv[argp], &argv[argp]) == -1)
        {
            perror("execv");
            exit(128);
        }
    }
    else if (pid == -1)
    {
        perror("fork");
        exit(128);
    }
    else
    {
        /*
        **    Parent
        */
        
        int     stat_loc;
        pid_t   result;
        close( filedes1[0] );
        close( filedes2[1] );
        close( filedes3[1] );

        /*
        **  Read from childs STDERR and STDOUT as the data becomes
        **  available
        **
        **  Prefex the data with an indication of the stream that
        **  it came from
        */

        /*
        ** Set up the select information
        */
        maxfd = (filedes2[0] > filedes3[0] ? filedes2[0] : filedes3[0]) + 1;

        stdout_isopen = 1;
        stderr_isopen = 1;
        while(stdout_isopen || stderr_isopen)
        {

                /* Set up polling using select. */
                    FD_ZERO(&fds);
                    if (stdout_isopen)
                FD_SET(filedes2[0],&fds);
                    if (stderr_isopen)
                FD_SET(filedes3[0],&fds);
        
                    /* Wait for some input. */
                    nready = select(maxfd, &fds, (fd_set *) 0, (fd_set *) 0, (struct timeval *) 0);
            if ( nready < 0 )
            {
                break;
            }
                    /* If either descriptor has some input, read it */

                    if( FD_ISSET(filedes2[0], &fds))
                    {
                            int nread = read(filedes2[0], buf, sizeof(buf));
                            /* If error or eof, terminate. */
                            if(nread < 1){
                            stdout_isopen = 0;
                            }
                proc_data( buf, nread, &line_out);
                    }

                    if( FD_ISSET(filedes3[0], &fds))
                    {
                            int nread = read(filedes3[0], buf, sizeof(buf));
                            /* If error or eof, terminate. */
                            if(nread < 1){
                            stderr_isopen = 0;
                            }
                proc_data( buf, nread, &line_err );
                    }
        }

        flush_data( &line_out);
        flush_data( &line_err);

        /*
        **  Determine child exit status
        */
        result = waitpid(pid, &stat_loc, 0 );
        if ( result == pid )
        {
            if ( WIFEXITED(stat_loc)  )
                exit( WEXITSTATUS(stat_loc));
        }
    }

    /*
    **  Bad pid (and keep compiler happy)
    */
    return (-1);
}

/*----------------------------------------------------------------------------
** FUNCTION           : proc_data
**
** DESCRIPTION        : Multiplex data to stdout
**                      Prefix each line with a 'TAG' that indicates where
**                      it came from.
**
**
** INPUTS             : buf                     - Data to process
**                      nread                   - Bytes of data
**                      p                       - p structure
**
** RETURNS            : Nothing
**
----------------------------------------------------------------------------*/

void proc_data( char *buf, size_t nread, proc *p )
{
    while ( nread-- )
    {
        char ch = *buf++;

        if ( ch == '\r' )
            continue;

        if ( ch == '\n' )
        {
            flush_data(p);
            continue;
        }

        *p->ptr++ = ch;
        if ( (size_t)(p->ptr - p->buf) >= p->size - 2  )
        {
            flush_data(p);
            continue;
        }
    }
}

/*----------------------------------------------------------------------------
** FUNCTION           : flush_data
**
** DESCRIPTION        : Write a complete line to the stdout
**                      Needs to be atomic write
**
**
** INPUTS             : p               - Ref to proc structure
**
** RETURNS            : Nothing
**
----------------------------------------------------------------------------*/

void flush_data( proc *p )
{
    if ( p->ptr != p->buf )
    {
        *p->ptr++ = '\n';
        *p->ptr = 0;

        if ( p->name_len )
            fputs(p->name, stdout);
        fputs(p->buf, stdout);
        p->ptr = p->buf;
    }
}