]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
mountpoint-util: rework name_to_handle_at() unique mount id handling
authorMike Yuan <me@yhndnzj.com>
Thu, 5 Feb 2026 00:32:59 +0000 (01:32 +0100)
committerMike Yuan <me@yhndnzj.com>
Thu, 5 Feb 2026 13:14:40 +0000 (14:14 +0100)
name_to_handle_at_try_unique_mntid_fid() in its current form is
ill-designed for various reasons:

* AT_HANDLE_FID requires file system support, while unique mount id
  is a VFS concept hence is always available if supported. Hence
  the fallback for AT_HANDLE_MNT_ID_UNIQUE should be independent
  of fid.
* The request for AT_HANDLE_MNT_ID_UNIQUE can be identified via
  specifying ret_unique_mnt_id, no need for opening up the control
  to caller (and currently the function simply doesn't handle
  mismatch between ret params and flags).
* The caller cannot realistically differentiate whether the returned
  mount id is actually unique.
* The path_get_unique_mnt_id() fallback did not handle AT_SYMLINK_FOLLOW.

Let's instead move the statx() fallback into name_to_handle_at_loop()
directly, and revamp interaction of ret_mnt_id/ret_unique_mnt_id:
if both are set, it indicates that the caller can handle both, hence
set what we have and return 0/1 for whether we managed to acquire
the unique one.

The !ret_handle && ret_mnt_id logic is removed. Let's not rely on
undocumented bizaare behavior and it's unused anyways.
path_get_mnt_id_at() exists for a reason...

src/basic/mountpoint-util.c
src/basic/mountpoint-util.h
src/basic/stat-util.c
src/libsystemd/sd-json/json-util.c
src/shared/discover-image.c
src/shared/tar-util.c

