The normal way to safely, atomically write a file X
on Unix is:
- Write the new file contents to a temporary file
Y
. rename(2)
Y
toX
In two steps it appears that we have done nothing but change X
"in-place".
It is protected against race conditions and unintentional data loss (where X
is destroyed but Y
is incomplete or destroyed).
The drawback (in this case) of this is that it doesn't write the inode referred to by X
in-place; rename(2)
makes X
refer to a new inode number.
When X
was a file with link count > 1 (an explicit hard link), now it doesn't refer to the same inode as before, the hard link is broken.
The obvious way to eliminate the drawback is to write the file in-place, but this is not atomic, can fail, might result in data loss etc.
Is there some way to do it atomically like rename(2)
but preserve hard links?
Perhaps to change the inode number of Y
(the temporary file) to the same as X
, and give it X
's name? An inode-level "rename."
This would effectively write the inode referred to by X
with Y
's new contents, but would not break its hard-link property, and would keep the old name.
If the hypothetical inode "rename" was atomic, then I think this would be atomic and protected against data loss / races.
Best Answer
The issue
You have a (mostly) exhaustive list of systems calls here.
You will notice that there is no "replace the content of this inode" call. Modifying that content always implies:
Step 4 can be done earlier. There are some shortcuts as well, such as pwrite, which directly write at a specified offset, combining steps #2 and #3, or scatter writing.
An alternate way is to use a memory mapping, but it gets worse as every byte written may be sent to the underlying file independently (conceptually as if every write was a 1-byte
write
call).→ The point is the very best scenario you can have is still 2 operations: one
write
and onetruncate
.Whatever the order you perform them in, you still risk another process to mess with the file in between and end up with a corrupted file.
Solutions
Normal solution
As you have noted, this is why the canonical approach is to create a new file, you know you are the only writer of (you can even guarantee this by combining
O_TMPFILE
andlinkat
), then atomically redirect the old name to the new file.There are two other options, however both fail in some way:
Mandatory locking
It enables file access to be denied to other processes by setting a special flag combination. Sounds like the tool for the job, right? However:
This is only logical, as Unix has always shun away from locks. They are error prone, and it is impossible to cover all edge cases and guarantee no deadlock.
Advisory locking
It is set using the fcntl system call. However, it is only advisory, and most programs simply ignore it.
In fact it is only good for managing locks on shared file among several processes cooperating.
Conclusion
No.
Inodes are low level, almost an implementation detail. Very few APIs acknowledge their existence (I believe the
stat
family of calls is the only one).Whatever you try to do probably relies on either misusing the design of Unix filesystems or simply asking too much to it.
Could this be somewhat of an XY-problem?