Moving a directory atomically
Asked Answered
A

16

52

I have two directories in the same parent directory. Call the parent directory base and the children directories alpha and bravo. I want to replace alpha with bravo. The simplest method is:

rm -rf alpha
mv bravo alpha

The mv command is atomic, but the rm -rf is not. Is there a simple way in bash to atomically replace alpha with bravo? If not, is there a complicated way?

ADDENDUM:

By the by, it's not an insurmountable problem if the directory doesn't exist for a short period. There's only one place that tries to access alpha, and it checks if alpha exists before doing anything critical. If not, it gives an error message. But it would be nice if there was a way to do this. :) Maybe there's some way to modify the inodes directly, or something...

Archaism answered 21/11, 2008 at 0:48 Comment(1)
Your test in the addendum isn't safe - there is a race condition. Consider what happens if the check runs first (and Alpha exists) and then it is switched out while the second process deletes Alpha, and then it is switched back in to continue the run, with Alpha now missing.Humidor
D
19

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
Dissociable answered 21/11, 2008 at 1:28 Comment(5)
linux VFS doesn't support multiple directory hardlinks. Some other *nixes have limited support, restricted to the superuser. You also still would have to collect all the now orphaned links of the subdirectories and files.Antibaryon
Yes, it should be a soft link to be generally applicable. I have edited my response. However I don't believe there will be any orphans as long as alpha is always a link, which is what I meant by changing the question slightly. Of course, you will always have to remove the previous version of the dirDissociable
Very close; turns out you need the -n flag too, otherwise you'll end up creating a symlink under the original directory. I actually tried your idea before posting the question, and it didn't work, but when I looked again and noticed the -n flag, that did it. Also, poop on whoever voted you down :)Archaism
process A tries to do something within alpha whatever you do after this point might be atomic or not, you can still erase the directory while it is used. Being atomic is useless, what you need is serialisation, not atomicity, unless your code accessing alpha is also atomic.Metalline
@Archaism Why did you accept this answer? It is clearly not atomic.Atticism
B
69

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.

Balfour answered 4/6, 2012 at 19:15 Comment(1)
rcrowley.org/2010/01/06/things-unix-can-do-atomically.html for an explanation of the -T flag of mv that allows swapping of two symlinks atomically.Action
K
27

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");

2024 update: util-linux v2.40, released 2024-03-27 (announcement), features a new exch program which atomically exchanges two paths (using RENAME_EXCHANGE).

cd .../base
exch alpha bravo
Kirsten answered 15/5, 2018 at 15:5 Comment(2)
Thanks for pointing this out! While not portable, this helps me in a situation where atomically moving a symlink is not enough.Polito
C source for util-linux exch: github.com/util-linux/util-linux/blob/master/misc-utils/exch.cHennessey
D
19

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
Dissociable answered 21/11, 2008 at 1:28 Comment(5)
linux VFS doesn't support multiple directory hardlinks. Some other *nixes have limited support, restricted to the superuser. You also still would have to collect all the now orphaned links of the subdirectories and files.Antibaryon
Yes, it should be a soft link to be generally applicable. I have edited my response. However I don't believe there will be any orphans as long as alpha is always a link, which is what I meant by changing the question slightly. Of course, you will always have to remove the previous version of the dirDissociable
Very close; turns out you need the -n flag too, otherwise you'll end up creating a symlink under the original directory. I actually tried your idea before posting the question, and it didn't work, but when I looked again and noticed the -n flag, that did it. Also, poop on whoever voted you down :)Archaism
process A tries to do something within alpha whatever you do after this point might be atomic or not, you can still erase the directory while it is used. Being atomic is useless, what you need is serialisation, not atomicity, unless your code accessing alpha is also atomic.Metalline
@Archaism Why did you accept this answer? It is clearly not atomic.Atticism
S
19

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/

Setose answered 5/7, 2013 at 15:55 Comment(0)
H
8

Use a separate, guaranteed atomic, operation to act as a semaphore.

So, if the creating and removing a file operations are atomic:

1) create a file called "semaphore".

2) If and only if that is successful (no conflict with existing file), do the operation (either process alpha or move the directory, depending on the process)

3) rm semaphore.

Humidor answered 21/11, 2008 at 1:33 Comment(3)
that would only help if any operation supposed to happen on alpha is re-written to check for semaphore first and wait for being able to lock the semaphore itself .. and if they prevent you from creating your semaphore when they start their own operation.Action
@PypeBros: Yes. If you don't check it before you do the operation, it isn't being used as a semaphore. If it can be created by two concurrent processes, it isn't a semaphore.Humidor
This is a great complement to the other answers - this is likely the only way you can truly make the process atomic.Buffybuford
H
6

If you mean atomic across both operations, I don't believe so. The closest would be:

mv alpha delta
mv bravo alpha
rm -rf delta

but that would still have a small window where alpha didn't exist.

To minimize the likelihood of anything trying to use alpha while it's not there you could (if you have the authority):

