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:
+61
-71
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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))
|
||||
|
||||
Reference in New Issue
Block a user