Search code examples
stdio

How can I log and proxy all stdio to a sub-process?


I have two processes, a parent and a child. The parent execs the child process and uses stdin/out to communicate and control the child process.

I would like to inspect the protocol being used between these two processes by logging all io (stdin, out, err) happening between them.

I have the ability to specify the command the parent process execs to start the sub-process, so my question is:

Is there a command line tool and/or simple C/Java/python program that can "wrap" the sub-command and log (to files) all stdin + stdout while also forwarding all io between them (so the sub-process continues to work?)

Graphically, what I had in mind:

Currently: Parent <-io-> Child

Idea: Parent <-io-> Wrapper/Proxy <-io-> Child

Thanks in advance!


Solution

  • It's not pretty, but it worked. It doesn't shutdown very well, but it allowed me to inspect the communication between a parent and sub-process by wrapping the sub-process command with my own executable:

    Overview:

    • It expects the sub-command (and it's arguments) as the wrappers arguments
    • It sets up 3 pipes to reroute + intercept std{in,out,err}
    • It forks internally a number of times to have a process to consume and log each stdio stream
    • writes out to files stdin.log, stdout.log, stderr.log
    • eventually forks the intended executable, setting up the intercepted stdio pipes

    Usage: ./wrapper /bin/othercommand -a -b -c

    #include <stdio.h>
    #include <unistd.h>
    #include <sys/wait.h>
    #include <signal.h>
    
    int ioin[2];
    int ioout[2];
    int ioerr[2];
    
    void handler(int sig)
    {
        printf("Closing everything!\n");
    
        close(ioin[0]);
        close(ioin[1]);
        close(ioout[0]);
        close(ioout[1]);
        close(ioerr[0]);
        close(ioerr[1]);
    }
    
    int main(int argc, const char * argv[]) {
        pipe(ioin);
        pipe(ioout);
        pipe(ioerr);
    
        // execvp(argv[1], argv+1);
    
        signal(SIGHUP, &handler);
    
        if(fork() == 0)
        {
            close(ioin[0]); // close in read
            close(ioout[1]); // close in write
            close(ioerr[1]); // close in write
    
            if(fork() == 0)
            {
                if(fork() == 0)
                {
                    char buf;
                    FILE* f = fopen("stdin.log", "w+");
                    // fprintf(f, "%d\n", getpid());
                    // fflush(f);
                    while (read(STDIN_FILENO, &buf, 1) > 0) {
                        write(ioin[1], &buf, 1);
                        fwrite(&buf, 1, 1, f);
                        fflush(f);
                    }
                    fprintf(f, "Done\n");
    
                    fclose(f);
                    close(ioin[1]);
                    close(0);
    
                    kill(0, SIGHUP);
    
                    _exit(0);
                }
                else
                {
                    char buf;
                    FILE* f = fopen("stdout.log", "w+");
                    // fprintf(f, "%d\n", getpid());
                    // fflush(f);
                    while (read(ioout[0], &buf, 1) > 0) {
                        write(STDOUT_FILENO, &buf, 1);
                        fwrite(&buf, 1, 1, f);
                        fflush(f);
                    }
                    fprintf(f, "Done\n");
    
                    fclose(f);
                    close(ioout[0]);
                    _exit(0);
                }
            }
            else
            {
                char buf;
                FILE* f = fopen("stderr.log", "w+");
                // fprintf(f, "%d\n", getpid());
                // fflush(f);
                while (read(ioerr[0], &buf, 1) > 0) {
                    write(STDERR_FILENO, &buf, 1);
                    fwrite(&buf, 1, 1, f);
                    fflush(f);
                }
                fprintf(f, "Done\n");
    
    
                fclose(f);
                close(ioerr[0]);
                _exit(0);
            }
        }
        else
        {
            close(ioin[1]); // close in write
            close(ioout[0]); // close in read
            close(ioerr[0]); // close in read
    
            if(fork() == 0)
            {
                close(0);
                dup(ioin[0]);
    
                close(1);
                dup(ioout[1]);
    
                close(2);
                dup(ioerr[1]);
    
                execvp(argv[1], argv+1);
            }
            else
            {
                wait(NULL);
            }
        }
    }
    

    I am happy to accept another answer that is cleaner and/or more correct.