25

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

4 Answers 4

56

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.

2
  • 1
    Very nice! It might be helpful to say that at the end you have a link from z to b! Commented May 11, 2016 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 $!' Commented Apr 4, 2018 at 12:30
12

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

9

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
  • 6
    ln -snf is not atomic: it unlinks the destination, then creates the desired symlink. Commented Aug 13, 2011 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
    Commented Oct 29, 2013 at 18:51
  • 2
    Not sure for busybox, but I have confirmed by strace that on modern linux distribution such as Ubuntu Bionic, the ln -snf works atomically, it does not call unlink, it will do actually "ln -s source a_tmp_file && mv -T a_tmp_file symlink"
    – osexp2000
    Commented Feb 12, 2022 at 7:30
2

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.

2
  • 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. Commented Dec 22, 2010 at 19:13
  • @Gilles'SO-stopbeingevil' It looks like it is possible using the 2 techniques in the other answers. 1) use rename system call with mv -T, or 2) use mv to move the new symlink with the correct (target) name already, located in a temp dir in the same filesystem as the target dir, to the target dir (containing the target symlink).
    – jrw32982
    Commented Sep 8, 2021 at 0:12

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .