0

I mean to associate a file descriptor with a file pointer and use that for writing. I put together program io.cc below:

int main() {
    ssize_t nbytes;
    const int fd = 3;
    char c[100] = "Testing\n";
    nbytes = write(fd, (void *) c, strlen(c));     // Line #1
    FILE * fp = fdopen(fd, "a");
    fprintf(fp, "Writing to file descriptor %d\n", fd);
    cout << "Testing alternate writing to stdout and to another fd" << endl;
    fprintf(fp, "Writing again to file descriptor %d\n", fd);
    close(fd);     // Line #2
    return 0;
}

I can alternately comment lines 1 and/or 2, compile/run

./io 3> io_redirect.txt

and check the contents of io_redirect.txt. Whenever line 1 is not commented, it produces in io_redirect.txt the expected line Testing\n. If line 2 is commented, I get the expected lines

Writing to file descriptor 3
Writing again to file descriptor 3

in io_redirect.txt. But if it is not commented, those lines do not show up in io_redirect.txt.

  • Why is that?
  • What is the correct way of using fdopen?

NOTE. This seems to be the right approach for a (partial) answer to Smart-write to arbitrary file descriptor from C/C++ I say "partial" since I would be able to use C-style fprintf. I still would like to also use C++-style stream<<.

EDIT: I was forgetting about fclose(fp). That "closes" part of the question.

  • If you've figured out one of your questions, instead of adding a correction could you remove it? – John Kugelman May 17 '20 at 19:41
  • @tadman - I checked and either `fclose(fp):` or `fflush(fp): close(fd):` work fine. Would they be equivalent? – sancho.s ReinstateMonicaCellio May 17 '20 at 19:42
  • 1
    You're mixing buffered and unbuffered writes, which is always going to be a problem. What's the use case here? You should pick one and only one method for interacting with a filehandle in actual code. Any method is fine so long as you're consistent. If you need to hand-off from one method to another, make *sure* to flush and avoid writing after the hand-off. – tadman May 17 '20 at 19:44
  • 1
    @tadman - Great. Related: https://stackoverflow.com/a/61708362/2707864 – sancho.s ReinstateMonicaCellio May 17 '20 at 19:45
  • @JohnKugelman - Because I *think* I understood the answer to the question. But I feel I do not have an authoritative answer, and comments posted shed light on it. I feel, in this case, it is more useful as it is. In other cases I certainly edit to remove the question. – sancho.s ReinstateMonicaCellio May 17 '20 at 19:54

1 Answers1

2

Why is that?

The opened stream ("stream" is an opened FILE*) is block buffered, so nothing gets written to the destination before the file is flushed. Exiting from an application closes all open streams, which flushes the stream.

Because you close the underlying file descriptor before flushing the stream, the behavior of your program is undefined. I would really recommend you to read posix 2.5.1 Interaction of File Descriptors and Standard I/O Streams (which is written in a horrible language, nonetheless), from which:

... if two or more handles are used, and any one of them is a stream, the application shall ensure that their actions are coordinated as described below. If this is not done, the result is undefined.

...

For the first handle, the first applicable condition below applies. ...

  • ...

  • If it is a stream which is open for writing or appending (but not also open for reading), the application shall either perform an fflush(), or the stream shall be closed.

A "handle" is a file descriptor or a stream. An "active handle" is the last handle that you did something with.

The fp stream is the active handle that is open for appending to file descriptor 3. Because fp is an active handle and is not flushed and you switch the active handle to fd with close(fd), the behavior of your program is undefined.

What is my guess and most probably happens is that your C standard library implementation calls fflush(fp) after main returns, because fd is closed, some internal write(3, ...) call returns an error and nothing is written to the output.

What is the correct way of using fdopen?

The usage you presented is the correct way of using fdopen.

Community
  • 1
  • 1
KamilCuk
  • 120,984
  • 8
  • 59
  • 111
  • So there is some error hanging around that I could possibly inquire about/check? What would the correct way of catching it be? – sancho.s ReinstateMonicaCellio May 18 '20 at 09:57
  • Undefined behavior means that the behavior of your program is not defined, anything can happen. It's not possible to check against undefined behavior, because once it happens, anything can happen, so the only way is to write programs so that the behavior is defined. From the practical side, I would encourage to confirm with glibc source code that `fclose` (or `fflush`) returns nonzero value in such cases. – KamilCuk May 18 '20 at 10:02
  • But you said "some internal write(3, ...) call returns an error..." That is not undefined behavior, I guess. – sancho.s ReinstateMonicaCellio May 18 '20 at 10:12
  • `That is not undefined behavior` The standard literally states: `the result is undefined`. It's irrelevant what implementation does, as far as I'm concerned, [your program opens gate to hell and spawns nasal demons](https://en.wikipedia.org/wiki/Undefined_behavior). – KamilCuk May 18 '20 at 10:13