9

Imagine the following code running as a thread:

void *thread_worker(void *q) {
 for (;;) {
  int fd = some_queue_get(q);
  FILE *writer = fdopen(fd, "w");
  if (!writer) { perror("fdopen"; close(fd); continue; }
  // do something with writer

  if (fclose(writer) == EOF) {
   perror("fclose writer");
   // should fd be closed here?
   close(fd);
  }
}

fclose(3) can fail for various reasons - is there a guarantee that/when the underlying file descriptor is closed or that it is still open afterwards?

  • if fd is not closed by fclose on a flushing failure, one leaks fds without an additional close.
  • if fd is closed by fclose, an additional close might close a file descriptor that was newly opened by an other thread.
Flow
  • 23,572
  • 15
  • 99
  • 156
thejonny
  • 488
  • 2
  • 9
  • 2
    From the doc: if fclose failed, any further access (including another call to fclose) to the stream results in undefined behavior. – Jabberwocky Feb 04 '21 at 15:09
  • 1
    @Jabberwocky i don't want to access the stream, but the file descriptor – thejonny Feb 04 '21 at 15:11
  • 1
    Given the name of your function - `thread_worker`, `close(fd)` is ***dangerous***. If `fclose()` did close its file descriptor but failed anyway, another thread could reuse that value and you'd close it right out from under that other thread. – Andrew Henle Feb 04 '21 at 15:16
  • 1
    @AndrewHenle As thejonny wrote in the last sentence... – Thomas Feb 04 '21 at 15:17
  • @Thomas And there's really no way to check for that condition. – Andrew Henle Feb 04 '21 at 15:17
  • @AndrewHenle yes, that's why i asked this question - is there a way to be sure – thejonny Feb 04 '21 at 15:18
  • @thejonny: If you are using an OS which doesn't guarantee that close deallocates the FD, then you'll just have to live with the possibility of a leaked file descriptor. You *must not* retry `fclose` after failure, nor may you attempt to `close` the underlying FD. Fortunately, you are unlikely to be using such an OS. For example, "the Linux kernel always releases the file descriptor early in the close operation, freeing it for reuse; the steps that may return an error, such as flushing data to the filesystem or device, occur only later in the close operation." (from man close) – rici Feb 04 '21 at 16:29
  • Or, from man close on freebsd: "In case of any error except EBADF, the supplied file descriptor is deallocated and therefore is no longer valid." (This will also be the case on OS X, afaik.) – rici Feb 04 '21 at 16:31
  • I might move those comments to an answer later. – rici Feb 04 '21 at 16:39

2 Answers2

2

man fclose does not provide the answer on my system either, but man 3p fclose reveals the official version from the POSIX Programmer's manual, which is much more comprehensive on the matter:

The fclose() function shall perform the equivalent of a close() on the file descriptor that is associated with the stream pointed to by stream.

Thomas
  • 174,939
  • 50
  • 355
  • 478
  • 3
    But [`close()` can fail](https://stackoverflow.com/questions/33114152/what-to-do-if-a-posix-close-call-fails) and there's nothing you can do about it other than log it. In particular, you ***can't*** safely retry the `close()` call. – Andrew Henle Feb 04 '21 at 15:36
  • this also matches the current glibc behaviour, but not the first glibc source if found googling (an ancient version) :) – thejonny Feb 04 '21 at 15:43
0

The answer is NO, the C Standard is ambiguous about this (emphasis mine):

7.21.5.1 The fclose function

Synopsis

#include <stdio.h>
int fclose(FILE *stream);
 

Description
A successful call to the fclose function causes the stream pointed to by stream to be flushed and the associated file to be closed. Any unwritten buffered data for the stream are delivered to the host environment to be written to the file; any unread buffered data are discarded. Whether or not the call succeeds, the stream is disassociated from the file and any buffer set by the setbuf or setvbuf function is disassociated from the stream (and deallocated if it was automatically allocated).

Returns
The fclose function returns zero if the stream was successfully closed, or EOF if any errors were detected.

chqrlie
  • 131,814
  • 10
  • 121
  • 189
  • The C standard is irrelevant since file descriptors are not a standard C concept. You need to look at some POSIX/Unix specification/implementation to know the effect of `fclose` on file descriptors. – Gilles 'SO- stop being evil' Feb 04 '21 at 16:59
  • @Gilles'SO-stopbeingevil': It would be perverse to implement *the stream is disassociated from the file* by discarding the `FILE` structure while keeping the associated file descriptor open. Yet there is a more general issue: `close()` can fail for a reason that I have never seen anybody handle even in production code. If it is interrupted by a signal, `close()` fails, `errno` is set to `EINTR` and the file descriptor is still open. – chqrlie Feb 04 '21 at 19:13