You have to consider that programs operate on a lot of exclusively owned resources, e.g. mmap'd database files. Interfering with those can lead to UB. To be able to do those things safely the hand-wavy rule is "don't touch FDs you have no right to touch". IO-safety rules are a bit more formalized version of that.
For an inherited resource someone has to decide whether it's a single-owner that grabs it at program startup or a shared resource. Someone has to make that decision, Rust can't make it for you and the implicit IPC protocols are underspecified in that area, so the programmer has to make a choice. This results in the need for unsafe code where the programmer makes the choice and then has to ensure that it is upheld for the rest of the program.
try_clone exists and is a "safe" function. In other words two threads could get access to a single file description and modify it simultaneously which, while technically within the posix spec, would still result in race conditions if both tried to edit/seek/modify their separate "owned" instances at the same time.
You don't need try_clone for that, you can share a &File across threads and get concurrent access to the same fd.
This isn't unsafe and it's perfectly fine in many cases such as O_APPEND files, datagram sockets, eventfd, ...
because inheriting file descriptors is both possible and frequent in all major operating systems, yet rusts' abstractions make the feature universally unsafe so either a developer must give up a fairly major part of I/O or they must break the guarantees that rust makes about file descriptors, and
It's somewhat possible, but hard to get right. File descriptors are shared global program state. So all parts of the program have to agree on ownership. And if you're coordinating through environment variables then the FD state needs to stay consistent with the environment variables.
So recommended patterns would be:
- use unix domain sockets in the filesystem or abstract-namespace, then use SCM_RIGHTS for passing to a single receiver
- or on linux:
- pass as a path to
/proc/<other process id>/fd/Ninstead of inheritance - use
pidfd_getfd()
- pass as a path to
- do the setup early during single-threaded main
- add safety nets such as including dev/ino in the environment variable so that a child can crosscheck
- Afaik systemd do this in some cases
- use
BorrowedFd<'static>instead ofOwnedFdif it's a shared resource - in cases where you take ownership
- if you set O_CLOEXEC also unset the environment variables
- also use
try_clone_to_owned/try_cloneto renumber the Fd and close the original to make incorrect shared access more obvious