6

I have noticed some unexpected behaviour on both OS X and Linux. Turning on non-blocking I/O (using O_NONBLOCK) for standard output turns it on for standard input too!

Are these OSes behaving correctly? If so, is this behaviour defined by POSIX? Please point me to the relevant documentation if this is the case.

Here's a example program I used to test this:

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

int main (int argc, char *argv[]) {
  int flags = fcntl(STDOUT_FILENO, F_GETFL);
  if (argc > 1 && strcmp(argv[1], "1") == 0) {
    fcntl(STDOUT_FILENO, F_SETFL, flags | O_NONBLOCK);
  }
  printf("stdout O_NONBLOCK is: %d\n", fcntl(STDOUT_FILENO, F_GETFL) & O_NONBLOCK);
  printf("stdin O_NONBLOCK is: %d\n", fcntl(STDIN_FILENO, F_GETFL) & O_NONBLOCK);
  return 0;
}

On OS X:

$ clang -o fd-test fd-test.c
$ ./fd-test
stdout O_NONBLOCK is: 0
stdin O_NONBLOCK is: 0
$ ./fd-test 1
stdout O_NONBLOCK is: 4
stdin O_NONBLOCK is: 4

On Linux:

$ gcc -o fd-test fd-test.c
$ ./fd-test
stdout O_NONBLOCK is: 0
stdin O_NONBLOCK is: 0
$ ./fd-test 1
stdout O_NONBLOCK is: 2048
stdin O_NONBLOCK is: 2048
Chaitanya Gupta
  • 4,043
  • 2
  • 31
  • 41

1 Answers1

6

When a process is started from the shell, stdin, stdout and stderr point to the same file description. This file description is marked as O_NONBLOCK by your fcntl(1) call and therefore expectedly both file descriptors are marked as O_NONBLOCK.

If you want to indeed write to the same file from two file descriptors but want one to be marked as O_NONBLOCK, you need to create a new file description for the same file. If you know the file name of the file in question, you can achieve this by simply calling open() with the desired file name and flags. On some operating systems, you can find the file name using a platform-specific API, such as the /proc/fd virtual file system (many Unices including Linux) or the fd2path() function from Plan 9. Refer to this question for more details.

Note that simply calling open("/dev/fd/0", ...) may not work as intended as it returns a pointer to the same file description on some platforms if I recall correctly.

fuz
  • 88,405
  • 25
  • 200
  • 352
  • 3
    It should perhaps be noted that *file description* is confusingly similar to, but different from, *file descriptor*. – n. m. could be an AI May 26 '14 at 08:46
  • @n.m. Well, stdin, stdout and stderr are per definition different file *descriptors*, so I didn't think there was any ambiguity. Feel free to edit my answer. – fuz May 26 '14 at 08:59
  • There is no ambiguity per se. There is only potential confusion because of unfortunate similarity of terms. – n. m. could be an AI May 26 '14 at 10:43
  • How can this be accomplished? If i use `pipe()` or `socketpair()`, i can set one non-blocking without the other. – 12431234123412341234123 Aug 29 '17 at 13:54
  • @12431234123412341234123 That's because you get two file descriptors pointing to different file descriptions from `pipe()` and `socketpair()`. Give me a second to expand my answer to address ways to do what you want. – fuz Aug 29 '17 at 13:59
  • 1
    Note that stdin and stdout are different file descriptors if the process is inside a pipeline! Try ``echo hello | ./fd-text`` – carveone Oct 15 '18 at 12:30