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 thatfoo
existed, then took an interrupt, context switched and handled the two unlinks from the other process, then got around to finishing handling thelink
where it left off - but I'm likely overly paranoid no sane kernel would do such a thing. – mtraceur Oct 02 '16 at 21:32