Moving a directory atomically

Since Linux 3.15, the new renameat2 system call can atomically exchange two paths on the same file system. However, there’s not even a glibc wrapper for it yet, let alone a coreutils way to access it. So it would look something like this:

int dirfd = open(".../base", O_PATH | O_DIRECTORY | O_CLOEXEC);
syscall(SYS_renameat2, dirfd, "alpha", dirfd, "bravo", RENAME_EXCHANGE);
close(dirfd);
system("rm -rf alpha");

(Of course, you should do proper error handling etc. – see this gist for a more sophisticated renameat2 wrapper.)

That said – the symlink solution mentioned by others is both easier and portable, so unless bravo already exists and you must atomically update it, go with the symlink instead.


2020 update: a glibc wrapper for this system call is available since glibc 2.28, released 2018-08-01 (Debian Stretch, Fedora 29). It’s still not accessible via coreutils, though.

int dirfd = open(".../base", O_PATH | O_DIRECTORY | O_CLOEXEC);
renameat2(dirfd, "alpha", dirfd, "bravo", RENAME_EXCHANGE);
close(dirfd);
system("rm -rf alpha");

The final solution is combining the symlink- and the rename-approach:

mkdir alpha_real
ln -s alpha_real alpha

# now use "alpha"

mkdir beta_real
ln -s beta_real tmp 

# atomically rename "tmp" to "alpha"
# use -T to actually replace "alpha" instead of moving *into* "alpha"
mv -T tmp alpha

Of course, the application accessing alpha has to be able to deal with symlinks changing in the path.


Picking up on David's solution here, which is fully atomic ... the only problem you'd run into is that the -T option for mv is non-POSIX, and so certain POSIX OSes may not support it (FreeBSD, Solaris, etc. ... http://pubs.opengroup.org/onlinepubs/9699919799/utilities/mv.html). With slight modification, this approach can be altered to be fully atomic, and portable across all POSIX OSes:

mkdir -p tmp/real_dir1 tmp/real_dir2
touch tmp/real_dir1/a tmp/real_dir2/a
# start with ./target_dir pointing to tmp/real_dir1
ln -s tmp/real_dir1 target_dir
# create a symlink named target_dir in tmp, pointing to real_dir2
ln -sf tmp/real_dir2 tmp/target_dir
# atomically mv it into ./ replacing ./target_dir
mv tmp/target_dir ./

exaple via: http://axialcorps.wordpress.com/2013/07/03/atomically-replacing-files-and-directories/


You can do this if you use symlinks:

Let's say alpha is a symlink to directory alpha_1, and you want to switch the symlink to point to alpha_2. Here's what that looks like before the switch:

$ ls -l
lrwxrwxrwx alpha -> alpha_1
drwxr-xr-x alpha_1
drwxr-xr-x alpha_2

To make alpha refer to alpha_2, use ln -nsf:

$ ln -nsf alpha_2 alpha
$ ls -l
lrwxrwxrwx alpha -> alpha_2
drwxr-xr-x alpha_1
drwxr-xr-x alpha_2

Now you can remove the old directory:

$ rm -rf alpha_1

Note that this is NOT actually a fully atomic operation, but it does happen very quickly since the "ln" command both unlinks and then immediately recreates the symlink. You can verify this behaviour with strace:

$ strace ln -nsf alpha_2 alpha
...
symlink("alpha_2", "alpha")             = -1 EEXIST (File exists)
unlink("alpha")                         = 0
symlink("alpha_2", "alpha")             = 0
...

You can repeat this procedure as desired: e.g. when you have a new version, alpha_3:

$ ln -nsf alpha_3 alpha
$ rm -rf alpha_2

Tags:

Linux

Bash

Atomic