Is /dev/null always openable?
Asked Answered
C

4

6

I want to suppress certain fprintf calls by redirecting the destination FILE to /dev/null. But can I be sure, that fopen("/dev/null", "w"); NEVER returns NULL. In other words, is it everytime possible to open this "file"?

If so, I could use this nice ternary operator:

FILE *whereToPrint = (strcmp(custom_topic, ERROR_TOPIC) == 0) ? fopen("/dev/null", "w") : stdout;

fprintf(whereToPrint, "Message sent!\n\n");
Credulity answered 23/2, 2018 at 19:23 Comment(1)
Another reason opening a file -- any file -- can fail is because your program has too many files open.Carder
F
7

Yes, it is possible that fopen fails to open /dev/null for some reason, e.g. permission issue on /dev/null. Though it is very rare but cannot deny the possibility.

Fai answered 23/2, 2018 at 19:38 Comment(0)
X
10

Yes, on a properly functioning system, /dev/null is world writable:

 ls -l /dev/null
 crw-rw-rw- 1 root root 1, 3 Jul 20  2017 /dev/null

So it'll always work. It's not the most efficient way to suppress output.. it would be better to simply not attempt to write if you don't want to.. but if it's not a lot of output, that won't matter.

Someone pointed out that it is possible for root to set the permissions of /dev/null so that it is not writable by other. Or they could delete the device altogether. This is true.. but it would result in a broken unix. /dev/null is supposed to have permissions as I showed above.. it is installed that way and should never be changed. Nevertheless, you should check the return value of fopen() or open() whenever opening any file.

Xylia answered 23/2, 2018 at 19:29 Comment(5)
I don't think it is an answer for a question "Is /dev/null always openable?". You can sudo chmod 000 /dev/null, though it's something abnormal.Comprehensive
Well i think this answer is pretty good, and i changed my mind to an additional IF-statement and not outputing the unnecessary text!Credulity
@liliscent True.. and you could also sudo chmod 000 /etc/passwd or sudo rm -f /bin/bash .. or do an endless number of things as root to break your system. My answer is for a properly functioning system... Ideally one would want to check the return value of fopen(), as always.. but I didn't want to get away from the basic question.Xylia
I added that to my answerXylia
on Godbolt trying to open /dev/null for write results in a permission denied error.Babita
F
7

Yes, it is possible that fopen fails to open /dev/null for some reason, e.g. permission issue on /dev/null. Though it is very rare but cannot deny the possibility.

Fai answered 23/2, 2018 at 19:38 Comment(0)
S
4

It is possible for /dev/null to be unwritable or missing. I mean, you can open up a root shell and type rm /dev/null and press RETURN and it will cheerfully go ahead and delete the device node.

It is reasonable for your program to fail if /dev/null is unwritable or missing. Lots of other programs have that property. But it is not reasonable for your program to blindly assume that fopen("/dev/null", "w") succeeded, unless it is a one-off test program that nobody other than yourself will ever run. Write the additional two lines to call perror and exit if whereToPrint is NULL. Honestly I would include that even if it was a one-off test program that only I would ever run. It's no more than ten extra seconds of typing, and who knows? Maybe the problem I'm having will turn out to be that some buggy system script deleted /dev/null behind my back!

EDIT: It just occurred to me that you might be hesitating to check the result of fopen not because of the extra typing, but because you don't want to mess up your "nice ternary expression". There's no need to mess it up; you just put the check right afterward:

FILE *whereToPrint = (strcmp(custom_topic, ERROR_TOPIC) == 0) ? fopen("/dev/null", "w") : stdout;
if (!whereToPrint) {
    perror("/dev/null");
    exit(1);
}

If whereToPrint has been set equal to stdout, then it will not be NULL and the check will succeed.

If you have a whole bunch of these error topics, then you should put them and their names in a table, so you can loop over them in initialization, and also there's no need to open /dev/null more than once:

enum error_topic_codes { 
    ET_INPUT, ET_CRUNCHING, ET_FROBNICATING, ET_OUTPUT,
    ET_MISC
};
struct error_report_s {
    const char *label;
    FILE *fp;
};
static struct error_report_s error_destinations[] = {
    /* ET_INPUT */        { "input", 0 },
    /* ET_CRUNCHING */    { "crunching", 0 },
    /* ET_FROBNICATING */ { "frobnicating", 0 },
    /* ET_OUTPUT */       { "output", 0 },
    /* ET_MISC */         { "misc", 0 },
};

void error_report_init (const char *squelched)
{
    FILE *devnull = fopen("/dev/null", "w");
    if (!devnull) {
        perror("/dev/null");
        exit(1);
    }
    for (int i = 0; i <= ET_MISC; i++)
        error_destinations[i].fp =
            strstr(squelched, error_destinations[i].label)
            ? devnull : stderr;
    /* FIXME 2018-02-23: This leaks a FILE if none of the
       error categories are squelched.  */
}
Saucier answered 23/2, 2018 at 19:42 Comment(0)
C
3

Yes, /dev/null is always openable -- except when it isn't.

That sounds silly, but I'm not joking. If /dev/null can't be opened, you may have a badly broken, probably borderline nonfunctional system -- but knowing this is not the same as a guarantee that the file is openable.

There's always a reason that opening a file can fail. You should never look for an excuse not to check the return value of fopen for failure.

It may never happen that you know about, it may never happen on a properly-functioning system, but ask yourself, what will happen if opening /dev/null "impossibly" fails?

  1. If your program checks for fopen failure, it will print a message like "Impossible error! Can't open /dev/null", and it will be clear what's going on.

  2. If your program fails to check for fopen failure, it will mysteriously crash the first time it tries to print something to whereToPrint, and your user will be left wondering what went wrong.

Programs that crash mysteriously are Bad. Programs that tell you what's going on are Good.

And the more you can tell your user about what's going on, the better. I suggested printing "Impossible error! Can't open /dev/null", and that's better than nothing, but it's actually still significantly incomplete. You should really write code that behaves something like this:

#include <stdio,h>
#include <string.h>
#include <errno.h>

FILE *whereToPrint;

if(strcmp(custom_topic, ERROR_TOPIC) != 0)
    whereToPrint  = stdout;
else if((whereToPrint = fopen("/dev/null", "w")) == NULL) {
    fprintf(stderr, "Impossible error! Can't open /dev/null: %s\n", strerror(errno));
    exit(1);
}

Now, on that "impossible" occasion that it fails, it will tell you why it couldn't open /dev/null, and that may be fantastically useful information. It might print

Impossible error! Can't open /dev/null: No such file or directory

if /dev/null somehow doesn't exist. Or it might print

Impossible error! Can't open /dev/null: Permission denied

if, as others have suggested, someone wrongly restricted the permissions on /dev/null on your system. Or it might print

Impossible error! Can't open /dev/null: Too many open files

And that, in fact, is a way it could fail even on a correctly-configured system, due to a bug in your program!

For example, going back to your "nice ternary operator", if you ever wrote something like

void log_message(const char *msg)
{
    FILE *whereToPrint = (strcmp(custom_topic, ERROR_TOPIC) == 0) ?
                                       fopen("/dev/null", "w") : stdout;
    fprintf(whereToPrint, "%s", msg);
}

you would be quite likely to get the "Too many open files" error sooner or later, because of course the log_message() function I've written here has a bug: it opens the file (maybe) every time it's called, but never closes it.

"Nice" uses of the ternary operator -- or any other "nice" tricks -- are fun to write, and they're fine if they work, but don't please don't cling to them at the expense of other, more important facets of your code, like making sure that it works well under all circumstances. :-)

Carder answered 23/2, 2018 at 21:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.