index 1f90217dc43708cf17d4a4a7a6dff168511622d3..5f62e383a440c96fa0ee5d41d6b3fedaa3eca03a 100644 (file)
@@ -53,27 +53,29 @@ int name_to_handle_at_loop(
                 uint64_t *ret_unique_mnt_id,
                 int flags) {
 
-        size_t n = ORIGINAL_MAX_HANDLE_SZ;
+        int r;
 
         assert(fd >= 0 || fd == AT_FDCWD);
-        assert((flags & ~(AT_SYMLINK_FOLLOW|AT_EMPTY_PATH|AT_HANDLE_FID|AT_HANDLE_MNT_ID_UNIQUE)) == 0);
+        assert((flags & ~(AT_SYMLINK_FOLLOW|AT_EMPTY_PATH|AT_HANDLE_FID)) == 0);
 
         /* 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. */
+         * as NULL if there's no interest in either.
+         *
+         * If unique mount id is requested via ret_unique_mnt_id, try AT_HANDLE_MNT_ID_UNIQUE flag first
+         * (needs kernel v6.12), and fall back to statx() if not supported. If neither worked, and caller
+         * also specifies ret_mnt_id, then the old-style mount id is returned, -EUNATCH otherwise. */
 
         if (isempty(path)) {
                 flags |= AT_EMPTY_PATH;
                 path = "";
         }
 
-        for (;;) {
+        for (size_t n = ORIGINAL_MAX_HANDLE_SZ;;) {
                 _cleanup_free_ struct file_handle *h = NULL;
-                int mnt_id = -1, r;
-                uint64_t unique_mnt_id = 0;
 
                 h = malloc0(offsetof(struct file_handle, f_handle) + n);
                 if (!h)
@@ -81,18 +83,61 @@ int name_to_handle_at_loop(
 
                 h->handle_bytes = n;
 
-                if (FLAGS_SET(flags, AT_HANDLE_MNT_ID_UNIQUE))
+                if (ret_unique_mnt_id) {
+                        uint64_t mnt_id;
+
                         /* The kernel will still use this as uint64_t pointer */
-                        r = name_to_handle_at(fd, path, h, (int *) &unique_mnt_id, flags);
-                else
-                        r = name_to_handle_at(fd, path, h, &mnt_id, flags);
+                        r = name_to_handle_at(fd, path, h, (int *) &mnt_id, flags|AT_HANDLE_MNT_ID_UNIQUE);
+                        if (r >= 0) {
+                                if (ret_handle)
+                                        *ret_handle = TAKE_PTR(h);
+
+                                *ret_unique_mnt_id = mnt_id;
+
+                                if (ret_mnt_id)
+                                        *ret_mnt_id = -1;
+
+                                return 1;
+                        }
+                        if (errno == EOVERFLOW)
+                                goto grow;
+                        if (errno != EINVAL)
+                                return -errno;
+                }
+
+                int mnt_id;
+                r = name_to_handle_at(fd, path, h, &mnt_id, flags);
                 if (r >= 0) {
+                        if (ret_unique_mnt_id) {
+                                /* Hmm, AT_HANDLE_MNT_ID_UNIQUE is not supported? Let's try to acquire
+                                 * the unique mount id from statx() then, which has a slightly lower
+                                 * kernel version requirement (6.8 vs 6.12). */
+
+                                struct statx sx;
+                                r = xstatx(fd, path,
+                                           at_flags_normalize_nofollow(flags & (AT_SYMLINK_FOLLOW|AT_EMPTY_PATH))|AT_STATX_DONT_SYNC,
+                                           STATX_MNT_ID_UNIQUE,
+                                           &sx);
+                                if (r >= 0) {
+                                        if (ret_handle)
+                                                *ret_handle = TAKE_PTR(h);
+
+                                        *ret_unique_mnt_id = sx.stx_mnt_id;
+
+                                        if (ret_mnt_id)
+                                                *ret_mnt_id = -1;
+
+                                        return 1;
+                                }
+                                if (r != -EUNATCH || !ret_mnt_id)
+                                        return r;
+
+                                *ret_unique_mnt_id = 0;
+                        }
 
                         if (ret_handle)
                                 *ret_handle = TAKE_PTR(h);
 
-                        if (ret_unique_mnt_id)
-                                *ret_unique_mnt_id = unique_mnt_id;
                         if (ret_mnt_id)
                                 *ret_mnt_id = mnt_id;
 
@@ -101,19 +146,7 @@ int name_to_handle_at_loop(
                 if (errno != EOVERFLOW)
                         return -errno;
 
-                if (!ret_handle && ((ret_mnt_id && mnt_id >= 0) || (ret_unique_mnt_id && unique_mnt_id > 0))) {
-
-                        /* As it appears, name_to_handle_at() fills in mnt_id even when it returns EOVERFLOW when the
-                         * buffer is too small, 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. */
-
-                        if (ret_unique_mnt_id)
-                                *ret_unique_mnt_id = unique_mnt_id;
-                        if (ret_mnt_id)
-                                *ret_mnt_id = mnt_id;
-                        return 0;
-                }
-
+        grow:
                 /* If name_to_handle_at() didn't increase the byte size, then this EOVERFLOW is caused by
                  * something else (apparently EOVERFLOW is returned for untriggered nfs4 autofs mounts
                  * sometimes), not by the too small buffer. In that case propagate EOVERFLOW */
@@ -134,6 +167,7 @@ int name_to_handle_at_try_fid(
                 const char *path,
                 struct file_handle **ret_handle,
                 int *ret_mnt_id,
+                uint64_t *ret_unique_mnt_id,
                 int flags) {
 
         int r;
@@ -144,55 +178,11 @@ int name_to_handle_at_try_fid(
          * we'll try without the flag, in order to support older kernels that didn't have AT_HANDLE_FID
          * (i.e. older than Linux 6.5). */
 
-        r = name_to_handle_at_loop(fd, path, ret_handle, ret_mnt_id, /* ret_unique_mnt_id= */ NULL, flags | AT_HANDLE_FID);
+        r = name_to_handle_at_loop(fd, path, ret_handle, ret_mnt_id, ret_unique_mnt_id, flags | AT_HANDLE_FID);
         if (r >= 0 || is_name_to_handle_at_fatal_error(r))
                 return r;
 
-        return name_to_handle_at_loop(fd, path, ret_handle, ret_mnt_id, /* ret_unique_mnt_id= */ NULL, flags & ~AT_HANDLE_FID);
-}
-
-int name_to_handle_at_try_unique_mntid_fid(
-                int fd,
-                const char *path,
-                struct file_handle **ret_handle,
-                uint64_t *ret_mnt_id,
-                int flags) {
-
-        int mnt_id = -1, r;
-
-        assert(fd >= 0 || fd == AT_FDCWD);
-
-        /* First issues name_to_handle_at() with AT_HANDLE_MNT_ID_UNIQUE and AT_HANDLE_FID.
-         * If this fails and this is not a fatal error we'll try without the
-         * AT_HANDLE_MNT_ID_UNIQUE flag because it's only available from Linux 6.12 onwards. */
-        r = name_to_handle_at_loop(fd, path, ret_handle, /* ret_mnt_id= */ NULL, ret_mnt_id, flags | AT_HANDLE_MNT_ID_UNIQUE | AT_HANDLE_FID);
-        if (r >= 0 || is_name_to_handle_at_fatal_error(r))
-                return r;
-
-        flags &= ~AT_HANDLE_MNT_ID_UNIQUE;
-
-        /* Then issues name_to_handle_at() with AT_HANDLE_FID. If this fails and this is not a fatal error
-         * we'll try without the flag, in order to support older kernels that didn't have AT_HANDLE_FID
-         * (i.e. older than Linux 6.5). */
-
-        r = name_to_handle_at_loop(fd, path, ret_handle, &mnt_id, /* ret_unique_mnt_id= */ NULL, flags | AT_HANDLE_FID);
-        if (r < 0 && is_name_to_handle_at_fatal_error(r))
-                return r;
-        if (r >= 0) {
-                if (ret_mnt_id && mnt_id >= 0) {
-                        /* See if we can do better because statx can do unique mount IDs since Linux 6.8
-                         * and only if this doesn't work we use the non-unique mnt_id as returned. */
-                        if (path_get_unique_mnt_id_at(fd, path, ret_mnt_id) < 0)
-                                *ret_mnt_id = mnt_id;
-                }
-
-                return r;
-        }
-
-        r = name_to_handle_at_loop(fd, path, ret_handle, &mnt_id, /* ret_unique_mnt_id= */ NULL, flags & ~AT_HANDLE_FID);
-        if (ret_mnt_id && mnt_id >= 0)
-                *ret_mnt_id = mnt_id;
-        return r;
+        return name_to_handle_at_loop(fd, path, ret_handle, ret_mnt_id, ret_unique_mnt_id, flags & ~AT_HANDLE_FID);
 }
 
 int name_to_handle_at_u64(int fd, const char *path, uint64_t *ret) {
index 82eb1516c3390a78344a167103606e5d92ea3a02..1381f3d8aba0ec32579fec35b4aa48bbcab52059 100644 (file)
@@ -35,8 +35,7 @@
 bool is_name_to_handle_at_fatal_error(int err);
 
 int name_to_handle_at_loop(int fd, const char *path, struct file_handle **ret_handle, int *ret_mnt_id, uint64_t *ret_unique_mnt_id, int flags);
-int name_to_handle_at_try_fid(int fd, const char *path, struct file_handle **ret_handle, int *ret_mnt_id, int flags);
-int name_to_handle_at_try_unique_mntid_fid(int fd, const char *path, struct file_handle **ret_handle, uint64_t *ret_mnt_id, int flags);
+int name_to_handle_at_try_fid(int fd, const char *path, struct file_handle **ret_handle, int *ret_mnt_id, uint64_t *ret_unique_mnt_id, int flags);
 int name_to_handle_at_u64(int fd, const char *path, uint64_t *ret);
 static inline int path_to_handle_u64(const char *path, uint64_t *ret) {
         return name_to_handle_at_u64(AT_FDCWD, path, ret);
index 7413f515055c4947ea9aa0f15d853736e66f743f..703cf4fc9252bf00b83c85787c1cd2067b53d486 100644 (file)
@@ -431,12 +431,14 @@ int inode_same_at(int fda, const char *filea, int fdb, const char *fileb, int fl
 
                 int ntha_flags = at_flags_normalize_follow(flags) & (AT_EMPTY_PATH|AT_SYMLINK_FOLLOW);
                 _cleanup_free_ struct file_handle *ha = NULL, *hb = NULL;
-                int mntida = -1, mntidb = -1;
+                uint64_t mntida, mntidb;
+                int _mntida, _mntidb;
 
                 r = name_to_handle_at_try_fid(
                                 fda,
                                 filea,
                                 &ha,
+                                &_mntida,
                                 &mntida,
                                 ntha_flags);
                 if (r < 0) {
@@ -445,12 +447,15 @@ int inode_same_at(int fda, const char *filea, int fdb, const char *fileb, int fl
 
                         goto fallback;
                 }
+                if (r == 0)
+                        mntida = _mntida;
 
                 r = name_to_handle_at_try_fid(
                                 fdb,
                                 fileb,
                                 &hb,
-                                &mntidb,
+                                r > 0 ? NULL : &_mntidb, /* if we managed to get unique mnt id for a, insist on that for b */
+                                r > 0 ? &mntidb : NULL,
                                 ntha_flags);
                 if (r < 0) {
                         if (is_name_to_handle_at_fatal_error(r))
@@ -458,6 +463,8 @@ int inode_same_at(int fda, const char *filea, int fdb, const char *fileb, int fl
 
                         goto fallback;
                 }
+                if (r == 0)
+                        mntidb = _mntidb;
 
                 /* Now compare the two file handles */
                 if (!file_handle_equal(ha, hb))
index 30dbfde37b6fa33d6d96dccabcb500f89cdd00e8..41ac8c40976f72425a2ca045f974e2a64d9039f6 100644 (file)
@@ -685,7 +685,7 @@ int json_variant_new_fd_info(sd_json_variant **ret, int fd) {
         if (r < 0)
                 return r;
 
-        r = name_to_handle_at_try_fid(fd, "", &fid, &mntid, AT_EMPTY_PATH);
+        r = name_to_handle_at_try_fid(fd, "", &fid, &mntid, /* ret_unique_mnt_id = */ NULL, AT_EMPTY_PATH);
         if (r < 0 && is_name_to_handle_at_fatal_error(r))
                 return r;
 
index d80ecdaf7f8570981c19e3b03761894401853912..885b896de4abd4f4319f11542497a5e4ee4c087b 100644 (file)
@@ -451,27 +451,15 @@ static int image_make(
                 path_startswith(path, "/usr") ||
                 (faccessat(fd, "", W_OK, AT_EACCESS|AT_EMPTY_PATH) < 0 && errno == EROFS);
 
-        uint64_t on_mount_id = 0;
         _cleanup_free_ struct file_handle *fh = NULL;
+        uint64_t on_mount_id;
+        int _mnt_id;
 
-        r = name_to_handle_at_try_unique_mntid_fid(fd, /* path= */ NULL, &fh, &on_mount_id, /* flags= */ 0);
-        if (r < 0) {
-                if (is_name_to_handle_at_fatal_error(r))
-                        return r;
-
-                r = path_get_unique_mnt_id_at(fd, /* path= */ NULL, &on_mount_id);
-                if (r < 0) {
-                        if (!ERRNO_IS_NEG_NOT_SUPPORTED(r))
-                                return r;
-
-                        int on_mount_id_fallback = -1;
-                        r = path_get_mnt_id_at(fd, /* path= */ NULL, &on_mount_id_fallback);
-                        if (r < 0)
-                                return r;
-
-                        on_mount_id = on_mount_id_fallback;
-                }
-        }
+        r = name_to_handle_at_try_fid(fd, /* path= */ NULL, &fh, &_mnt_id, &on_mount_id, AT_EMPTY_PATH);
+        if (r < 0)
+                return r;
+        if (r == 0)
+                on_mount_id = _mnt_id;
 
         if (S_ISDIR(st->st_mode)) {
                 unsigned file_attr = 0;
index 9436eff868ba621512dd43157e5b8ffe516741d0..7fe4544c3c82d8787fc9e45d378608e8e335457b 100644 (file)
@@ -1015,8 +1015,10 @@ static int make_tmpfs(void) {
 struct make_archive_data {
         struct archive *archive;
         TarFlags flags;
+
         int hardlink_db_fd;
         char *hardlink_db_path;
+        int have_unique_mount_id;
 };
 
 static int hardlink_lookup(
@@ -1042,16 +1044,29 @@ static int hardlink_lookup(
         if (FLAGS_SET(sx->stx_mask, STATX_TYPE) && !inode_type_can_hardlink(sx->stx_mode))
                 goto bypass;
 
+        uint64_t unique_mnt_id;
         int mnt_id;
-        r = name_to_handle_at_try_fid(inode_fd, /* path= */ NULL, &handle, &mnt_id, /* flags= */ AT_EMPTY_PATH);
+        r = name_to_handle_at_try_fid(inode_fd, /* path= */ NULL,
+                                      &handle,
+                                      d->have_unique_mount_id <= 0 ? &mnt_id : NULL,
+                                      d->have_unique_mount_id != 0 ? &unique_mnt_id : NULL,
+                                      /* flags= */ AT_EMPTY_PATH);
         if (r < 0)
                 return log_error_errno(r, "Failed to get file handle of file: %m");
+        if (d->have_unique_mount_id < 0)
+                d->have_unique_mount_id = r > 0;
+        else
+                assert(d->have_unique_mount_id == (r > 0));
 
         m = hexmem(SHA256_DIRECT(handle->f_handle, handle->handle_bytes), SHA256_DIGEST_SIZE);
         if (!m)
                 return log_oom();
 
-        if (asprintf(&n, "%i:%i:%s", mnt_id, handle->handle_type, m) < 0)
+        if (d->have_unique_mount_id)
+                r = asprintf(&n, "%" PRIu64 ":%i:%s", unique_mnt_id, handle->handle_type, m);
+        else
+                r = asprintf(&n, "%i:%i:%s", mnt_id, handle->handle_type, m);
+        if (r < 0)
                 return log_oom();
 
         if (d->hardlink_db_fd < 0) {
@@ -1467,6 +1482,7 @@ int tar_c(int tree_fd, int output_fd, const char *filename, TarFlags flags) {
                 .archive = a,
                 .flags = flags,
                 .hardlink_db_fd = -EBADF,
+                .have_unique_mount_id = -1,
         };
 
         r = recurse_dir(tree_fd,