Duplicating output to a file
August 4th, 2009I spent some time today trying to figure out the best way to duplicate stdout and stderr to a file. Note that I don’t mean redirect, but rather duplicate.
The solution is relatively simple, but the code is a little more difficult. Basically, the process needs to fork a child process to handle writing to stdout and the file. The tee command already handles some of this, so the simplest solution is using tee with popen:
FILE *piped = popen("/usr/bin/tee -a /tmp/out.txt", "w"); // use tee for duplication
dup2(fileno(piped), STDOUT_FILENO);
dup2(fileno(piped), STDERR_FILENO);
setvbuf(stdout, NULL, _IOLBF, 0); // line buffered
setvbuf(stderr, NULL, _IONBF, 0); // no buffer
printf("duplicated stdout\n");
fprintf(stderr, "duplicated stderr\n");
fflush(stdout); // flush output
fflush(stderr);
pclose(pipe);
What I don’t like about this solution is that popen doesn’t give you much flexibility with the command that’s being run. Therefore, you could end up having to quote your file name to pass off to the shell appropriately. I always avoid this when I can. That led me to a more complex solution, but far more general. Here’s an example of it being used (the brackets are there just to better display groups of where output is going):
int main() {
{
duplicate_t *a = duplicate("a.txt");
printf("duplicated stdout\n");
fprintf(stderr, "duplicated stderr\n");
{
duplicate_t *b = duplicate("b.txt");
fprintf(stderr, "duplicated stderr 2\n");
unduplicate(b);
}
unduplicate(a);
}
printf("only stdout\n");
fprintf(stderr, "only stderr\n");
}
Basically, this will write 3 lines to a.txt, 1 line to b.txt, and all 6 lines will still be sent to the console. Calls to duplicate obviously need to be bracketed by a call to unduplicate (except in the case where you want output to continue to be duplicated forever).
Below is the code that implements duplicate and unduplicate. The EXEC_TEE macro can be changed based on whether you want to use tee to handle duplicating the output or not (I’d likely recommend not).
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#define EXEC_TEE 0
#define READ_END 0
#define WRITE_END 1
typedef struct duplicate {
pid_t pid;
int stdout;
int stderr;
} duplicate_t;
duplicate_t *duplicate(char *to) {
int fds[2];
if (pipe(fds) != 0) { return NULL; }
duplicate_t *info = malloc(sizeof(duplicate_t));
if ((info->pid = fork()) == 0) { // child
close(fds[WRITE_END]);
#ifdef EXEC_TEE
dup2(fds[READ_END], STDIN_FILENO);
execl("/usr/bin/tee", "tee", "-a", to, NULL);
exit(-1);
#else
int amt;
int file = open(to, O_APPEND | O_WRONLY | O_CREAT);
char buffer[1024];
while ((amt = read(fds[READ_END], buffer, sizeof(buffer))) != 0) {
write(STDOUT_FILENO, buffer, amt);
write(file, buffer, amt);
}
fchmod(file, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
close(file);
exit(0);
#endif
} else { // parent
close(fds[READ_END]);
info->stdout = dup(STDOUT_FILENO);
info->stderr = dup(STDERR_FILENO);
if (dup2(fds[WRITE_END], STDOUT_FILENO) == STDOUT_FILENO &&
dup2(fds[WRITE_END], STDERR_FILENO) == STDERR_FILENO) {
close(fds[WRITE_END]);
setvbuf(stdout, NULL, _IOLBF, 0); // line buffered
setvbuf(stderr, NULL, _IONBF, 0); // no buffer
}
}
return info;
}
int unduplicate(duplicate_t *info) {
if (!info) { return -1; }
fflush(stdout); // flush output
fflush(stderr);
dup2(info->stdout, STDOUT_FILENO); // restore fds
dup2(info->stderr, STDERR_FILENO);
close(info->stdout); // make sure fds are closed
close(info->stderr);
waitpid(info->pid, NULL, 0);
free(info);
return 0;
}