fuz
fuz

Reputation: 92966

How to reserve a file descriptor?

I'm writing a curses-based program. In order to make it simpler for me to find errors in this program, I would like to produce debug output. Due to the program already displaying a user interface on the terminal, I cannot put debugging output there.

Instead, I plan to write debugging output to file descriptor 3 unconditionally. You can invoke the program as program 3>/dev/ttyX with /dev/ttyX being a different teletype to see the debugging output. When file descriptor 3 is not opened, write calls fail with EBADF, which I ignore like all errors when writing debugging output.

A problem occurs when I open another file and no debugging output has been requested (i.e. file descriptor 3 has not been opened). In this case, the newly opened file might receive file descriptor 3, causing debugging output to randomly corrupt a file I just opened. This is a bad thing. How can I avoid this? Is there a portable way to mark a file descriptor as “reserved” or such?

Here are a couple of ideas I had and their problems:

Upvotes: 2

Views: 2048

Answers (4)

Vatine
Vatine

Reputation: 21248

Why use fd 3? Why not use fd 2 (stderr)? It already has a well-defined "I am logging of some sorts" meaning, is always (not true, but sufficiently true...) and you can redirect it before starting your binary, to get the logs where you want.

Another option would be to log messages to syslog, using the LOG_DEBUG level. This entails calling syslog() instead of a normal write function, but that's simply making the logging more explicit.

A simple way of checking if stderr has been redirected or is still pointing at the terminal is by using the isatty function (example code below):

#include <stdio.h>
#include <unistd.h>

int main(void) {
  if (isatty(2)) {
    printf("stderr is not redirected.\n");
  } else {
    printf("stderr seems to be redirected.\n");
  }
}

Upvotes: 4

abligh
abligh

Reputation: 25119

You claim you can't guarantee you can open /dev/null successfully, which is a little strange, but let's run with it. You should be able to use socketpair() to get a pair of FDs. You can then set the write end of the pair non-blocking, and dup2 it. You claim you are already ignoring errors on writes to this FD, so the data going in the bit-bucket won't bother you. You can of course close the other end of the socketpair.

Upvotes: 2

Andrew Henle
Andrew Henle

Reputation: 1

Don't focus on a specific file descriptor value - you can't control it in a portable manner anyway. If you can control it at all. But you can use an environment variable to control debug output to a file:

int debugFD = getDebugFD();

...

int getDebugFD()
{
    const char *debugFile = getenv( "DEBUG_FILE" );
    if ( NULL == debugFile )
    {
        return( -1 );
    }

    int fd = open( debugFile, O_CREAT | O_APPEND | O_WRONLY, 0644 );
    // error checking can be here

    return( fd );
}

Now you can write your debug output to debugFD. I assume you know enough to make sure debugFD is visible where you need it, and also how to make sure it's initialized before trying to use it.

If you don't pass a DEBUG_FILE envval, you get an invalid file descriptor and your debug calls fail - presumably silently.

Upvotes: 1

Claudio
Claudio

Reputation: 10947

In the very beginning of your program, open /dev/null and then assign it to file descriptor 3:

 int fd = open ("/dev/null", O_WRONLY);
 dup2(fd, 3);

This way, file descriptor 3 won't be taken.

Then, if needed, reuse dup2() to assign file descriptor 3 to your debugging output.

Upvotes: 2

Related Questions