Eric Webb
Eric Webb

Reputation: 335

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!

Upvotes: 1

Views: 264

Answers (2)

Basilevs
Basilevs

Reputation: 23939

I've done the same with Node.js:

// Run: node shim.js

const { spawn } = require('child_process');
const { createWriteStream } = require('fs');

// Start another process and pipe its output to the console and to a log file
// Pass our standard input too
function startProcess(commandAndArguments, log) {
    var child = spawn(commandAndArguments[0], commandAndArguments.slice(1));
    child.stdout.pipe(process.stdout);
    child.stderr.pipe(process.stderr);
    process.stdin.pipe(child.stdin);
    const logStream = createWriteStream(log);
    logStream.write('Starting process: ' + commandAndArguments.join(' ') + '\n');
    child.stdout.on('data', (data) => { logStream.write( "Output: " + data + "\n"); });
    child.stderr.on('data', (data) => { logStream.write( "Error: " + data + "\n"); });
    process.stdin.on('data', (data) => {    logStream.write( "Input: " + data + "\n"); });
}


function agentShim() {
    startProcess("/usr/bin/bc", '/tmp/shim4.log');
}

agentShim();

The advantage (and disadvantage) here is that all IO streams are interleaved, making association of requests and responses easier.

Upvotes: 0

Eric Webb
Eric Webb

Reputation: 335

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.

Upvotes: 0

Related Questions