]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
mountpoint-util: make fd_is_mountpoint() work reasonably with AT_EMPTYPATH
authorLennart Poettering <lennart@poettering.net>
Tue, 2 Aug 2022 11:58:09 +0000 (13:58 +0200)
committerDaan De Meyer <daan.j.demeyer@gmail.com>
Tue, 2 Aug 2022 18:01:59 +0000 (20:01 +0200)
Inspired by: https://github.com/systemd/systemd/pull/24141

Calling fd_is_mountpoint() with AT_EMPTYPATH and an empty filename can
only work if we have new statx() available. If we do not, we can still
make things work for directories, but not for other inodes (since there
we cannot query information about the parent inode to compare things.)

Hence, let's handle and test this explicitly, to support this to the
level this is possible.

src/basic/mountpoint-util.c
src/test/test-mountpoint-util.c

index 2e451085beccf48e83f48bdad6e56ea751fca980..c7468e2a4526901a2d11603be70f6dbcd94fabdc 100644 (file)
@@ -182,12 +182,18 @@ int fd_is_mount_point(int fd, const char *filename, int flags) {
         int r;
 
         assert(fd >= 0);
-        assert(filename);
-        assert((flags & ~(AT_SYMLINK_FOLLOW|AT_EMPTY_PATH)) == 0);
+        assert((flags & ~AT_SYMLINK_FOLLOW) == 0);
 
-        /* Insist that the specified filename is actually a filename, and not a path, i.e. some inode further
-         * up or down the tree then immediately below the specified directory fd. */
-        if (!filename_possibly_with_slash_suffix(filename))
+        if (!filename) {
+                /* If the file name is specified as NULL we'll see if the specified 'fd' is a mount
+                 * point. That's only supported if the kernel supports statx(), or if the inode specified via
+                 * 'fd' refers to a directory. Otherwise, we'll have to fail (ENOTDIR), because we have no
+                 * kernel API to query the information we need. */
+                flags |= AT_EMPTY_PATH;
+                filename = "";
+        } else if (!filename_possibly_with_slash_suffix(filename))
+                /* Insist that the specified filename is actually a filename, and not a path, i.e. some inode further
+                 * up or down the tree then immediately below the specified directory fd. */
                 return -EINVAL;
 
         /* First we will try statx()' STATX_ATTR_MOUNT_ROOT attribute, which is our ideal API, available
@@ -234,7 +240,10 @@ int fd_is_mount_point(int fd, const char *filename, int flags) {
                 nosupp = true;
         }
 
-        r = name_to_handle_at_loop(fd, "", &h_parent, &mount_id_parent, AT_EMPTY_PATH);
+        if (isempty(filename))
+                r = name_to_handle_at_loop(fd, "..", &h_parent, &mount_id_parent, 0); /* can't work for non-directories ðŸ˜¢ */
+        else
+                r = name_to_handle_at_loop(fd, "", &h_parent, &mount_id_parent, AT_EMPTY_PATH);
         if (r < 0) {
                 if (is_name_to_handle_at_fatal_error(r))
                         return r;
@@ -271,7 +280,10 @@ fallback_fdinfo:
         if (r < 0)
                 return r;
 
-        r = fd_fdinfo_mnt_id(fd, "", AT_EMPTY_PATH, &mount_id_parent);
+        if (isempty(filename))
+                r = fd_fdinfo_mnt_id(fd, "..", 0, &mount_id_parent); /* can't work for non-directories ðŸ˜¢ */
+        else
+                r = fd_fdinfo_mnt_id(fd, "", AT_EMPTY_PATH, &mount_id_parent);
         if (r < 0)
                 return r;
 
@@ -295,7 +307,11 @@ fallback_fstat:
         if (S_ISLNK(a.st_mode)) /* Symlinks are never mount points */
                 return false;
 
-        if (fstatat(fd, "", &b, AT_EMPTY_PATH) < 0)
+        if (isempty(filename))
+                r = fstatat(fd, "..", &b, 0);
+        else
+                r = fstatat(fd, "", &b, AT_EMPTY_PATH);
+        if (r < 0)
                 return -errno;
 
         /* A directory with same device and inode as its parent? Must be the root directory */
index 6daab022aa15cc7cf8b9afdc8deb562f4c8b3fed..bf0de0c4c78ab1b02c55bf638b2893ba75416227 100644 (file)
@@ -265,6 +265,7 @@ TEST(path_is_mount_point) {
 
 TEST(fd_is_mount_point) {
         _cleanup_close_ int fd = -1;
+        int r;
 
         fd = open("/", O_RDONLY|O_CLOEXEC|O_DIRECTORY|O_NOCTTY);
         assert_se(fd >= 0);
@@ -290,6 +291,22 @@ TEST(fd_is_mount_point) {
          * the system is borked. Let's allow for it to be missing though. */
         assert_se(IN_SET(fd_is_mount_point(fd, "root", 0), -ENOENT, 0));
         assert_se(IN_SET(fd_is_mount_point(fd, "root/", 0), -ENOENT, 0));
+
+        safe_close(fd);
+        fd = open("/proc", O_RDONLY|O_CLOEXEC|O_DIRECTORY|O_NOCTTY);
+        assert_se(fd >= 0);
+
+        assert_se(fd_is_mount_point(fd, NULL, 0) > 0);
+        assert_se(fd_is_mount_point(fd, "", 0) == -EINVAL);
+        assert_se(fd_is_mount_point(fd, "version", 0) == 0);
+
+        safe_close(fd);
+        fd = open("/proc/version", O_RDONLY|O_CLOEXEC|O_NOCTTY);
+        assert_se(fd >= 0);
+
+        r = fd_is_mount_point(fd, NULL, 0);
+        assert_se(IN_SET(r, 0, -ENOTDIR)); /* on old kernels we can't determine if regular files are mount points if we have no directory fd */
+        assert_se(fd_is_mount_point(fd, "", 0) == -EINVAL);
 }
 
 static int intro(void) {