How does one atomically change a symlink to a directory in busybox?

11,719

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

Solution 2

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.

Solution 3

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

Solution 4

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

Share:
11,719

Related videos on Youtube

Shawn J. Goff
Author by

Shawn J. Goff

I've grown up with computers around me my whole life. I started programming when I was a kid, and stuck with it ever since. I came across Linux around 1999 and have enjoyed working with it ever since. For a job, I get to write software that runs on embedded Linux devices. I do everything from working on low-level drivers to writing shell scripts and even web apps.

Updated on September 17, 2022

Comments

  • Shawn J. Goff
    Shawn J. Goff almost 2 years

    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.

  • Gilles 'SO- stop being evil'
    Gilles 'SO- stop being evil' over 13 years
    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'
    Gilles 'SO- stop being evil' almost 13 years
    ln -snf is not atomic: it unlinks the destination, then creates the desired symlink.
  • Alan47
    Alan47 over 10 years
    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.
  • Vincenzo Pii
    Vincenzo Pii about 8 years
    Very nice! It might be helpful to say that at the end you have a link from z to b!
  • Slaven Rezic
    Slaven Rezic about 6 years
    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 $!'
  • vijay
    vijay almost 3 years
    @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).
  • osexp2003
    osexp2003 over 2 years
    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"