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
end

This 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.

Posted in , ,
Tags , ,
5 comments
no trackbacks
Reddit Delicious

Comments

  1. Eiki Martinson said 778 days later:

    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?

  2. Tom Moertel said 778 days later:

    Darn! I forgot the -T flag on mv. Fixed.

    Thanks for the catch, Eiki!

    Cheers,
    Tom

  3. Eiki Martinson said 819 days later:

    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!

  4. anon said 925 days later:

    Excellent post! Thanks :)

  5. anon said 955 days later:

    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.

Trackbacks

Use the following link to trackback from your own site:
http://blog.moertel.com/articles/trackback/49

(leave url/email »)

   Comment Markup Help Preview comment