nice --20 ( mv alpha delta ; mv bravo alpha )
rm -rf delta

which will crank up your process priority substantially while the mv operations are happening.

If, as you say in your addendum, there's only one place that checks alpha and it errors if it's not there, you could change that code to not error immediately, but try again in a short time (easily sub-second for two mv operations) - these retries should alleviate any problem unless you're replacing alpha very frequently.

Hereunto answered 21/11, 2008 at 0:51 Comment(1)
That's about as fast as you can do it in the shell; you could write a custom piece of C to move the two directories which would shave a few milliseconds off the time interval, or use a Perl script (or choose your own poison). There'd be no point in rewriting 'rm -fr', though.Hagbut
E
5

This should do the trick:

mkdir bravo_dir alpha_dir
ln -s bravo_dir bravo
ln -s alpha_dir alpha
mv -fT bravo alpha

strace mv -fT bravo alpha shows:

rename("bravo", "alpha")

which looks pretty atomic to me.

Epsomite answered 6/1, 2014 at 21:50 Comment(1)
This is the exact same solution that was already posted two years earlier by David Schmitt. See above.Herrin
A
4

The SQLite documentation section File Locking and Concurrency in SQLite Version 3 has a well-written description of its escalating locking protocol to control concurrent reading, exclusive writing, and rollback after a crash. Some of those ideas apply here.

Archibold answered 7/11, 2009 at 23:0 Comment(0)
T
1

Even if you were accessing the inodes directly there would still be no way to atomically swap the inode values in user-space.

Thaumaturgy answered 21/11, 2008 at 1:19 Comment(0)
B
1

Worrying about the atomic nature of the operation is meaningless. The thing is, the access to alpha by the other task will not be atomic anyway.

Oddthinking's semaphore approach is the only way to go.

If you can't modify the other task then you'll have to ensure it's not running before doing the replacement.

Bosanquet answered 21/11, 2008 at 4:17 Comment(0)
E
1

mount --bind bravo alpha should do this on Linux

It leaves the contents of alpha hidden but you can bind mount the parent filesystem elsewhere if you want to clean it out.

If the filesystem is NFS exported you would likely need to ensure the export options allowed crossing filesystem boundaries and do the bind mount on the server.

You also need to be careful about processes that have an open handle on the alpha directory or subdirectory (eg cwd).

Other *nix may have similar tricks up their sleeves but it isn't standardised.

Ey answered 1/11, 2019 at 15:24 Comment(0)
R
0

I don't believe there's any atomic way to do this. Your best bet is to do something like this:

mv alpha delme
mv bravo alpha
rm -rf delme
Resolvable answered 21/11, 2008 at 0:51 Comment(0)
S
0

Something to keep in mind is that if your process has any of the files in alpha open when this move/delete occurs the process will not notice and any data written will be lost when the file is closed and finally removed.

Scopoline answered 21/11, 2008 at 3:38 Comment(0)
S
0

mv and ln can be used for atomic operations. I've used ln(1) to deploy web applications atomically.

The correct way to replace a symlink is with ln -nsf

ln -nsf <target> <link_name>

e.g.

$ mkdir dir1
$ mkdir dir2
$ ln -s dir1 mylink
$ ls -l mylink
lrwxrwxrwx  1 phil phil 4 Nov 16 14:45 mylink -> dir1
$ ln -nsf dir2 mylink
$ ls -l mylink
lrwxrwxrwx  1 phil phil 4 Nov 16 14:46 mylink -> dir2
Scarificator answered 16/11, 2009 at 14:48 Comment(1)
ln -nsf is fast but it's not actually atomic. The ln process actually unlinks and then immediately recreates the symlink. You can verify this with strace. See blog.moertel.com/articles/2005/08/22/…Jaynes
D
0

It is also possible to replace a whole parts of the content at once in some prefix (Z here) using unionfs-fuse:

# mkdir a b c Z
# touch a/1 b/2 c/3
# ln -s a X
# ln -s b Y
# unionfs X=RW:Y=RW Z
# shopt -s globstar
# file **
a:   directory
a/1: empty
b:   directory
b/2: empty
c:   directory
c/3: empty
X:   symbolic link to a
Y:   symbolic link to b
Z:   directory
Z/1: empty
Z/2: empty
# ln -sfn c Y
# file **/*
a:   directory
a/1: empty
b:   directory
b/2: empty
c:   directory
c/3: empty
X:   symbolic link to a
X/1: empty
Y:   symbolic link to c
Y/3: empty
Z:   directory
Z/1: empty
Z/3: empty
# fusermount -u Z
# rm -r a b c X Y Z
Derringdo answered 12/1, 2018 at 20:9 Comment(0)
A
-2

Why don't you just do something like:

rm -rf alpha/*
mv bravo/* alpha/
rm -rf bravo/

That would mean that everything in alpha is destroyed, alpha never gets deleted, and all the contents get moved.

Ashlynashman answered 21/11, 2008 at 4:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.