]> git.ipfire.org Git - thirdparty/util-linux.git/commitdiff
libmount: support bind symlink over symlink
authorKarel Zak <kzak@redhat.com>
Thu, 26 Sep 2024 12:44:36 +0000 (14:44 +0200)
committerKarel Zak <kzak@redhat.com>
Thu, 26 Sep 2024 12:44:36 +0000 (14:44 +0200)
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>
libmount/src/context.c
libmount/src/context_umount.c

index 28cce65e8341e55380db02bc8257f4733393bae8..96eb32d0296162937b1df4b676034fca9ba86351 100644 (file)
@@ -1854,6 +1854,9 @@ int mnt_context_open_tree(struct libmnt_context *cxt, const char *path, unsigned
        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" : ""));
index 499cc43acd2d9f5c2739cb3e786f9649e8d62868..64a1d884ba0f7400cafa43a7f0ec06f2917e79e6 100644 (file)
@@ -887,7 +887,17 @@ static int do_umount(struct libmnt_context *cxt)
        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);