unixsymlink

Can you change what a symlink points to after it is created?


Does any operating system provide a mechanism (system call — not command line program) to change the pathname referenced by a symbolic link (symlink) — other than by unlinking the old one and creating a new one?

The POSIX standard does not. Solaris 10 does not. Mac OS X 10.5 (Leopard) does not. (I'm tolerably certain neither AIX nor HP-UX does either. Judging from this list of Linux system calls, Linux does not have such a system call either.)

Is there anything that does?

(I'm expecting that the answer is "No".)


Since proving a negative is hard, let's reorganize the question.

If you know that some (Unix-like) operating system not already listed has no system call for rewriting the value of a symlink (the string returned by readlink()) without removing the old symlink and creating a new one, please add it — or them — in an answer.


Solution

  • Actually, you can overwrite a symlink and thus update the pathname referenced by it:

    $ ln -s .bashrc test
    $ ls -al test
    lrwxrwxrwx 1 pascal pascal 7 2009-09-23 17:12 test -> .bashrc
    $ ln -s .profile test
    ln: creating symbolic link `test': File exists
    $ ln -s -f .profile test
    $ ls -al test
    lrwxrwxrwx 1 pascal pascal 8 2009-09-23 17:12 test -> .profile
    

    As the OP pointed out in a comment, using the --force option will make ln perform a system call to unlink() before symlink(). Below, the output of strace on my linux box proving it:

    $ strace -o /tmp/output.txt ln -s -f .bash_aliases test
    $ grep -C3 ^unlink /tmp/output.txt
    lstat64("test", {st_mode=S_IFLNK|0777, st_size=7, ...}) = 0
    stat64(".bash_aliases", {st_mode=S_IFREG|0644, st_size=2043, ...}) = 0
    symlink(".bash_aliases", "test")        = -1 EEXIST (File exists)
    unlink("test")                          = 0
    symlink(".bash_aliases", "test")        = 0
    close(0)                                = 0
    close(1)                                = 0
    

    The following is copied from Arto Bendiken's answer over on unix.stackexchange.com, circa 2016.

    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.

    This is how Capistrano has done it for years now, ever since ~2.15. See this pull request.