Bilow
Bilow

Reputation: 2314

How to deal with open() returning 1

I wrote a program that creates an empty text file, and prints Succeed if it succeeds.

Compile with cc main.c

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

int main()
{
    int fd;

    // Create empty text file
    fd = open("foo.txt", O_RDWR | O_CREAT | O_TRUNC, 0644);
    assert(fd != -1);

    fprintf(stderr, "File descriptor is %d\n", fd);

    printf("Succeed\n");
}

It works well when run with ./a.out

When run with ./a.out >&-, open() returns 1, which I understand.

But then, printf is writing into my file!

$ cat foo.txt
Succeed

I don't want this, so I wrote the following:

int main()
{
    int fd1;
    int fd2;
    int fd3;
    int fd4;

    fd1 = open("foo.txt", O_RDWR | O_CREAT | O_TRUNC, 0644);
    assert(fd1 != -1);

    fd2 = open("foo.txt", O_RDWR | O_CREAT | O_TRUNC, 0644);
    assert(fd2 != -1);

    fd3 = open("foo.txt", O_RDWR | O_CREAT | O_TRUNC, 0644);
    assert(fd3 != -1);

    fd4 = open("foo.txt", O_RDWR | O_CREAT | O_TRUNC, 0644);
    assert(fd4 != -1);

    int final_fd = fd4;
    close(fd1);
    close(fd2);
    close(fd3);

    fprintf(stderr, "File descriptor is %d\n", final_fd);

    printf("Standard output\n");
}

It works well, printf will fail and return -1 but I don't care.

I know I could optimize by checking if open() return value is greater or equal to 3 but this is just an example.

Using ./a.out >/dev/null works but is not a solution, I can't forbid the users of my program to close standard output.

Is this a correct way to deal with the problem?

Upvotes: 1

Views: 1117

Answers (1)

On POSIX open will always return smallest available file descriptor. The only way you can handle this is in the beginning of your program to check the file descriptors 0 ... 2 say with isatty - if isatty returns 0 and errno is set to EBADF, the file descriptor isn't in use, and one should then open a new descriptor from /dev/null:

for (int fd = 0; fd <= 2; fd++) {
    errno = 0;
    if (! isatty(fd) && errno == EBADF) {
        open("/dev/null", O_RDWR);
    }
}

Notice also that the state of those streams not being open is not standards-compliant at all. C11 7.21.3p7:

7 At program startup, three text streams are predefined and need not be opened explicitly -- standard input (for reading conventional input), standard output (for writing conventional output), and standard error (for writing diagnostic output). As initially opened, the standard error stream is not fully buffered; the standard input and standard output streams are fully buffered if and only if the stream can be determined not to refer to an interactive device.

This could be considered a failure in the C startup routine - in my opinion it should open those streams to /dev/null at least


Though now that I tried this, I've reached the conclusion that running programs with standard streams closed is not only brain-damaged but totally brain-dead, because any fopen would also open over stdin, stdout or stderr, so I'd modify the code into:

struct stat statbuf;
for (int fd = 0; fd <= 2; fd++) {
    if (fstat(fd) == 0 && errno == EBADF) {
        fprintf(stderr, "Id10t error: closed standard IO descriptor %d\n", fd);
        abort();
    }
}

Upvotes: 3

Related Questions