From: Karel Zak Date: Thu, 26 Sep 2024 12:44:36 +0000 (+0200) Subject: libmount: support bind symlink over symlink X-Git-Tag: v2.42-start~187^2~2 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=22cda2f71cdf2c75ac01cdbedf3a57b146186812;p=thirdparty%2Futil-linux.git libmount: support bind symlink over symlink 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 --- diff --git a/libmount/src/context.c b/libmount/src/context.c index 28cce65e8..96eb32d02 100644 --- a/libmount/src/context.c +++ b/libmount/src/context.c @@ -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" : "")); diff --git a/libmount/src/context_umount.c b/libmount/src/context_umount.c index 499cc43ac..64a1d884b 100644 --- a/libmount/src/context_umount.c +++ b/libmount/src/context_umount.c @@ -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);