tags:

views:

300

answers:

5

I'm looking for a way in C to programmatically (ie, not using redirection from the command line) implement 'tee' functionality such that my stdout goes to both stdout and a log file. This needs to work for both my code and all linked libraries that output to stdout. Any way to do this?

+2  A: 

You could popen() the tee program.

Or you can fork() and pipe stdout through a child process such as this (adapted from a real live program I wrote, so it works!):

void tee(const char* fname) {
    int pipe_fd[2];
    check(pipe(pipe_fd));
    const pid_t pid = fork();
    check(pid);
    if(!pid) { // our log child
     close(pipe_fd[1]); // Close unused write end
     FILE* logFile = fname? fopen(fname,"a"): NULL;
     if(fname && !logFile)
      fprintf(stderr,"cannot open log file \"%s\": %d (%s)\n",fname,errno,strerror(errno));
     char ch;
     while(read(pipe_fd[0],&ch,1) > 0) {
      //### any timestamp logic or whatever here
      putchar(ch);
      if(logFile)
       fputc(ch,logFile);
      if('\n'==ch) {
       fflush(stdout);
       if(logFile)
        fflush(logFile);
      }
     }
     putchar('\n');
     close(pipe_fd[0]);
     if(logFile)
      fclose(logFile);
     exit(EXIT_SUCCESS);
    } else {
     close(pipe_fd[0]); // Close unused read end
     // redirect stdout and stderr
     dup2(pipe_fd[1],STDOUT_FILENO);  
     dup2(pipe_fd[1],STDERR_FILENO);  
     close(pipe_fd[1]);  
    }
}
Will
A: 

There's no trivial way of doing this in C. I suspect the easiest would be to call popen(3), with tee as the command and the desired log file as an arument, then dup2(2) the file descriptor of the newly-opened FILE* onto fd 1.

But that looks kinda ugly and I must say that I have NOT tried this.

Vatine
A: 

I'd suggest taking a look at tee.c.

brianegge
This isn't really going to help since the guy doesn't just want to redirect his stdin but wants both his own stdout and that of his libraries to be redirected somewhere.
Benj
A: 

You can use forkpty() with exec() to execute the monitored program with its parameters. forkpty() returns a file descriptor which is redirected to the programs stdin and stdout. Whatever is written to the file descriptor is the input of the program. Whatever is written by the program can be read from the file descriptor.

The second part is to read in a loop the program's output and write it to a file and also print it to stdout.

Example:

pid = forkpty(&fd, NULL, NULL, NULL);
if (pid<0)
    return -1;

if (!pid) /* Child */
{
execl("/bin/ping", "/bin/ping", "-c", "1", "-W", "1", "192.168.3.19", NULL);
}

/* Parent */
waitpid(pid, &status, 0);
return WEXITSTATUS(status);
eyalm
But this means running his program from another program doesn't it? Presumably he wants to avoid this otherwise he'd be willing to just use tee.
Benj
A: 

You could use pipe(2) and dup2(2) to connect your standard out to a file descriptor you can read from. Then you can have a separate thread monitoring that file descriptor, writing everything it gets to a log file and the original stdout (saved avay to another filedescriptor by dup2 before connecting the pipe). But you would need a background thread.

Actually, I think the popen tee method suggested by vatine is probably simpler and safer (as long as you don't need to do anyhing extra with the log file, such as timestamping or encoding or something).

Rasmus Kaj