PALEN
PALEN

Reputation: 2886

Redirect stderr to both file and stdout in C

Is there a way to write the error messages to a log file and also print them on the terminal screen?

I tried the following:

dup2(fileno(pFile), STDERR_FILENO); /* redirect stderr to file */

which redirects stderr to the file. However, that writes the error messages to the file but does not show them on screen.

Can this be done without reading and copying the contents of stderr to the file?

Note: I would like to not invoke the shell (system, popen). I did look the implementation of the tee command of coreutils. It copies the standard stream to files.

Upvotes: 4

Views: 3533

Answers (2)

Havoc P
Havoc P

Reputation: 8477

You have to copy the data "by hand" to send it to two places, though it can be made slightly more efficient with the linux-specific tee and splice system calls (note tee syscall not tee command, http://blog.superpat.com/2010/07/08/a-cup-of-tee-and-a-splice-of-cake/)

It sounds like you might know how to do it and were just hoping not to, but for others, the solution could be something like:

dup2 the original stderr (typically the terminal) to a new file descriptor to save it. Then make a pipe with pipe(). dup2 the write end of the pipe to fd 2. close original write end fd. Now your stderr is a pipe. Start up a thread, or process. In this thread, you copy data from the read end of your pipe into both the file and the original stderr which you saved. When the thread or process gets EOF reading the pipe, close it and exit.

The popen("tee") solution is the same except that it creates an extra shell process (and you have to properly quote the filename passed to the shell in case there are special chars in it... be sure to test weird filenames with greater than sign, spaces, and quote marks in them...). If using popen I think you may also have a (cosmetic?) problem that you can't pclose() because your stderr would stop working, so you will always leave an extra fd open and won't wait4() the child.

If you were using something like GLib, it has a g_spawn_async family of functions which could be used to spawn the tee command without a shell. Otherwise, you would have to do the fork/exec stuff by hand to avoid the shell; you could exec the tee command, or you could fork but NOT exec - just have the child code after fork do the stderr copying, don't rely on the tee command. Or, you could use a thread instead of a process.

If using fork, you may find the source code in gspawn.c helpful; in any complex program it's quite a nightmare that FD_CLOEXEC is not the default on unix for example and it's easy to have the child inherit descriptors that cause problems. It is also pretty annoying to avoid zombie processes and handle all the errors and blah blah.

Anyway yeah it is more code than one might hope but between the tee source in coreutils and gspawn.c you might have most of it available to copy.

Upvotes: 4

Barmar
Barmar

Reputation: 782775

Use a pipe to the tee command.

pFile = popen("tee logfile", "w");
dup2(fileno(pFile), STDERR_FILENO);

Upvotes: -1

Related Questions