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)
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;
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 */
const char *path,
struct file_handle **ret_handle,
int *ret_mnt_id,
+ uint64_t *ret_unique_mnt_id,
int flags) {
int r;
* 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) {
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(
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) {
.archive = a,
.flags = flags,
.hardlink_db_fd = -EBADF,
+ .have_unique_mount_id = -1,
};
r = recurse_dir(tree_fd,