Skip to content

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

Closed as not planned

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

Activity

  1. the8472 commented on Jun 21, 2022

    @the8472
    Member

    If - assuming we're talking about regular files here - you need durability then relying on close is the wrong thing because data may only make it to the page cache and later encounter an error during writeback. You should rely on fsync instead.

    let file = File::from(owned_fd);
    file.sync_all()?;

    Of course that's expensive since it forces the OS to do immediate writeback, so it should only be done when durability is critical.

  2. ChrisDenton commented on Jun 21, 2022

    @ChrisDenton
    Member

    I think it makes sense to have a close method for OwnedFd, OwnedHandle, etc if only for diagnostic purposes. There's not a whole bunch you can do with the error if you get in that situation, other than logging or maybe restarting the whole operation over again. But maybe it can help diagnose a problem.

    The API proposal is just drop but doesn't swallow the error, right?

    impl OwnedFd {
        fn close(self) -> io::Result<()>;
    }
  3. jgoerzen commented on Jun 21, 2022

    @jgoerzen
    Author

    @the8472 close(2) on Linux even states:

           A  successful  close  does not guarantee that the data has been success‐
           fully saved to disk, as the  kernel  uses  the  buffer  cache  to  defer
           writes.   Typically,  filesystems  do  not  flush buffers when a file is
           closed.  If you need to be sure that the data is  physically  stored  on
           the underlying disk, use fsync(2).  (It will depend on the disk hardware
           at this point.)
    

    But it also includes the comment I quoted above about the importance of still checking the return value of close().

    @ChrisDenton A scenario in which an error here may be relevant could be this:

    Let's say you are processing items from a queue of some sort. If processing is successful, you remove the item from the queue. If not, it remains on the queue. An error from close would be a reason to leave the item on the queue, or to signal failure to the network client, etc.

    I don't know the particulars of the NFS issue, but I can imagine them. I also imagine various FUSE filesystems (eg, s3fs) do the bulk of their work only when close is called.

    Another scenario I could imagine would involve pipes. If a prior write() put data into the pipe buffer, but the process on the other end of the pipe crashed before reading it, the only way to detect this would be via SIGPIPE (which is blocked by default in Rust; see #97889) or, I would imagine, by the return value of close(). (I have not personally verified this)

    @ChrisDenton Yes, you are correct about my proposal. It should consume and cause the dropping of the OwnedFd because, as the close(2) manpage notes, you really shouldn't attempt to close it again, even on error (with a side asterisk that certain Unices, such as HP/UX, you SHOULD close it after EINTR, but this is ambiguous in POSIX and is not the case on Linux at least; I'd argue that if somebody ports Rust to one of those OSs, EINTR handling should be internal to fn close there.)

    I would also like to see it bubbled up to the higher-level interfaces (File, sockets, etc). This shouldn't be difficult to use in cross-platform code, I'd hope. I don't know if Windows has the same close() semantics, but even if on Windows it is identical to drop, having an interface available would be helpful.

    The one trick I'd see would be preventing a double-close when there is a call to fn close (by drop).

  4. the8472 commented on Jun 21, 2022

    @the8472
    Member

    But it also includes the comment I quoted above about the importance of still checking the return value of close().

    My understanding is that fsync subsumes all that as long as the system defines _POSIX_SYNCHRONIZED_IO, which is the case on all major unix systems. E.g. postgres heavily relies on fsync for ACID guarantees, not close.

    Looking at the return value can only ever be a diagnostic thing. Unless you're opening a file with O_SYNC or O_DIRECT close doesn't guarantee anything due to writeback.

    I don't know the particulars of the NFS issue, but I can imagine them. I also imagine various FUSE filesystems (eg, s3fs) do the bulk of their work only when close is called.

    NFS supports fsync. Anything that ignores fsync is not a durable filesystem and you shouldn't entrust critical data to it.

    Another scenario I could imagine would involve pipes. If a prior write() put data into the pipe buffer, but the process on the other end of the pipe crashed before reading it, the only way to detect this would be via SIGPIPE (which is blocked by default in Rust; see #97889) or, I would imagine, by the return value of close(). (I have not personally verified this)

    Close has no special treatment for pipes. Any data still stuck in a pipe is lost.

  5. sunfishcode commented on Jun 22, 2022

    @sunfishcode
    Member

    I also imagine various FUSE filesystems (eg, s3fs) do the bulk of their work only when close is called.

    FUSE for its part documents that filesystems should avoid doing this.

  6. jgoerzen commented on Jun 22, 2022

    @jgoerzen
    Author

    @the8472 and @sunfishcode I would like to understand -

    1. Are you folks opposed to having an explicit close() that returns an error?

    2. Or are you fine with that, but dislike my justification?

    If 1, how do you reconcile that with the strong admonishment in close(2) that "failing to check the return value when closing a file may lead to silent loss of data"?

    If 2, I'd like to hear more.

    Over at https://stackoverflow.com/questions/24477740/what-are-the-reasons-to-check-for-error-on-close there is a conversation about this which I found interesting. Among the points are:

    "Consider the inverse of your question: "Under what situations can we guarantee that close will succeed?" The answer is: when you call it correctly, and when you know that the file system the file is on does not return errors from close in this OS and Kernel version"

    It is this second that has me particularly troubled. On my Debian Linux box, there are 58 in-kernel filesystem modules and dozens more implemented via FUSE. Some of them (eg, ext4) are specifically designed for POSIX semantics. Some of them (eg, cifs, exfat) were not. I use sshfs and NTFS via FUSE on a regular basis and who knows about them; I am certain they are not fully POSIX compliant. Even NFS has never been fully compliant with POSIX semantics, only getting somewhat better in more recent years.

    I'm not arguing for everything to be perfect here; in a strict sense, if one wants perfect ACID, one would not just fsync, but also check the return value of fsync and close, and fsync the parent directory after a rename also. Except on MacOS, where fsync doesn't actually force data to durable storage, unlike Linux, where fsync explicitly does.

    I just want the tools to be a careful programmer wherever possible, that's all.

  7. jgoerzen commented on Jun 22, 2022

    @jgoerzen
    Author

    This is probably a separate issue, but an incidental remark: I don't see any way to fsync a directory in std, either. File::open() won't open a directory (perhaps via O_DIRECTORY in custom_flags, but there is nothing in std that exposes O_DIRECTORY), and read_dir doesn't expose the fd for syncing either.

  8. sunfishcode commented on Jun 22, 2022

    @sunfishcode
    Member

    @the8472 and @sunfishcode I would like to understand -

    1. Are you folks opposed to having an explicit close() that returns an error?
    

    I myself am.

    If 1, how do you reconcile that with the strong admonishment in close(2) that "failing to check the return value when closing a file may lead to silent loss of data"?

    Ignoring errors from close is so pervasive that filesystem developers that care about robustness are practically expected to make sure close never fails.

    Rust has ignored errors from close since 1.0, which is a precedent across the ecosystem. If we change that, it will create a new expectation for almost all Rust libraries and utilities that write files. It's tempting to say "you don't need to check if your use case doesn't need it", but libraries and utilities usually don't pick the filesystems they run on, or how data is used outside their scope.

    If we approach this issue as "Do we care about this kind of data loss?", it's difficult to explain saying no. But we can instead ask "Who is responsible for this kind of data loss?", and there are multiple options:

    • Declare that Rust libraries and utilities are now expected to check errors from close.

    • Declare that end users using NFS are responsible for actively monitoring how much free space they have, and that FUSE filesystem authors are responsible for following the FUSE documentation.

    Sometimes fixing existing code to correctly handle errors from close is easy, but sometimes it's costly. Either way, the new code paths will be rarely exercised and difficult to test. There is an ecosystem-wide cost for adding this expectation.

    To my knowledge, the main filesystems where close does fail in practice are networked filesystems which postpone reporting errors in order to go faster (NFS calls this "async"), and FUSE filesystems which ignore the documentation. End users using NFS and caring about the data being written to it should probably already be actively monitoring for free space and other conditions, because of the amount of existing code out there that doesn't check for these errors. FUSE authors that care about protecting their users' data should already be following the documentation for flush.

    If NFS were as common as it once was, things might look different, but in practice, POSIX filesystem APIs, with all the guarantees and common assumptions, aren't a great fit for networks. Instead of exposing the async nature of networks to applications via mechanisms that could map to Rust's async, NFS' "async" mode tries to hide everything behind the illusion of a synchronous API. Filesystem APIs can be convenient, but they have significant costs.

    Neither option is without downsides. I don't have data, but I assume the costs of passing on the responsibility for errors from close to individual developers throughout the Rust ecosystem would be greater than the cost of stating that this is not the ecosystem's responsibility, and passing the responsibility onto end users that choose to use NFS, and FUSE filesystem authors, who arguably already have this responsibility.

    As a separate consideraion, stdout can be redirected to a file, and all the same data-loss scenarios are possible when writing to a file via stdout. If we add an expectation to Rust that code check for errors from close after writing to files, we should add a way to close stdout and check for errors from that too. But that's tricky, because Stdout::lock returns a &'static, which effectively guarantees that stdout is always open.

    I just want the tools to be a careful programmer wherever possible, that's all.

    This is why it's useful to make these decisions at a broad level, like Rust (or ideally higher, but one step at a time), so that we can collectively agree on what should be expected of individual careful programmers.

    I also aspire to be a careful programmer, and I once added logic to LLVM to close stdout and check for errors, because LLVM is often used to write to stdout with redirection to files. I too read the close man page and believed it was my responsibility. That code lived in the LLVM codebase for years, but only with me defending it against a long series of people hitting problems with it and wanting to remove it. I eventually stopped defending it, and it has since been removed. It was just too much trouble to be worth the cost.

  9. the8472 commented on Jun 22, 2022

    @the8472
    Member

    Are you folks opposed to having an explicit close() that returns an error?
    Or are you fine with that, but dislike my justification?

    I'm ambivalent about adding the API. Generally it does not make sense to check for errors on close.
    But I can see some niche uses when hunting for errors or having a policy of failing as loudly and as explosively as possible then it might make sense to print an error and then abort when a close fails (what else are you going to do? there likely is no sane path to recovering from an error).

    But I do dislike the justification. Checking for close is not what you do when you care about your data making it to storage intact. As I said earlier, fsync is the go-to mechanism here. To me it would be purely a diagnostic thing that something has gone horribly wrong.

    File::open() won't open a directory

    But it does? (playground)

    Some of them (eg, ext4) are specifically designed for POSIX semantics. Some of them (eg, cifs, exfat) were not. I use sshfs and NTFS via FUSE on a regular basis and who knows about them; I am certain they are not fully POSIX compliant. Even NFS has never been fully compliant with POSIX semantics, only getting somewhat better in more recent years.

    Sure but would you run a production database on a dodgy USB stick formatted with exfat?

  10. jgoerzen commented on Jun 22, 2022

    @jgoerzen
    Author

    Thank you for these thoughtful comments.

    I definitely do not want to create a precedent to require libraries to check close(). You are right that this would be unworkable. Already I wouldn't necessarily trust libraries with writing (are they doing the POSIX-safe way to do atomic writes, with fsyncs and renames? Almost uniformly not, but then you may not always want that).

    I want the option to be able to check it myself in a safe way, that's all.

    There are a lot of machines out there that are running on filesystems that I wouldn't count to apply these guarantees, or have other errors that may be signaled at close. FreeBSD can return ECONNRESET for instance. An entire class of devices (Raspeberry Pis) typically runs on dodgy micro SDs. I particular bane of my existance for years has been VMs (and even hardware) that fails to propagate fsync. I /frequently/ work with software that has to deal with USB sticks formatted with exfat or NTFS (or, heck, vfat). While NFS no doubt isn't as prolific as it once was, it is still with us, and now we have a bunch of others in that range: afs, coda, glusterfs, ocfs2, ceph, cifs, davfs, sshfs, s3fs, rclone -- to name a few. We have a /proliferation/ of filesystems or filesystem-like things, actually. It is an explicit design goal of the program I was working on when I created this issue to be maximally compatible with filesystems that differ from POSIX semantics in significant ways; for instance, s3fs, in which a file created may not be able to be opened for reading right away due to eventual consistency.

    I care about data integrity. I am realistic that checking the return value from close is not itself sufficient to gaurantee it. I don't believe there exists a perfect guarantee, in fact, because hardware isn't perfect. But I want to take every step I reasonably can, and which documentation has showed I ought to, along that path -- in part because I realize I am dealing with filesystems with a lot more failure modes than ext4 on a local SSD.

    I wouldn't try to run PostgreSQL atop S3. But for Filespooler, written in Rust, this is an explicit design goal and documented practice. Although written in Go, a similar tool, NNCP, is something I use for getting data to airgapped backup & archival systems.

  11. the8472 commented on Jun 22, 2022

    @the8472
    Member

    There seems to be some separate scenarios here

    • posix-compliant FS
      -> use fsync, close doesn't matter
    • FS that always lie about comitting data
      -> neither fsync nor close will save you here, the data will remain in some volatile buffers for unknown amounts of time. I don't think much can be done about these other than not entrusting any valuable data to them or replacing them with better solutions.
    • things that ignore fsync but commit on close
      -> these obviously DO have a mechanism to ensure durability. Instead of burdening every single downstream consumer with using close-as-fsync they should instead be fixed by doing doing the same on fsync as they do on close
    • file descriptors that aren't regular files where fsync doesn't apply (such as the freebsd socket case you mention)
      -> that's going to be depend to the type of FD and the platform you're dealing with. E.g. on sockets you could do a dance involving shutdown on your end and then waiting for the other side to terminate the connection before you call close then there shouldn't be anything left on the wire that could possibly cause close to fail.

    I definitely do not want to create a precedent to require libraries to check close(). You are right that this would be unworkable.

    Ok. Then imo it should be documented in a way that it redirects users to sync_all if they want durability and detect IO errors and basically discourage its behavior due to being non-portable and FS-dependent.

  12. 13 remaining items

  13. FeldrinH commented on Nov 26, 2025

    @FeldrinH

    My impression based on this StackOverflow answer is that C++ actually acts similarly to Rust here, even if you try to configure it otherwise:

    If you just let the file stream go out of scope then yes, C++ discards errors, the same as Rust. However, the crucial difference is that C++ has a filestream::close method that you can call to retrieve any close errors, whereas the Rust standard library doesn't.

  14. the8472 commented on Nov 26, 2025

    @the8472
    Member

    I think there should be an explicit File::close(self) -> Result<()> method that you can (and are encouraged to) call to close the file and retrieve any returned error.

    The encouragement is the issue here. It would give the false impression that a successful close somehow means you have handled all possible errors for the lifecycle of a file.

    To quote

    I will concede that the number of issues that can cause data loss after successful close is larger than I initially thought.

    This kind of misconception (and worse) is common, especially among beginners.

    The portable options are either making sure the data is persisted (which can be complicated, look at the dances databases have to do) and handling errors, or accepting that writeback will happen after the file has been closed in which case error handling is least-effort.

    There is middle-ground between those poles, but that's quite nuanced and very environment-/platform-specific. Trying to write software that is meaningful in the presence of errors from closed files would fall into that nuanced middle ground and is not something that would be recommended.

  15. FeldrinH commented on Nov 26, 2025

    @FeldrinH

    At this point I think it just comes down to a value judgement where we disagree. I think that even in a least-effort situation we should attempt to handle all the errors that the OS returns to us (possibly by aborting with a user-facing error) and that includes the error returned by close.

  16. jgoerzen commented on Nov 26, 2025

    @jgoerzen
    Author

    The thing I don't see is an argument against adding something akin to the C++ filestream::close. As noted previously, there are many many different failure cases. Of course, FDs on Unix are used for more than just files, and it seems that a lot of the conversation here has been restricted to files. For TcpStream, we have shutdown as something of a proxy, but it would be less unwieldy to have a proper close that can be checked for errors. Even on things presenting as files, some filesystems, especially FUSE ones, present an interface to an underlying layer that is not random-access or easily mutable (ie, s3fs) and which actually do the meaningful part of their work during close().

    As noted at the very top of this bug at #98338 (comment) , the Linux manpage specifically notes that checking the return value of close() is important. While I agree that this does not catch everything, what seems to be happening here is a lot of people suggesting that the documentation is incorrect. This strikes me as dangerous, and difficult to prove universally.

  17. devtempmac commented on Nov 27, 2025

    @devtempmac

    At this point I think it just comes down to a value judgement where we disagree. I think that even in a least-effort situation we should attempt to handle all the errors that the OS returns to us (possibly by aborting with a user-facing error) and that includes the error returned by close.

    hmm this isn't a simple "value judgement" matter -- this is a foundational matter here.

    what's the value-proposition of rustlang itself? "rust is safer AND faster" than c/c++/etc

    You should rely on fsync instead.

    now... by simply telling this to OP, we're saying "rust is safer XOR faster" than c/c++/etc

    to summarize (not a bot here):

    1. for c/c++, it's 3 levels of guarantees:
    • fastest (no check)
    • fast (check close result - lets say 90%)
    • slowest but 100% (fsync)
    1. but for rust (status quo) is limited to:
    • fastest (no check)
    • slowest (fsync)

    ... we can't be OK with this -- how can we persuade project-managers to use Rust for the next project, when they can simply point out this kind of stuff?

  18. workingjubilee commented on Nov 27, 2025

    @workingjubilee
    Member

    @devtempmac I struggle to believe "ask a filesystem to do anything, then check a register" is something that makes a difference performance-wise. And Rust does not exist to make all programs perfectly "safe". It exists to combat certain forms of common vulnerabilities. IO safety is one of them, but has little to do with write durability. If a project manager doesn't care about the benefits of memory safety or the use of an ownership model, then sure, Rust sucks I guess.

    The debate here is whether a close API which returned Result<(), io::Error> would improve logical correctness in Rust programs, or if it would be ritualistic checking to no purpose in the general case. We know filesystems can experience errors in highly asynchronous ways, as that is the main reason why filesystems, especially NFS, report errors on close: it is usually an error from a previous write. Thus a synchronous API cannot model the problem without an equally-strong "barrier" like fsync. That this barrier also requires a huge performance hit is a flaw of the underlying file APIs, as described by Dan Luu: https://danluu.com/deconstruct-files/

    Anything else is misleading at best, as exemplified by this thread repeatedly seeing arguments like "close results in eventual durability on most filesystems" while also referencing the Linux manpage. The manpage says this about durability:

    A careful programmer who wants to know about I/O errors may precede close() with a call to fsync(2).

    So, closing a file descriptor, if we take a manpage for one operating system as the authority, explicitly doesn't constitute a promise to tell you about IO errors. IO errors, if they occur, can mean we have failed to achieve write durability. By saying we want something the manpage doesn't promise, we've erased the manpage as a source of authority.

    I think we should be reluctant to speculate in ways like "oh, it addresses 90% of issues" when we've erased the ability of the last 10% to even appear in our statistics.

    If we want File::close (or OwnedFd::close, or whatever) because we want people to write better Rust code for file handling, a stronger argument would be a roundup of the behaviors of filesystems with write-then-close. Preferably beyond just Linux, but at least for those we do have prior research.

    The prior research

    I think a simpler, stronger argument for File::close is that we can use it to expose information. Does that information have much relation to reality, or even to closing the file itself? We don't know! We can even document the function accordingly: "use this for funsies! try not to base any irreversible logic on this since it could have been an error from forever ago, just write! a message to your log, mmk? if you wanted to really handle issues, then you would have sync_all'd and then... well, probably just panic if that fails, or go back to some checkpoint, since that's all databases can do".

    That all said, if someone wants to actually present their reasoning for adding new API instead of dithering about whether this issue is a bug or not, the appropriate thing to do is to open an ACP by filing a new issue over here: https://github.com/rust-lang/libs-team/issues

  19. tbu- commented on Nov 28, 2025

    @tbu-
    Contributor

    Opened an API change proposal: rust-lang/libs-team#705.

  20. devtempmac commented on Nov 28, 2025

    @devtempmac

    @workingjubilee well actually, I'm rather concerned about people making certain syscall usage impossible

    The debate here is whether a close API which returned Result<(), io::Error> would improve logical correctness in Rust programs, or if it would be ritualistic checking to no purpose in the general case.

    ...hmm I don't know why you would limit this discussion to that "debate topic"

    If we want File::close (or OwnedFd::close, or whatever) because we want people to write better Rust code for file handling, a stronger argument would be a roundup of the behaviors of filesystems with write-then-close. Preferably beyond just Linux, but at least for those we do have prior research.

    hmm... my view is that shouldn't be something to be dictated by the rust's stdlib
    that's something to be resolved by:

    1. adding warnings on File::close() doc-strings
    2. adding clippy/linter warnings

    also, if it's really dangerous for people to use close() instead of fsync(), maybe we should put it behind unsafe?

    (And... unsafe alone seems... too vague -- maybe we should have multiple "flavors" or "levels" of unsafe?
    eg: unsafe(DueToFFI), unsafe(DueToFFI | RAW_MEM_ACCESS), etc )

  21. ChrisDenton commented on Nov 28, 2025

    @ChrisDenton
    Member

    well actually, I'm rather concerned about people making certain syscall usage impossible

    Creating a wrapper for libc close that consumes a File is a few lines of code that does not need to be implemented inside the standard library. Nothing is made impossible either way.

  22. workingjubilee commented on Dec 5, 2025

    @workingjubilee
    Member

    @devtempmac:

    ...hmm I don't know why you would limit this discussion to that "debate topic"

    GitHub issues are a poor place for open-ended discussion, and this is an issue tracker. Please try to avoid diverging from discussing the issue into hypothetical arguments about hypothetical project managers that have hypothetical standards for filesystem handling that would ban use of Rust because its defaults are strange when considering what appears to be a single extant filesystem.

    hmm... my view is that shouldn't be something to be dictated by the rust's stdlib

    ...dictate what?

    Understanding what actual behaviors are is important for designing APIs.

    also, if it's really dangerous for people to use close() instead of fsync(), maybe we should put it behind unsafe?

    unsafe is about memory safety. This is about writing to the filesystem. A File type that doesn't write all your data because you're dealing with NFS is safe code. I realize that gets a bit strange when we start dealing with things like mmap, but notably we don't expose an mmap API in the stdlib, and even if we did it probably wouldn't be for File because not every OS thinks of mmap the same way.

  23. devtempmac commented on Dec 6, 2025

    @devtempmac

    @ChrisDenton well if the main reason is std bloat, then another crate could do.
    but that won't be good for rust adoption, unless it's somewhat semi-official (eg std-utils).
    some devs don't like using "unofficial" stuff, and could result in "hmm maybe rust std isn't 100 complete? better go for zig/go/c3c/etc"
    (I've done so many use-dtolnay-crates-to-solve-my-way, some coworkers wonder if rust-lang isn't "fit-for-production"...)

    @workingjubilee ok then -- I don't want to say this... but:
    whatever we discuss here about close(), doesn't linux man page take precedence?

    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
    

    ...have we contacted linux people (especially linus) and asked whether this no longer holds?

    this whole discussion of "platform A1 works like this, platform A2 works like that, ..."
    ...these actually don't even matter, given that that manpage still holds (not deprecated).
    some obscure platform or combination(eg musl/NFS/custom-FS/etc) we don't even know could result in exceptional cases.

    about unsafe -- if we're dictating users to not manually close result, I was hoping there was actually some valid reason (eg. handling close() result does something unsafe-ish on some platform)

  24. programmerjake commented on Feb 4, 2026

    @programmerjake
    Member

    Closing as not planned as decided by the libs-api team: rust-lang/libs-team#705 (comment)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-ioArea: `std::io` (note: `std::fs`, `std::net`, `std::path` are A-filesystem, A-net, A-path)C-bugCategory: This is a bug.T-libs-api[DEPRECATED; DO NOT USE]

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions