24

From what I have been reading on The Open Group website on fcntl, open, read, and write, I get the impression that whether O_NONBLOCK is set on a file descriptor, and hence whether non-blocking I/O is used with the descriptor, should be a property of that file descriptor rather than the underlying file. Being a property of the file descriptor means, for example, that if I duplicate a file descriptor or open another descriptor to the same file, then I can use blocking I/O with one and non-blocking I/O with the other.

Experimenting with a FIFO, however, it appears that it is not possible to have a blocking I/O descriptor and non-blocking I/O descriptor to the FIFO simultaneously (so whether O_NONBLOCK is set is a property of the underlying file [the FIFO]):

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char **argv)
{
    int fds[2];
    if (pipe(fds) == -1) {
        fprintf(stderr, "`pipe` failed.\n");
        return EXIT_FAILURE;
    }

    int fd0_dup = dup(fds[0]);
    if (fd0_dup <= STDERR_FILENO) {
        fprintf(stderr, "Failed to duplicate the read end\n");
        return EXIT_FAILURE;
    }

    if (fds[0] == fd0_dup) {
        fprintf(stderr, "`fds[0]` should not equal `fd0_dup`.\n");
        return EXIT_FAILURE;
    }

    if ((fcntl(fds[0], F_GETFL) & O_NONBLOCK)) {
        fprintf(stderr, "`fds[0]` should not have `O_NONBLOCK` set.\n");
        return EXIT_FAILURE;
    }

    if (fcntl(fd0_dup, F_SETFL, fcntl(fd0_dup, F_GETFL) | O_NONBLOCK) == -1) {
        fprintf(stderr, "Failed to set `O_NONBLOCK` on `fd0_dup`\n");
        return EXIT_FAILURE;
    }

    if ((fcntl(fds[0], F_GETFL) & O_NONBLOCK)) {
        fprintf(stderr, "`fds[0]` should still have `O_NONBLOCK` unset.\n");
        return EXIT_FAILURE; // RETURNS HERE
    }

    char buf[1];
    if (read(fd0_dup, buf, 1) != -1) {
        fprintf(stderr, "Expected `read` on `fd0_dup` to fail immediately\n");
        return EXIT_FAILURE;
    }
    else if (errno != EAGAIN) {
        fprintf(stderr, "Expected `errno` to be `EAGAIN`\n");
        return EXIT_FAILURE;
    }

    return EXIT_SUCCESS;
}

This leaves me thinking: is it ever possible to have a non-blocking I/O descriptor and blocking I/O descriptor to the same file and if so, does it depend on the type of file (regular file, FIFO, block special file, character special file, socket, etc.)?

Daniel Trebbien
  • 38,421
  • 18
  • 121
  • 193
  • I am wondering about this because if O_NONBLOCK being set is a property of the underlying file, then a call to open a file with O_NONBLOCK *not* set in oflags *could* nevertheless return a file descriptor with the O_NONBLOCK flag. – Daniel Trebbien May 22 '10 at 22:06

1 Answers1

39

O_NONBLOCK is a property of the open file description, not of the file descriptor, nor of the underlying file.

Yes, you could have separate file descriptors open for the same file, one of which is blocking and the other of which is non-blocking.

You need to distinguish between a FIFO (created using mkfifo()) and a pipe (created using pipe()).

Note that the blocking status is a property of the 'open file description', but in the simplest cases, there is a one-to-one mapping between file descriptors and open file descriptions. The open() function call creates a new open file description and a new file descriptor that refers to the open file description.

When you use dup(), you have two file descriptors sharing one open file description, and the properties belong to the open file description. The description of fcntl() says that F_SETFL affects the open file description associated with the file descriptor. Note that lseek() adjusts the file position of the open file description associated with the file descriptor - so it affects other file descriptors duplicated from the original one.

Removing the error handling from your code to reduce it, you have:

int fds[2];
pipe(fds);
int fd0_dup = dup(fds[0]);
fcntl(fd0_dup, F_SETFL, fcntl(fd0_dup, F_GETFL) | O_NONBLOCK);

Now both fd0_dup and fds[0] refer to the same open file description (because of the dup()), so the fcntl() operation affected both file descriptors.

if ((fcntl(fds[0], F_GETFL) & O_NONBLOCK)) { ... }

Hence the observed behaviour here is required by POSIX.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • 3
    Oh, okay. So there's a difference between a "file descriptor" and "file description". Two file descriptors can share the same file description, and the flags are a property of the description. Looking at the docs for open again, it says that the function "shall create an open file description". I think that I understand now. – Daniel Trebbien May 22 '10 at 22:35
  • 3
    @DanielTrebbien: some flags belong to file descriptors e.g., `FD_CLOEXEC`. – jfs Feb 12 '15 at 16:57
  • "O_NONBLOCK is a property of the open file descriptor, not of the file descriptor, nor of the underlying file." I do not understand the distinction between "open file descriptor", "file descriptor" and the ''open file description" mentioned later on. Are all unique and separate? – TomE Apr 22 '21 at 21:11
  • 2
    @TomE: There are "open file descriptors" and "open file descriptions", and the two are separate. However, there is a bug in the first sentence, which I'm about to fix. That bug was introduced by an edit made on 2021-01-08 in [Revision 6](https://stackoverflow.com/revisions/2889920/6) which I've since rolled back as it completely subverts what I'm saying. I'm not sure how I missed the edit being made at the time; I was probably notified of it. It is refixed again. – Jonathan Leffler Apr 22 '21 at 21:16
  • 2
    You can find information about 'open file descriptors' and 'open file descriptions' in the links in the answer, notably the link to the POSIX specification for `open()`. – Jonathan Leffler Apr 22 '21 at 21:17