18

I am trying to (as close as possibly) atomically change a symlink. I've tried:

ln -sf other_dir existing_symlink

That just put the new symlink in the directory that existing_symlink pointed to.

ln -sf other_dir new_symlink
mv -f new_symlink existing_symlink

That did the same thing: it moved the symlink into the directory.

cp -s other_dir existing_symlink

It refuses because it's a directory.

I've read that mv -T is was made for this, but busybox doesn't have the -T flag.

1

I don't see how you can get atomic operation. The man page for symlink(2) says it gives EEXIST if the target already exists. If the kernel doesn't support atomic operation, your userland limitations are irrelevant.

I also don't see how mv -T helps, even if you have it. Try it on a regular Linux box, one with GNU mv:

$ mkdir a b
$ ln -s a z
$ mv -T b z
mv: cannot overwrite non-directory `z' with directory `b'

I think you're going to have to do this in two steps: remove the old symlink and recreate it.

  • 1
    Indeed, there is unfortunately no way you can modify a symbolic atomically. The best you can do is remove the old link and create a new one. GNU coreutils has an option to do this with a single command (ln -snf), but there are still two system calls under the hood. – Gilles 'SO- stop being evil' Dec 22 '10 at 19:13
43

This can indeed be done atomically with rename(2), by first creating the new symlink under a temporary name and then cleanly overwriting the old symlink in one go. As the man page states:

If newpath refers to a symbolic link the link will be overwritten.

In the shell, you would do this with mv -T as follows:

$ mkdir a b
$ ln -s a z
$ ln -s b z.new
$ mv -T z.new z

You can strace that last command to make sure it is indeed using rename(2) under the hood:

$ strace mv -T z.new z
lstat64("z.new", {st_mode=S_IFLNK|0777, st_size=1, ...}) = 0
lstat64("z", {st_mode=S_IFLNK|0777, st_size=1, ...}) = 0
rename("z.new", "z")                    = 0

Note that in the above, both mv -T and strace are Linux-specific.

On FreeBSD, use mv -h alternately.

  • 1
    Very nice! It might be helpful to say that at the end you have a link from z to b! – Vincenzo Pii May 11 '16 at 7:44
  • For an OS-independent solution use a scripting language which is capable to to use the rename syscall directly instead of mv -h or mv -T. For example with Perl: perl -e 'rename "z.new", "z" or die $!' – Slaven Rezic Apr 4 '18 at 12:30
8

Picking up where Arto left off here, this is entirely possible, even without mv -T, you just need to create a new symlink with the same name as the target directory and mv it into the parent directory of your target:

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

Code example taken via (http://axialcorps.wordpress.com/2013/07/03/atomically-replacing-files-and-directories/)

3

Have you tried ln -snf?

The option -n overwrites the destination rather than writing under it when the destination is a symbolic link to a directory.

Cheers

  • 3
    ln -snf is not atomic: it unlinks the destination, then creates the desired symlink. – Gilles 'SO- stop being evil' Aug 13 '11 at 12:37
  • 2
    Given that the OP was interested in getting "as close as possib[e]" to atomically changing a symlink, this is a perfectly reasonable answer. If there is a better one that can get closer (or be) atomic, that one can be accepted. I don't think there's a need to downvote. – Wilco Oct 29 '13 at 18:51

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.