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?
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();
}
}