From: Mike Yuan Date: Thu, 5 Feb 2026 00:32:59 +0000 (+0100) Subject: mountpoint-util: rework name_to_handle_at() unique mount id handling X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=5817c73391b5f3599c50df2c0873b26ea426f848;p=thirdparty%2Fsystemd.git mountpoint-util: rework name_to_handle_at() unique mount id handling 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... --- diff --git a/src/basic/mountpoint-util.c b/src/basic/mountpoint-util.c index 1f90217dc43..5f62e383a44 100644 --- a/src/basic/mountpoint-util.c +++ b/src/basic/mountpoint-util.c @@ -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) { diff --git a/src/basic/mountpoint-util.h b/src/basic/mountpoint-util.h index 82eb1516c33..1381f3d8aba 100644 --- a/src/basic/mountpoint-util.h +++ b/src/basic/mountpoint-util.h @@ -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); diff --git a/src/basic/stat-util.c b/src/basic/stat-util.c index 7413f515055..703cf4fc925 100644 --- a/src/basic/stat-util.c +++ b/src/basic/stat-util.c @@ -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)) diff --git a/src/libsystemd/sd-json/json-util.c b/src/libsystemd/sd-json/json-util.c index 30dbfde37b6..41ac8c40976 100644 --- a/src/libsystemd/sd-json/json-util.c +++ b/src/libsystemd/sd-json/json-util.c @@ -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; diff --git a/src/shared/discover-image.c b/src/shared/discover-image.c index d80ecdaf7f8..885b896de4a 100644 --- a/src/shared/discover-image.c +++ b/src/shared/discover-image.c @@ -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; diff --git a/src/shared/tar-util.c b/src/shared/tar-util.c index 9436eff868b..7fe4544c3c8 100644 --- a/src/shared/tar-util.c +++ b/src/shared/tar-util.c @@ -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,