GTBebbo
GTBebbo

Reputation: 1218

Logging in C printing to both stdout and stderr without duplicates

I've implemented my own message logging functions for my C command line program, I want to be able to print Info messages to stdout, Error messages to stderr, and Warning messages to both without having duplicate messages if they output to the same location.

The Info and Error messages work fine, but for the Warning messages I have no idea how to efficiently check if stdout and stderr file streams point to the same output location. My code works, but I can't figure out why, because logically when stepping through the function it should produce duplicate entrys if stdout and stderr point to the same file.

I've checked when stdout and stderr have the same output file, they still produce different memory addresses in the pointers and fileno(stdout) and fileno(stderr) are different.

tl;dr I have code that works, but as far as I'm aware... it shouldn't. Can anyone help explain why it is working or does anyone know the correct way to solve this.

Edit: the way I'm calling the program is: myProgram >out.lis 2>out.lis

Edit 2: When calling like this it does produce duplicates: myProgram >out.lis 2>&1

My Code for the warning message:

/* Warning message, a unified function for printing warning messages */
/* Warning messages are printed to both the output log and the error log (if different) */
void warningMessage(char * msg) {
    if (isatty(fileno(stderr))) {
        /* Add color if printing to terminal */
        fprintf(stderr, "\033[0;31;43mWarning: \033[0;30;43m%s\033[39;49m\r\n", msg);
    } else {
        fprintf(stderr, "\nWarning: %s\n", msg);

        if (isatty(fileno(stdout))) {
            fprintf(stdout, "\033[0;31;43mWarning: \033[0;30;43m%s\033[39;49m\r\n", msg);
        } else {
            fprintf(stdout, "\nWarning: %s\n", msg);
        }
    }
}

Any other pointers about my code would be helpful as well! I've only recently started learning C!

Upvotes: 1

Views: 610

Answers (2)

David G.
David G.

Reputation: 690

The trick is going to be fstat().

Get the fileno() for both (should be 1 and 2 respectively, but might differ for non-Unix OSes), pass these as the first parameter to fstat(), and compare the structures filled in as the second parameter. I would expect exact matches if they output to the same place. I could believe that might the timestamps might be different.

I'm afraid I can't tell you if MS-Windows has the same call or not, but it should have an equivalent.

Don't forget to flush appropriately.

Edit:

The answer by Some programmer dude notes you only need to check two fields. This is correct (except perhaps on some weird filesystems).

There are some weird things that can happen. If there are two device nodes for the same device (as in /dev/tty1 and /dev/tty1_alternate pointing to the same device) then st_ino will not match but st_rdev will. I would treat these as different, as the user is playing games with you.

It might also be good to try to check if the two opens are the same open. (Dealing with the myprogram >out 2>out case.) For this, you probably need to mess with some of the parameters and see if changing one changes the other. Probably the fcntl() function, using F_GETFL and F_SETFL.

Upvotes: 2

Some programmer dude
Some programmer dude

Reputation: 409176

A mentioned in the answer by David G. you could use fstat to check where the file descriptors really write.

On a POSIX system you use the stat structure members st_dev and st_ino to find out what files are used. If these two members are equal for both stdout and stderr then you're writing to the same file.

I also suggest you make this check only once early in your program. Redirection happens only once, and you don't need to check for it every time you want to write a message.

Upvotes: 1

Related Questions