Question
Suppose I have some non-directory (file, named pipe/socket, whatever) at the pathname /tmp/foo and some other non-directory at the pathname /tmp/bar. Then two (or more) processes start executing concurrently:
Process one does:
unlink('/tmp/foo') /* or rename('/tmp/foo', '/tmp/removed') */
unlink('/tmp/bar') /* or rename('/tmp/bar', '/tmp/removed') */
Process two (and so on) does:
link('/tmp/foo', '/tmp/bar')
As I understand it, there is no way process two could possibly succeed (either the link(2) is attempted while /tmp/foo is still present, in which case /tmp/bar is also present so it must fail with EEXIST, or /tmp/foo is gone so is must fail with ENOENT).
But this intuition relies on the assumption that the unlink(2) and/or rename(2) system calls are inherently sequential in their unlinking effects, so I am looking for verification of my understanding: Is there any *nix-like system out there whose kernel allows the two unlink(2) and/or rename(2) calls to succeed, but simultaneously causes link(2) to succeed as well (whether due to re-order the unlinking of /tmp/foo and /tmp/bar and not abstracting/hiding that from the process calling link(2), or through through some other quirky race condition/bug)?
Current Understanding
I have read the manpages for unlink(2), rename(2), and link(2) for Linux and a few BSDs, and the POSIX specification for these functions. But I don't think they actually contain anything reassuring on this matter, upon careful consideration. At least with rename(2), we're promised that the destination is atomically replaced if it's already present (bugs in the OS itself aside), but nothing else.
I have seen claims that multiple simultaneous executions of rename(foo, qux) will atomically and portably have all but one rename fail with ENOENT - so that's promising! I am just uncertain if that can be extended to having a link(foo, bar) fail with ENOENT under the same circumstances as well.
Preferred Answers
I realize that this is one of those "can't prove a negative" situations - we can at best only note that there is no evidence that a *nix-like system which will allow process two's link(2) to succeed exists.
So what I'm looking for is answers covering as many *nix-like systems as possible (at least Linux, OS X, and the various BSDs, but ideally also the proprietary still-in-some-use systems like Solaris 10) - from people who have sufficient familiarity with these systems and this narrow set of problems (atomic/well-ordered file system operations) that they're confident (as much as one realistically can be) that they'd know of issues like the aforementioned Mac OS X rename(2)-not-actually-atomic bug if they existed on the platforms they're familiar with. That would give me enough confidence that this works the way I think it does in a portable-enough manner to rely on.
Final Note
This isn't an "X/Y problem" question - there's no underlying problem that can be answered by referring me to the various locking/IPC mechanisms or something else that works around the uncertainty about how these particular system calls interact: I specifically want to know if one can rely on the above system calls portably interacting as expected across *nix-like systems in practical use today.
unlink(foo) + unlink(bar)would leave the ordering of the system calls up to the the compiler..) – ilkkachu Oct 02 '16 at 21:22link(2). Since when two processes callrename(2)on the same source path, the kernel can keep track and causes one of them to fail withENOENT, even if the I/O for the operation isn't scheduled to be done until later (and most *nix-likes seem to do just that - it's just a matter of whether that logical consistency extends tounlink(2)andlink(2)). – mtraceur Oct 02 '16 at 21:22link, checked thatfooexisted, then took an interrupt, context switched and handled the two unlinks from the other process, then got around to finishing handling thelinkwhere it left off - but I'm likely overly paranoid no sane kernel would do such a thing. – mtraceur Oct 02 '16 at 21:32