12

In a POSIX environment when using system calls to manipulate text files (open(), close(), read(), write()), is there a way to to check to see if I actually closed a file descriptor when using close(file_descriptor)?

Example:

int main(int argc, char **argv)
{
    int input_file; // file descriptor for input file
    int output_file; // file descriptor for output file
    input_file = open(argv[1], O_RDONLY));
    ouput_file = open(argv[2], ...file properties & permissions and crap.....);

    // blah blah blah...

    close(input_file);
    close(output_file);

    // what can I code here to check if the file descriptor was actually closed?
}

3 Answers3

8

The easiest way is probably to just check the return value of your first close.

if (close(input_file)) {
    perror("close");
} else {
    // all good
}

This is the only thread-safe way. In a multi-threaded program, another thread could get a new file descriptor that recycles the fd number of the one that was just closed.


In single-threaded code only, where nothing can recycle an fd between close and check:

If you wish to check whether a file descriptor is valid afterwards then you can use any function that consumes a file descriptor and check it's error code. The most non-intervening and light weight is probably fcntl/F_GETFL

if (fcntl(fd, F_GETFL) < 0 && errno == EBADF) {
    // file descriptor is invalid or closed
}

You could also just call close a second time and it can also fail with EBADF.

if (close(fd) && errno == EBADF) {
    // file descriptor is invalid or closed
} else {
    // we successfully closed fd *now* (e.g. first close failed with EINTR)
    // or there was a different error
}

close doesn't modify errno on success, but we only read errno when close returns non-zero, meaning it has been set.

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
Sergey L.
  • 21,822
  • 5
  • 49
  • 75
  • 1
    @SteveJobs: Each of those methods are dangerous in a multi-threaded environment, as the file descriptor in question might very have been recycled by another thread since it has been closed the last time. – alk Oct 29 '13 at 19:22
  • 1
    Evil assignment. It should be `errno == EBADF`. – Isaac Sep 30 '14 at 08:48
  • 3
    Be careful in multi-threaded programs. File descriptors are re-used. Therefore closing an FD could result in another FD with the same value being opened right after the `close()` - if another thread gets in and opens another file very quickly. The only thread-safe way to do this is to check the return value. – Philip Couling Jan 27 '15 at 19:05
  • Yoda conditions I recommend if you are to check global variables against values. Protect from evil assignments you must. `EBADF == errno` – Chris Watts Aug 25 '15 at 18:04
  • 1
    This answer isn't guaranteed to work. If `close()` succeeds, it won't modify `errno`. `errno` needs to be set to `0` prior to calling `close()`. Checking if the file descriptor is valid afterwards also won't work for a multithreaded program, or in any other situation where the file descriptor can be reused. – Andrew Henle Dec 08 '18 at 13:04
  • @AndrewHenle: this code only reads `errno` in cases where `close` failed and returned non-zero, so there's no need to zero it first. I did edit the error to warn about thread safety though. – Peter Cordes Dec 08 '18 at 18:26
3

You can check the return value of close() and the error number accordingly.

from http://pubs.opengroup.org/onlinepubs/009695399/functions/close.html

Upon successful completion, 0 shall be returned; otherwise, -1 shall be returned and errno set to indicate the error.

[...]

The close() function shall fail if:

[EBADF]

The fildes argument is not a valid file descriptor.

alk
  • 69,737
  • 10
  • 105
  • 255
tristan
  • 4,235
  • 2
  • 21
  • 45
0

The only save method to check whether a file descriptor is invalid is to explicitly set it to (for example) -1 on definition and every time it successfully has been closed.

The could be done using macro:

#define FD_INVALID (-1)

#define FD_CLOSE_AND_INVALIDATE(fd) \
do { \
  if (-1 == close(fd)) \
  { \
    perror("close failed"); \
  } \
  else \
  { \
    (fd) = FD_INVALID; \
  } \
} while (0)

int main(int argc, char ** argv)
{
  int input_file = FD_INVALID; /* Properly initialise file descriptor for input file */
  int output_file = FD_INVALID; /*  Properly initialise file descriptor for output file */

  input_file = open(argv[1], O_RDONLY));
  ouput_file = open(argv[2], ...);

  /* Do something. */

  FD_CLOSE_AND_INVALIDATE(input_file);
  FD_CLOSE_AND_INVALIDATE(output_file);

  /* To check if the file descriptor was actually closed just test for FD_INVALID. */
}
alk
  • 69,737
  • 10
  • 105
  • 255
  • If system has many file descriptors, maintain an hash map of file descriptors and each time u close, set them to -1. – Helping Bean Oct 29 '18 at 18:44