The new mount API allows for the use of AT_SYMLINK_NOFOLLOW when
opening a mount tree (aka the "mount source" for libmount).
As a result, you can now replace one symlink with another by using a
bind mount.
By default, the mount(8) command follows symlinks and canonicalizes
all paths. However, with the X-mount.nocanonicalize=source option, it
is possible to open the symlink itself. Similarly, with the
X-mount.nocanonicalize=target option, the path of the mount point can
be kept as the original symlink. (Using X-mount.nocanonicalize without
any argument works for both the "source" and "target".)
Example:
# file /mnt/test/symlinkA /mnt/test/symlinkB
/mnt/test/symlinkA: symbolic link to /mnt/test/fileA
/mnt/test/symlinkB: symbolic link to /mnt/test/fileB
# strace -e open_tree,move_mount \
./mount --bind -o X-mount.nocanonicalize /mnt/test/symlinkA /mnt/test/symlinkB
...
open_tree(AT_FDCWD, "/mnt/test/symlinkA", OPEN_TREE_CLONE|OPEN_TREE_CLOEXEC|AT_SYMLINK_NOFOLLOW) = 3
move_mount(3, "", AT_FDCWD, "/mnt/test/symlinkB", MOVE_MOUNT_F_EMPTY_PATH) = 0
# ls -la /mnt/test/symlinkB
lrwxrwxrwx 1 root root 15 Sep 26 13:41 /mnt/test/symlinkB -> /mnt/test/fileA
The result is that 'symlinkB' is still a symlink, but it now points to
a different file.
This commit also modifies umount(8) because it does not work with
symlinks by default. The solution is to call umount2(UMOUNT_NOFOLLOW)
for symlinks after a failed regular umount(). For example:
# strace -e umount,umount2 \
./umount /mnt/test/symlinkB
...
umount2("/mnt/test/symlinkB", 0) = -1 EINVAL (Invalid argument)
umount2("/mnt/test/symlinkB", UMOUNT_NOFOLLOW) = 0
Signed-off-by: Karel Zak <kzak@redhat.com>
if (cxt->force_clone)
oflg |= OPEN_TREE_CLONE;
+ if (mnt_context_is_xnocanonicalize(cxt, "source"))
+ oflg |= AT_SYMLINK_NOFOLLOW;
+
DBG(CXT, ul_debugobj(cxt, "open_tree(path=%s%s%s)", path,
oflg & OPEN_TREE_CLONE ? " clone" : "",
oflg & AT_RECURSIVE ? " recursive" : ""));
if (mnt_context_is_fake(cxt))
rc = 0;
else {
+ struct stat st;
+
rc = flags ? umount2(target, flags) : umount(target);
+
+ if (rc < 0
+ && errno == EINVAL
+ && !(flags & UMOUNT_NOFOLLOW)
+ && !mnt_context_is_restricted(cxt)
+ && mnt_safe_lstat(target, &st) == 0 && S_ISLNK(st.st_mode))
+ rc = umount2(target, flags | UMOUNT_NOFOLLOW);
+
if (rc < 0)
cxt->syscall_status = -errno;
free(tgtbuf);