]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
mount-util: fix fd_is_mount_point() when both the parent and directory are network fs
authorFranck Bui <fbui@suse.com>
Thu, 30 Sep 2021 12:05:36 +0000 (14:05 +0200)
committerZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
Fri, 1 Oct 2021 09:11:45 +0000 (11:11 +0200)
The second call to name_to_handle_at_loop() didn't check for the specific
errors that can happen when the parent dir is mounted by nfs and instead of
falling back like it's done for the child dir, fd_is_mount_point() failed in
this case.

src/basic/mountpoint-util.c

index 8c836a1b74e6f6e8582a146fa933476ee1c8d9b7..e7a5a995514ad4ae505213a87b6e2957e63e5bc6 100644 (file)
@@ -157,6 +157,19 @@ static bool filename_possibly_with_slash_suffix(const char *s) {
         return filename_is_valid(copied);
 }
 
+static bool is_name_to_handle_at_fatal_error(int err) {
+        /* name_to_handle_at() can return "acceptable" errors that are due to the context. For
+         * example the kernel does not support name_to_handle_at() at all (ENOSYS), or the syscall
+         * was blocked (EACCES/EPERM; maybe through seccomp, because we are running inside of a
+         * container), or the mount point is not triggered yet (EOVERFLOW, think nfs4), or some
+         * general name_to_handle_at() flakiness (EINVAL). However other errors are not supposed to
+         * happen and therefore are considered fatal ones. */
+
+        assert(err < 0);
+
+        return !IN_SET(err, -EOPNOTSUPP, -ENOSYS, -EACCES, -EPERM, -EOVERFLOW, -EINVAL);
+}
+
 int fd_is_mount_point(int fd, const char *filename, int flags) {
         _cleanup_free_ struct file_handle *h = NULL, *h_parent = NULL;
         int mount_id = -1, mount_id_parent = -1;
@@ -206,39 +219,40 @@ int fd_is_mount_point(int fd, const char *filename, int flags) {
                 return false; /* symlinks are never mount points */
 
         r = name_to_handle_at_loop(fd, filename, &h, &mount_id, flags);
-        if (IN_SET(r, -ENOSYS, -EACCES, -EPERM, -EOVERFLOW, -EINVAL))
-                /* This kernel does not support name_to_handle_at() at all (ENOSYS), or the syscall was blocked
-                 * (EACCES/EPERM; maybe through seccomp, because we are running inside of a container?), or the mount
-                 * point is not triggered yet (EOVERFLOW, think nfs4), or some general name_to_handle_at() flakiness
-                 * (EINVAL): fall back to simpler logic. */
-                goto fallback_fdinfo;
-        else if (r == -EOPNOTSUPP)
-                /* This kernel or file system does not support name_to_handle_at(), hence let's see if the upper fs
-                 * supports it (in which case it is a mount point), otherwise fall back to the traditional stat()
-                 * logic */
+        if (r < 0) {
+                if (is_name_to_handle_at_fatal_error(r))
+                        return r;
+                if (r != -EOPNOTSUPP)
+                        goto fallback_fdinfo;
+
+                /* This kernel or file system does not support name_to_handle_at(), hence let's see
+                 * if the upper fs supports it (in which case it is a mount point), otherwise fall
+                 * back to the traditional stat() logic */
                 nosupp = true;
-        else if (r < 0)
-                return r;
+        }
 
         r = name_to_handle_at_loop(fd, "", &h_parent, &mount_id_parent, AT_EMPTY_PATH);
-        if (r == -EOPNOTSUPP) {
+        if (r < 0) {
+                if (is_name_to_handle_at_fatal_error(r))
+                        return r;
+                if (r != -EOPNOTSUPP)
+                        goto fallback_fdinfo;
                 if (nosupp)
-                        /* Neither parent nor child do name_to_handle_at()?  We have no choice but to fall back. */
+                        /* Both the parent and the directory can't do name_to_handle_at() */
                         goto fallback_fdinfo;
-                else
-                        /* The parent can't do name_to_handle_at() but the directory we are interested in can?  If so,
-                         * it must be a mount point. */
-                        return 1;
-        } else if (r < 0)
-                return r;
 
-        /* The parent can do name_to_handle_at() but the directory we are interested in can't? If so, it must
-         * be a mount point. */
+                /* The parent can't do name_to_handle_at() but the directory we are
+                 * interested in can?  If so, it must be a mount point. */
+                return 1;
+        }
+
+        /* The parent can do name_to_handle_at() but the directory we are interested in can't? If
+         * so, it must be a mount point. */
         if (nosupp)
                 return 1;
 
-        /* If the file handle for the directory we are interested in and its parent are identical, we assume
-         * this is the root directory, which is a mount point. */
+        /* If the file handle for the directory we are interested in and its parent are identical,
+         * we assume this is the root directory, which is a mount point. */
 
         if (h->handle_bytes == h_parent->handle_bytes &&
             h->handle_type == h_parent->handle_type &&
@@ -338,10 +352,10 @@ int path_get_mnt_id(const char *path, int *ret) {
         }
 
         r = name_to_handle_at_loop(AT_FDCWD, path, NULL, ret, 0);
-        if (IN_SET(r, -EOPNOTSUPP, -ENOSYS, -EACCES, -EPERM, -EOVERFLOW, -EINVAL)) /* kernel/fs don't support this, or seccomp blocks access, or untriggered mount, or name_to_handle_at() is flaky */
-                return fd_fdinfo_mnt_id(AT_FDCWD, path, 0, ret);
+        if (r == 0 || is_name_to_handle_at_fatal_error(r))
+                return r;
 
-        return r;
+        return fd_fdinfo_mnt_id(AT_FDCWD, path, 0, ret);
 }
 
 bool fstype_is_network(const char *fstype) {