This is incomplete (error handling is omitted), but it demonstrates the principle of replacing filedescriptors via dup()
and dup2()
:
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <fcntl.h>
int errtee(char * path);
/*
** Function to write to *both* stderr and a named file
** Return the new filedescriptor, which should be
** used instead of STDERR_FILENO.
** (or -1 on error)
*/
int errtee(char * path){
int out[2];
pipe(out);
int pid=fork();
if(pid<0){ // error
perror( "fork() failed\n" );
return -1;
}
if(pid>0){ // parent
close(out[0]);
return out[1];
}
if(pid==0){ // child
int fd_extra;
char buff[512];
fd_extra = open(path, O_CREAT | O_WRONLY, 0666);
if (fd_extra < 0) return -1;
close(out[1]);
dup2(out[0], STDIN_FILENO);
close(out[0]);
while(1) {
unsigned done, todo;
int cnt, this;
cnt = read(STDIN_FILENO, buff, sizeof buff) ;
if (cnt <=0) break;
for(todo = cnt, done = 0; done < todo; done += this) {
this = write(STDERR_FILENO, buff+done, todo-done);
if (cnt <=0) break; // this is not perfect ...
}
for(todo = cnt, done = 0; done < todo; done += this) {
this = write(fd_extra, buff+done, todo-done);
if (cnt <=0) break; // this is not perfect ...
}
}
close( fd_extra);
}
exit(0);
}
int main(void){
int new_fd;
int old_fd;
char buff[222];
new_fd = errtee("Mylogfile.log" );
if (new_fd < 0) {
perror( "errtee() failed\n" );
exit(1);
}
// Save the stdin filedescriptor
old_fd = dup( STDERR_FILENO);
// ... and replace it by the one from the subprocess.
dup2(new_fd, STDERR_FILENO);
// Send some output to stderr ...
while (fgets(buff, sizeof buff, stdin) ) {
fprintf(stderr, "%s", buff);
}
// Restore the old stdin filedescriptor
dup2(old_fd, STDERR_FILENO);
// ... and check if stderr still works ...
fprintf(stderr,"Completed\n" );
return 0;
}
Note: diagnostic output should go to stderr. If you keep it that way, it can always be replaced by a method like above.
[It is also possible to fetch the fileno() from the fopen'd log file, and do it the other way around. YMMV]