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...
This commit is contained in:
Mike Yuan
2026-02-05 14:14:40 +01:00
parent 56adac1193
commit 5817c73391
6 changed files with 97 additions and 97 deletions
+61 -71
View 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) {
+1 -2
View 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);
+9 -2
View 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))