From: Linus Torvalds (torvalds@klaava.Helsinki.FI)
Date: 01/03/93


From: torvalds@klaava.Helsinki.FI (Linus Torvalds)
Subject: Re: BUG of feature: cannot move `foo' accross file systems
Date: Sun, 3 Jan 1993 21:41:55 GMT

In article <1993Jan3.205523.23937@klaava.Helsinki.FI> kankkune@klaava.Helsinki.FI (Risto Kankkunen) writes:
>>that's a feature, not a bug.
>>many *nix'es don't allow that.
>
>OK, I can live with that, but is there any reason for this? I haven't
>checked the sources (yet), but I think disallowing just requires extra
>code with no benefits. Afterall, symlink is just a file containing the
>path name of the file it points to.

There is no good way to handle this in the kernel: moving files (even
symbolic links) across filesystems is pretty much impossible to do
cleanly as different fs's can have different filesystem semantics, and
the kernel proper (or rather, the vfs layer) doesn't know about them
(nor does it care - the idea with the vfs is exactly to allow different
filesystems to have different opinions of what is going on).

It's very easy to fake this on a user level: either in the 'rename()'
library call or just in the 'mv' binary. The latter is probably the
preferred way, if only because it serves for most people, while not
cluttering up the library for other binaries (besides, handling the
error cases might be ugly in the library, while 'mv' would know what to
do).

Naturally, any non-kernel solution (be it a library or mv change), is
not guaranteed to be atomic, but with symbolic links this is probably
not something you usually have to worry about. And a move across
filesystems would be hard to make atomic even in the kernel, even if it
was something I though was worth it. It's easy enough to fake with
something like this:

        struct stat stat;

        if (lstat(oldname,&stat)) {
                perror("lstat");
                exit(1);
        }
        if (S_ISLINK(stat.st_mode)) {
                char buffer[1024];

                if (readlink(oldname,buffer,sizeof(buffer)) < 0) {
                        perror("readlink");
                        exit(1);
                }
                if (symlink(buffer, newfile)) {
                        perror("symlink");
                        exit(1);
                }
                if (unlink(oldname)) {
                        perror("unlink");
                        exit(1);
                }
                return 0;
        }

Note that GNU mv already does something similar with regular files (ie
it copies them across filesystems and then unlinks the original file if
the copy was successfull), but symbolic links aren't the only thing mv
won't touch. Directories, named pipes and any other special files share
their non-movability with symlinks - understandable, as moving them can
result in funny behavior (especially trying to move a directory by
copying it and then removing it is so fraught with race-conditions that
it's not really worth it).

                Linus