]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
mount-util: add name_to_handle_at_loop() wrapper around name_to_handle_at()
authorLennart Poettering <lennart@poettering.net>
Mon, 20 Nov 2017 14:29:53 +0000 (15:29 +0100)
committerLennart Poettering <lennart@poettering.net>
Tue, 21 Nov 2017 10:37:12 +0000 (11:37 +0100)
As it turns out MAX_HANDLE_SZ is a lie, the handle buffer we pass into
name_to_handle_at() might need to be larger than MAX_HANDLE_SZ, and we
thus need to invoke name_to_handle_at() in a loop, growing the buffer as
needed.

This adds a new wrapper name_to_handle_at_loop() around
name_to_handle_at() that does the necessary looping, and ports over all
users.

Fixes: #7082
src/basic/mount-util.c
src/basic/mount-util.h
src/libudev/libudev-monitor.c
src/tmpfiles/tmpfiles.c

index 8801e2f8f1f8ea4a3ff521f2bd87c52a6758b3f0..9fb96e6b6d7b31436b4cadb9119775012b2b8467 100644 (file)
 #include "string-util.h"
 #include "strv.h"
 
+int name_to_handle_at_loop(
+                int fd,
+                const char *path,
+                struct file_handle **ret_handle,
+                int *ret_mnt_id,
+                int flags) {
+
+        _cleanup_free_ struct file_handle *h;
+        size_t n = MAX_HANDLE_SZ;
+
+        /* We need to invoke name_to_handle_at() in a loop, given that it might return EOVERFLOW when the specified
+         * buffer is too small. Note that in contrast to what the docs might suggest, MAX_HANDLE_SZ is only good as a
+         * start value, it is not an upper bound on the buffer size required.
+         *
+         * This improves on raw name_to_handle_at() also in one other regard: ret_handle and ret_mnt_id can be passed
+         * as NULL if there's no interest in either. */
+
+        h = malloc0(offsetof(struct file_handle, f_handle) + n);
+        if (!h)
+                return -ENOMEM;
+
+        h->handle_bytes = n;
+
+        for (;;) {
+                int mnt_id = -1;
+                size_t m;
+
+                if (name_to_handle_at(fd, path, h, &mnt_id, flags) >= 0) {
+
+                        if (ret_handle) {
+                                *ret_handle = h;
+                                h = NULL;
+                        }
+
+                        if (ret_mnt_id)
+                                *ret_mnt_id = mnt_id;
+
+                        return 0;
+                }
+                if (errno != EOVERFLOW)
+                        return -errno;
+
+                if (!ret_handle && ret_mnt_id && mnt_id >= 0) {
+
+                        /* As it appears, name_to_handle_at() fills in mnt_id even when it returns EOVERFLOW, but
+                         * that's undocumented. Hence, let's make use of this if it appears to be filled in, and the
+                         * caller was interested in only the mount ID an nothing else. */
+
+                        *ret_mnt_id = mnt_id;
+                        return 0;
+                }
+
+                /* The buffer was too small. Size the new buffer by what name_to_handle_at() returned, but make sure it
+                 * is always larger than what we passed in before */
+                m = h->handle_bytes > n ? h->handle_bytes : n * 2;
+                if (m < n) /* Check for multiplication overflow */
+                        return -EOVERFLOW;
+                if (offsetof(struct file_handle, f_handle) + m < m) /* check for addition overflow */
+                        return -EOVERFLOW;
+                n = m;
+
+                free(h);
+                h = malloc0(offsetof(struct file_handle, f_handle) + n);
+                if (!h)
+                        return -ENOMEM;
+
+                h->handle_bytes = n;
+        }
+}
+
 static int fd_fdinfo_mnt_id(int fd, const char *filename, int flags, int *mnt_id) {
         char path[strlen("/proc/self/fdinfo/") + DECIMAL_STR_MAX(int)];
         _cleanup_free_ char *fdinfo = NULL;
@@ -79,7 +149,7 @@ static int fd_fdinfo_mnt_id(int fd, const char *filename, int flags, int *mnt_id
 }
 
 int fd_is_mount_point(int fd, const char *filename, int flags) {
-        union file_handle_union h = FILE_HANDLE_INIT, h_parent = FILE_HANDLE_INIT;
+        _cleanup_free_ struct file_handle *h = NULL, *h_parent = NULL;
         int mount_id = -1, mount_id_parent = -1;
         bool nosupp = false, check_st_dev = true;
         struct stat a, b;
@@ -111,39 +181,30 @@ int fd_is_mount_point(int fd, const char *filename, int flags) {
          * subvolumes have different st_dev, even though they aren't
          * real mounts of their own. */
 
-        r = name_to_handle_at(fd, filename, &h.handle, &mount_id, flags);
-        if (r < 0) {
-                if (IN_SET(errno, ENOSYS, EACCES, EPERM))
-                        /* This kernel does not support name_to_handle_at() at all, or the syscall was blocked (maybe
-                         * through seccomp, because we are running inside of a container?): fall back to simpler
-                         * logic. */
+        r = name_to_handle_at_loop(fd, filename, &h, &mount_id, flags);
+        if (IN_SET(r, -ENOSYS, -EACCES, -EPERM))
+                /* This kernel does not support name_to_handle_at() at all, or the syscall was blocked (maybe through
+                 * seccomp, because we are running inside of a container?): 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 fallback 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 (nosupp)
+                        /* Neither parent nor child do name_to_handle_at()?  We have no choice but to fall back. */
                         goto fallback_fdinfo;
-                else if (errno == 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 fallback to the
-                         * traditional stat() logic */
-                        nosupp = true;
                 else
-                        return -errno;
-        }
-
-        r = name_to_handle_at(fd, "", &h_parent.handle, &mount_id_parent, AT_EMPTY_PATH);
-        if (r < 0) {
-                if (errno == EOPNOTSUPP) {
-                        if (nosupp)
-                                /* Neither parent nor child do name_to_handle_at()?
-                                   We have no choice but to fall back. */
-                                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
-                        return -errno;
-        }
+                        /* 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
@@ -156,9 +217,9 @@ int fd_is_mount_point(int fd, const char *filename, int flags) {
          * assume this is the root directory, which is a mount
          * point. */
 
-        if (h.handle.handle_bytes == h_parent.handle.handle_bytes &&
-            h.handle.handle_type == h_parent.handle.handle_type &&
-            memcmp(h.handle.f_handle, h_parent.handle.f_handle, h.handle.handle_bytes) == 0)
+        if (h->handle_bytes == h_parent->handle_bytes &&
+            h->handle_type == h_parent->handle_type &&
+            memcmp(h->f_handle, h_parent->f_handle, h->handle_bytes) == 0)
                 return 1;
 
         return mount_id != mount_id_parent;
index 0f53fee2f23bd4bfd380d1dbff8c5e3b92151abd..453bf9a0a5594550b00795a95da5b9b52437e7f0 100644 (file)
@@ -30,6 +30,8 @@
 #include "macro.h"
 #include "missing.h"
 
+int name_to_handle_at_loop(int fd, const char *path, struct file_handle **ret_handle, int *ret_mnt_id, int flags);
+
 int fd_is_mount_point(int fd, const char *filename, int flags);
 int path_is_mount_point(const char *path, const char *root, int flags);
 
@@ -49,15 +51,8 @@ bool fstype_is_api_vfs(const char *fstype);
 bool fstype_is_ro(const char *fsype);
 bool fstype_can_discard(const char *fstype);
 
-union file_handle_union {
-        struct file_handle handle;
-        char padding[sizeof(struct file_handle) + MAX_HANDLE_SZ];
-};
-
 const char* mode_to_inaccessible_node(mode_t mode);
 
-#define FILE_HANDLE_INIT { .handle.handle_bytes = MAX_HANDLE_SZ }
-
 int mount_verbose(
                 int error_log_level,
                 const char *what,
index e43d7121955d93588c09406476d7ced1134d6226..68fd174a3a305857e56ef19e9f22117a3457a8e8 100644 (file)
@@ -115,13 +115,12 @@ static struct udev_monitor *udev_monitor_new(struct udev *udev)
 /* we consider udev running when /dev is on devtmpfs */
 static bool udev_has_devtmpfs(struct udev *udev) {
 
-        union file_handle_union h = FILE_HANDLE_INIT;
         _cleanup_fclose_ FILE *f = NULL;
         char line[LINE_MAX], *e;
         int mount_id;
         int r;
 
-        r = name_to_handle_at(AT_FDCWD, "/dev", &h.handle, &mount_id, 0);
+        r = name_to_handle_at_loop(AT_FDCWD, "/dev", NULL, &mount_id, 0);
         if (r < 0) {
                 if (errno != EOPNOTSUPP)
                         log_debug_errno(errno, "name_to_handle_at on /dev: %m");
index 173774ea0a096fcc81424a1fbc228a31cc31cab1..b8d67960b6d55ea8cedbb4ed29aa0b26257f3650 100644 (file)
@@ -346,16 +346,14 @@ static bool unix_socket_alive(const char *fn) {
 
 static int dir_is_mount_point(DIR *d, const char *subdir) {
 
-        union file_handle_union h = FILE_HANDLE_INIT;
         int mount_id_parent, mount_id;
         int r_p, r;
 
-        r_p = name_to_handle_at(dirfd(d), ".", &h.handle, &mount_id_parent, 0);
+        r_p = name_to_handle_at_loop(dirfd(d), ".", NULL, &mount_id_parent, 0);
         if (r_p < 0)
                 r_p = -errno;
 
-        h.handle.handle_bytes = MAX_HANDLE_SZ;
-        r = name_to_handle_at(dirfd(d), subdir, &h.handle, &mount_id, 0);
+        r = name_to_handle_at_loop(dirfd(d), subdir, NULL, &mount_id, 0);
         if (r < 0)
                 r = -errno;