Skip to content

Current handling of Unix close() can lead to silent data loss #98338

Open
@jgoerzen

Description

@jgoerzen

While working on #98209 , I went searching for where close() is called. I found it in std/src/os/fd/owned.rs:

impl Drop for OwnedFd {
    #[inline]
    fn drop(&mut self) {
        unsafe {
            // Note that errors are ignored when closing a file descriptor. The
            // reason for this is that if an error occurs we don't actually know if
            // the file descriptor was closed or not, and if we retried (for
            // something like EINTR), we might close another valid file descriptor
            // opened after we closed ours.
            let _ = libc::close(self.fd);
        }
    }
}

The Linux close(2) manpage states:

       A  careful programmer will check the return value of close(), since it is quite possi‐
       ble that errors on a previous write(2)  operation  are  reported  only  on  the  final
       close()  that  releases  the open file description.  Failing to check the return value
       when closing a file may lead to silent loss of data.  This can especially be  observed
       with NFS and with disk quota.

       Note,  however,  that  a  failure  return  should be used only for diagnostic purposes
       (i.e., a warning to the application that there may still be I/O pending or  there  may
       have been failed I/O) or remedial purposes (e.g., writing the file once more or creat‐
       ing a backup).

       Retrying the close() after a failure return is the wrong thing to do, since  this  may

Anyhow, checking the return value of close(2) is necessary in a number of cases. But here we have a conundrum, because what can we possibly do with a failure of close(2) inside a Drop impl?

  1. Ignore it as now
  2. Panic
  3. More complex options (eg, allow the caller to register a close-failure callback with the underlying File/whatever object)

These all have their pros and cons. But aren't we looking for something more like this?

fn close(self) -> io::Result<()>

In fact, a Close trait could define this function and it could be implemented on files, pipes, sockets, etc.

Meta

rustc --version --verbose:

rustc 1.56.1 (59eed8a2a 2021-11-01)
binary: rustc
commit-hash: 59eed8a2aac0230a8b53e89d4e99d55912ba6b35
commit-date: 2021-11-01
host: x86_64-unknown-linux-gnu
release: 1.56.1
LLVM version: 13.0.0

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-ioArea: `std::io`, `std::fs`, `std::net` and `std::path`C-bugCategory: This is a bug.T-libs-apiRelevant to the library API team, which will review and decide on the PR/issue.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions