How to change symlinks atomically
Posted by Tom Moertel Mon, 22 Aug 2005 16:00:00 GMT
Many people don’t realize that changing the target of a symbolic link (symlink) is not an atomic operation. “Changing” a symlink really means deleting it and creating a new link with the same file name. For example, if I have a symlink current that points to a directory old, and I want to change it to point to a directory new, I might use the following command:
$ ln -snf new current
Strace shows what really happens when I run the command:
$ strace ln -snf new current 2>&1 | grep link
unlink("current") = 0
symlink("new", "current") = 0
First, the existing symlink is deleted via the unlink system call. Then a new, identically named symlink is created via the symlink system call. It’s a two-step process, and in between the steps, there is no symlink.
This can be a problem if you expect the symlink to be there always, such as when using the link to point to the active version of a live web site. If you change the symlink while deploying a new version of your site, for example, the web server might try to dereference the link during the small window of time when it doesn’t exist. Oops.
The solution to this problem is to effect the change by creating a new symlink and then renaming it over the old symlink. On Unix-like systems, renaming is an atomic operation, and thus the symlink “change” will be atomic too. By hand, the process looks like this:
$ ln -s new current_tmp && mv -Tf current_tmp current
In Ruby, I make atomic symlinking available everywhere by extending the Pathname class with a new method atomic_symlink:
require 'pathname'
class Pathname
def atomic_symlink(old)
suffix = [Array.new(6){rand(256).chr}.join].pack("m").strip.tr('/','_');
tmplink = Pathname.new(self.to_s + "_" + suffix)
tmplink.make_symlink(old)
begin
tmplink.rename(self)
rescue
# if rename fails, we must remove the temporary link manually
File.unlink(tmplink.to_s)
raise
end
end
endThis code is nothing more than a robustified version of the by-hand method. It picks better names for temporary links, and it cleans up after itself, should something go wrong, but otherwise it does the same thing.
Given how easy it is to change symlinks atomically, why do it any other way? Life is hard enough without having to worry about another race condition.
readers
Nice tip, but the manual example doesn’t work – it just moves the current_tmp symlink into the directory pointed to by current.
Any idea how to fix it?
Darn! I forgot the -T flag on mv. Fixed.
Thanks for the catch, Eiki!
Cheers,
Tom
Ah, okay. I’ve been doing it with a line of perl instead, like: perl -e ‘rename(“current_tmp”, “current”)’
But I think I like yours better.
Thanks!
Excellent post! Thanks :)
The ‘-T’ in ‘mv -T’ is kind of recent, an alternative for older systems is the slightly gaudier
‘rename current_tmp current current_tmp’
Also passes the ‘strace’ atomic test in that it calls rename(2) w/o calling unlink(2) first.
3 years down the road and still informative. Thanks for the post.
I think you just saved me a lot of hassle with this post. Sending it though our office now :)
Thanks Tom,
I think this will let me revert back to a libc.so.6 pointing to libc-2.2.5.so rather than libc-3.2.2.so without breaking my system and forcing me to roll out my monitor/kbd/mouse for my KVM switch.
Many thanks,
-Zoid http://www.givemeopensourceordeath.net