mirror of
https://github.com/flatpak/flatpak.git
synced 2026-06-26 17:26:50 -04:00
fdio: Add support for name_to_handle_at
This commit is contained in:
@@ -8,6 +8,8 @@
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
#include <glnx-missing.h>
|
||||
|
||||
typedef enum _GlnxChaseFlags {
|
||||
/* Default */
|
||||
GLNX_CHASE_DEFAULT = 0,
|
||||
|
||||
179
glnx-fdio.c
179
glnx-fdio.c
@@ -33,6 +33,7 @@
|
||||
#include <sys/sendfile.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include <glnx-chase.h>
|
||||
#include <glnx-fdio.h>
|
||||
#include <glnx-dirfd.h>
|
||||
#include <glnx-errors.h>
|
||||
@@ -42,6 +43,18 @@
|
||||
#include <glnx-local-alloc.h>
|
||||
#include <glnx-missing.h>
|
||||
|
||||
/* From systemd mountpoint-util.c at d2b27a7:
|
||||
* This is the original MAX_HANDLE_SZ definition from the kernel, when the API
|
||||
* was introduced. We use that in place of any more currently defined value to
|
||||
* future-proof things: if the size is increased in the API headers, and our code
|
||||
* is recompiled then it would cease working on old kernels, as those refuse any
|
||||
* sizes larger than this value with EINVAL right-away. Hence, let's disconnect
|
||||
* ourselves from any such API changes, and stick to the original definition
|
||||
* from when it was introduced. We use it as a start value only anyway (see
|
||||
* below), and hence should be able to deal with large file handles anyway.
|
||||
*/
|
||||
#define ORIGINAL_MAX_HANDLE_SZ 128
|
||||
|
||||
/* The standardized version of BTRFS_IOC_CLONE */
|
||||
#ifndef FICLONE
|
||||
#define FICLONE _IOW(0x94, 9, int)
|
||||
@@ -1279,3 +1292,169 @@ glnx_fd_reopen (int fd,
|
||||
|
||||
return g_steal_fd (&new_fd);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
glnx_name_to_handle_at_internal (int fd,
|
||||
int flags,
|
||||
struct file_handle **handle_out,
|
||||
uint64_t *mnt_id_out,
|
||||
GError **error)
|
||||
{
|
||||
size_t handle_bytes = ORIGINAL_MAX_HANDLE_SZ;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
g_autofree struct file_handle *handle = NULL;
|
||||
uint64_t mnt_id_unique = 0;
|
||||
unsigned int mnt_id = 0;
|
||||
int *mnt_id_ptr;
|
||||
int r;
|
||||
|
||||
/* The kernel ABI involves an int * for backward compatibility,
|
||||
* but with AT_HANDLE_MNT_ID_UNIQUE it's really expecting a
|
||||
* uint64_t and will write a full 64-bit ID into it. */
|
||||
if ((flags & AT_HANDLE_MNT_ID_UNIQUE))
|
||||
mnt_id_ptr = (int *) &mnt_id_unique;
|
||||
else
|
||||
mnt_id_ptr = (int *) &mnt_id;
|
||||
|
||||
handle = g_malloc0 (offsetof (struct file_handle, f_handle) + handle_bytes);
|
||||
handle->handle_bytes = handle_bytes;
|
||||
r = name_to_handle_at (fd, "",
|
||||
handle,
|
||||
mnt_id_ptr,
|
||||
flags | AT_EMPTY_PATH);
|
||||
|
||||
if (r < 0)
|
||||
{
|
||||
if (errno != EOVERFLOW)
|
||||
return glnx_throw_errno (error);
|
||||
|
||||
if (handle->handle_bytes <= handle_bytes)
|
||||
return glnx_throw (error, "No file handle available");
|
||||
}
|
||||
|
||||
if (r >= 0)
|
||||
{
|
||||
if (handle_out)
|
||||
*handle_out = g_steal_pointer (&handle);
|
||||
if (mnt_id_out)
|
||||
*mnt_id_out = (flags & AT_HANDLE_MNT_ID_UNIQUE) ? mnt_id_unique : mnt_id;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
handle_bytes = handle->handle_bytes;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* glnx_name_to_handle_at:
|
||||
* @dfd: Directory FD to stat beneath
|
||||
* @path: Path to get the handle to beneath @dfd
|
||||
* @flags: Flags
|
||||
* @handle_out: (out) (transfer full): Return location for the `struct file_handle`
|
||||
* @mnt_id_out: (out caller-allocates): Return location for the mount id
|
||||
* @error: Return location for a #GError, or %NULL
|
||||
*
|
||||
* Wrapper around name_to_handle_at() which adds #GError support, takes care of
|
||||
* allocating the right size, and falls back to glnx_statx() for
|
||||
* AT_HANDLE_MNT_ID_UNIQUE.
|
||||
*
|
||||
* The @mnt_id_out is pointer to a 64 bit location, but can contain either a
|
||||
* traditional 32 bit mount id or a 64 bit unique mount id if
|
||||
* AT_HANDLE_MNT_ID_UNIQUE is set.
|
||||
*
|
||||
* The @flags must be a combination of AT_SYMLINK_FOLLOW, AT_EMPTY_PATH,
|
||||
* AT_HANDLE_FID, AT_HANDLE_MNT_ID_UNIQUE.
|
||||
*
|
||||
* Returns: %TRUE on success, or %FALSE setting both @error and `errno`
|
||||
* Since: UNRELEASED
|
||||
*/
|
||||
gboolean
|
||||
glnx_name_to_handle_at (int dfd,
|
||||
const char *path,
|
||||
int flags,
|
||||
struct file_handle **handle_out,
|
||||
uint64_t *mnt_id_out,
|
||||
GError **error)
|
||||
{
|
||||
int fd = -1;
|
||||
glnx_autofd int fd_owned = -1;
|
||||
uint64_t mnt_id;
|
||||
g_autoptr(GError) local_error = NULL;
|
||||
|
||||
g_return_val_if_fail (dfd >= 0 || dfd == AT_FDCWD, FALSE);
|
||||
g_return_val_if_fail (path != NULL, FALSE);
|
||||
g_return_val_if_fail ((flags & ~(AT_SYMLINK_FOLLOW |
|
||||
AT_EMPTY_PATH |
|
||||
AT_HANDLE_FID |
|
||||
AT_HANDLE_MNT_ID_UNIQUE)) == 0, FALSE);
|
||||
|
||||
if ((flags & AT_EMPTY_PATH) && path[0] == '\0')
|
||||
{
|
||||
fd = dfd;
|
||||
}
|
||||
else
|
||||
{
|
||||
int chase_flags = GLNX_CHASE_NO_AUTOMOUNT;
|
||||
|
||||
if ((flags & AT_SYMLINK_FOLLOW) == 0)
|
||||
chase_flags |= GLNX_CHASE_NOFOLLOW;
|
||||
|
||||
fd = fd_owned = glnx_chaseat (dfd, path, chase_flags, error);
|
||||
if (fd < 0)
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (glnx_name_to_handle_at_internal (fd,
|
||||
flags,
|
||||
handle_out, mnt_id_out,
|
||||
&local_error))
|
||||
return TRUE;
|
||||
|
||||
if (errno != EINVAL || (flags & AT_HANDLE_MNT_ID_UNIQUE) == 0)
|
||||
{
|
||||
g_propagate_prefixed_error (error, g_steal_pointer (&local_error),
|
||||
"name_to_handle_at: ");
|
||||
return FALSE;
|
||||
}
|
||||
g_clear_error (&local_error);
|
||||
|
||||
{
|
||||
struct glnx_statx stx;
|
||||
int statx_flags = AT_EMPTY_PATH;
|
||||
|
||||
if ((flags & AT_SYMLINK_FOLLOW) == 0)
|
||||
statx_flags |= AT_SYMLINK_NOFOLLOW;
|
||||
|
||||
if (!glnx_statx (fd, "", statx_flags, GLNX_STATX_MNT_ID_UNIQUE, &stx, error))
|
||||
{
|
||||
g_prefix_error (error, "statx: ");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if ((stx.stx_mask & GLNX_STATX_MNT_ID_UNIQUE) == 0)
|
||||
{
|
||||
errno = ENODATA;
|
||||
return glnx_throw_errno_prefix (error,
|
||||
"unique mount ID not in statx result");
|
||||
}
|
||||
|
||||
mnt_id = stx.stx_mnt_id;
|
||||
}
|
||||
|
||||
if (!glnx_name_to_handle_at_internal (fd,
|
||||
flags & (~AT_HANDLE_MNT_ID_UNIQUE),
|
||||
handle_out, NULL,
|
||||
&local_error))
|
||||
{
|
||||
g_propagate_prefixed_error (error, g_steal_pointer (&local_error),
|
||||
"name_to_handle_at: ");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (mnt_id_out)
|
||||
*mnt_id_out = mnt_id;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
@@ -419,4 +419,11 @@ int glnx_fd_reopen (int fd,
|
||||
int flags,
|
||||
GError **error);
|
||||
|
||||
gboolean glnx_name_to_handle_at (int dfd,
|
||||
const char *path,
|
||||
int flags,
|
||||
struct file_handle **handle_out,
|
||||
uint64_t *mnt_id_out,
|
||||
GError **error);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
@@ -589,3 +589,102 @@ inline_openat2 (int dfd,
|
||||
#define openat2 inline_openat2
|
||||
#define HAVE_OPENAT2
|
||||
#endif
|
||||
|
||||
#ifndef __IGNORE_name_to_handle_at
|
||||
# if defined(__aarch64__)
|
||||
# define systemd_NR_name_to_handle_at 264
|
||||
# elif defined(__alpha__)
|
||||
# define systemd_NR_name_to_handle_at 497
|
||||
# elif defined(__arc__) || defined(__tilegx__)
|
||||
# define systemd_NR_name_to_handle_at 264
|
||||
# elif defined(__arm__)
|
||||
# define systemd_NR_name_to_handle_at 370
|
||||
# elif defined(__i386__)
|
||||
# define systemd_NR_name_to_handle_at 341
|
||||
# elif defined(__ia64__)
|
||||
# define systemd_NR_name_to_handle_at 1326
|
||||
# elif defined(__loongarch_lp64)
|
||||
# define systemd_NR_name_to_handle_at 264
|
||||
# elif defined(__m68k__)
|
||||
# define systemd_NR_name_to_handle_at 340
|
||||
# elif defined(_MIPS_SIM)
|
||||
# if _MIPS_SIM == _MIPS_SIM_ABI32
|
||||
# define systemd_NR_name_to_handle_at 4339
|
||||
# elif _MIPS_SIM == _MIPS_SIM_NABI32
|
||||
# define systemd_NR_name_to_handle_at 6303
|
||||
# elif _MIPS_SIM == _MIPS_SIM_ABI64
|
||||
# define systemd_NR_name_to_handle_at 5298
|
||||
# else
|
||||
# error "Unknown MIPS ABI"
|
||||
# endif
|
||||
# elif defined(__hppa__)
|
||||
# define systemd_NR_name_to_handle_at 325
|
||||
# elif defined(__powerpc__)
|
||||
# define systemd_NR_name_to_handle_at 345
|
||||
# elif defined(__riscv)
|
||||
# if __riscv_xlen == 32
|
||||
# define systemd_NR_name_to_handle_at 264
|
||||
# elif __riscv_xlen == 64
|
||||
# define systemd_NR_name_to_handle_at 264
|
||||
# else
|
||||
# error "Unknown RISC-V ABI"
|
||||
# endif
|
||||
# elif defined(__s390__)
|
||||
# define systemd_NR_name_to_handle_at 335
|
||||
# elif defined(__sparc__)
|
||||
# define systemd_NR_name_to_handle_at 332
|
||||
# elif defined(__x86_64__)
|
||||
# if defined(__ILP32__)
|
||||
# define systemd_NR_name_to_handle_at (303 | /* __X32_SYSCALL_BIT */ 0x40000000)
|
||||
# else
|
||||
# define systemd_NR_name_to_handle_at 303
|
||||
# endif
|
||||
# elif !defined(missing_arch_template)
|
||||
# warning "name_to_handle_at() syscall number is unknown for your architecture"
|
||||
# endif
|
||||
|
||||
/* may be an (invalid) negative number due to libseccomp, see PR 13319 */
|
||||
# if defined __NR_name_to_handle_at && __NR_name_to_handle_at >= 0
|
||||
# if defined systemd_NR_name_to_handle_at
|
||||
G_STATIC_ASSERT (__NR_name_to_handle_at == systemd_NR_name_to_handle_at);
|
||||
# endif
|
||||
# else
|
||||
# if defined __NR_name_to_handle_at
|
||||
# undef __NR_name_to_handle_at
|
||||
# endif
|
||||
# if defined systemd_NR_name_to_handle_at && systemd_NR_name_to_handle_at >= 0
|
||||
# define __NR_name_to_handle_at systemd_NR_name_to_handle_at
|
||||
# endif
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#ifndef AT_HANDLE_FID
|
||||
#define AT_HANDLE_FID 0x200
|
||||
#endif
|
||||
|
||||
#ifndef AT_HANDLE_MNT_ID_UNIQUE
|
||||
#define AT_HANDLE_MNT_ID_UNIQUE 0x001
|
||||
#endif
|
||||
|
||||
#ifndef AT_HANDLE_CONNECTABLE
|
||||
#define AT_HANDLE_CONNECTABLE 0x002
|
||||
#endif
|
||||
|
||||
#if !HAVE_DECL_NAME_TO_HANDLE_AT && defined(__NR_name_to_handle_at)
|
||||
struct file_handle {
|
||||
unsigned int handle_bytes;
|
||||
int handle_type;
|
||||
unsigned char f_handle[0];
|
||||
};
|
||||
|
||||
static inline int
|
||||
name_to_handle_at (int fd,
|
||||
const char *name,
|
||||
struct file_handle *handle,
|
||||
int *mnt_id,
|
||||
int flags)
|
||||
{
|
||||
return syscall(__NR_name_to_handle_at, fd, name, handle, mnt_id, flags);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -26,6 +26,7 @@ check_functions = [
|
||||
'renameat2',
|
||||
'memfd_create',
|
||||
'copy_file_range',
|
||||
'name_to_handle_at',
|
||||
]
|
||||
conf = configuration_data()
|
||||
foreach check_function : check_functions
|
||||
|
||||
@@ -286,6 +286,111 @@ test_filecopy_procfs (void)
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
test_name_to_handle_at (void)
|
||||
{
|
||||
g_autoptr(GError) error = NULL;
|
||||
g_autofree struct file_handle *handle1 = NULL;
|
||||
g_autofree struct file_handle *handle2 = NULL;
|
||||
g_autofree struct file_handle *handle3 = NULL;
|
||||
uint64_t mnt_id1 = 0;
|
||||
uint64_t mnt_id2 = 0;
|
||||
uint64_t mnt_id3 = 0;
|
||||
glnx_autofd int dfd = -1;
|
||||
gboolean ok;
|
||||
|
||||
/* Create a test directory and file */
|
||||
ok = glnx_shutil_mkdir_p_at_open (AT_FDCWD, "handle_test", 0755, &dfd, NULL, &error);
|
||||
g_assert_no_error (error);
|
||||
g_assert_true (ok);
|
||||
|
||||
ok = glnx_file_replace_contents_at (dfd, "testfile",
|
||||
(const guint8 *)"test", 4,
|
||||
GLNX_FILE_REPLACE_NODATASYNC, NULL, &error);
|
||||
g_assert_no_error (error);
|
||||
g_assert_true (ok);
|
||||
|
||||
ok = glnx_name_to_handle_at (dfd, "testfile", 0, &handle1, &mnt_id1, &error);
|
||||
|
||||
/* Skip the test if the syscall is not supported */
|
||||
if (!ok && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED))
|
||||
return g_test_skip ("name_to_handle_at not supported");
|
||||
|
||||
/* Test 1: Get handle for a regular file */
|
||||
g_assert_no_error (error);
|
||||
g_assert_true (ok);
|
||||
g_assert_nonnull (handle1);
|
||||
g_assert_cmpuint (mnt_id1, >, 0);
|
||||
|
||||
/* Test 2: Get handle for the same file again - should have same handle and mount ID */
|
||||
ok = glnx_name_to_handle_at (dfd, "testfile", 0, &handle2, &mnt_id2, &error);
|
||||
g_assert_no_error (error);
|
||||
g_assert_true (ok);
|
||||
g_assert_nonnull (handle2);
|
||||
g_assert_cmpuint (mnt_id1, ==, mnt_id2);
|
||||
g_assert_cmpuint (handle1->handle_bytes, ==, handle2->handle_bytes);
|
||||
g_assert_cmpuint (handle1->handle_type, ==, handle2->handle_type);
|
||||
g_assert_cmpmem (handle1->f_handle, handle1->handle_bytes,
|
||||
handle2->f_handle, handle2->handle_bytes);
|
||||
|
||||
g_clear_pointer (&handle1, g_free);
|
||||
g_clear_pointer (&handle2, g_free);
|
||||
|
||||
/* Test 3: Get handle for a directory */
|
||||
ok = glnx_name_to_handle_at (AT_FDCWD, "handle_test", 0, &handle1, &mnt_id1, &error);
|
||||
g_assert_no_error (error);
|
||||
g_assert_true (ok);
|
||||
g_assert_nonnull (handle1);
|
||||
g_assert_cmpuint (mnt_id1, >, 0);
|
||||
|
||||
g_clear_pointer (&handle1, g_free);
|
||||
|
||||
/* Test 4: Test with AT_EMPTY_PATH */
|
||||
ok = glnx_name_to_handle_at (dfd, "", AT_EMPTY_PATH, &handle1, &mnt_id1, &error);
|
||||
g_assert_no_error (error);
|
||||
g_assert_true (ok);
|
||||
g_assert_nonnull (handle1);
|
||||
g_assert_cmpuint (mnt_id1, >, 0);
|
||||
|
||||
g_clear_pointer (&handle1, g_free);
|
||||
|
||||
/* Test 5: Create symlink and test AT_SYMLINK_FOLLOW */
|
||||
g_assert_no_errno (symlinkat ("testfile", dfd, "testlink"));
|
||||
|
||||
ok = glnx_name_to_handle_at (dfd, "testlink", 0, &handle1, &mnt_id1, &error);
|
||||
g_assert_no_error (error);
|
||||
g_assert_true (ok);
|
||||
g_assert_nonnull (handle1);
|
||||
|
||||
ok = glnx_name_to_handle_at (dfd, "testlink", AT_SYMLINK_FOLLOW, &handle2, &mnt_id2, &error);
|
||||
g_assert_no_error (error);
|
||||
g_assert_true (ok);
|
||||
g_assert_nonnull (handle2);
|
||||
|
||||
/* files are on the same mount, so we should get the same kind of handle */
|
||||
g_assert_true (handle1->handle_bytes == handle2->handle_bytes);
|
||||
g_assert_true (handle1->handle_type == handle2->handle_type);
|
||||
/* handle1 != handle2 */
|
||||
g_assert_false (memcmp (handle1->f_handle, handle2->f_handle, handle1->handle_bytes) == 0);
|
||||
|
||||
/* Following the symlink should give us the same handle as the target */
|
||||
ok = glnx_name_to_handle_at (dfd, "testfile", 0, &handle3, &mnt_id3, &error);
|
||||
g_assert_no_error (error);
|
||||
g_assert_true (ok);
|
||||
g_assert_cmpmem (handle2->f_handle, handle2->handle_bytes,
|
||||
handle3->f_handle, handle3->handle_bytes);
|
||||
|
||||
g_clear_pointer (&handle1, g_free);
|
||||
g_clear_pointer (&handle2, g_free);
|
||||
g_clear_pointer (&handle3, g_free);
|
||||
|
||||
/* Test 6: Error case - non-existent file */
|
||||
ok = glnx_name_to_handle_at (dfd, "nosuchfile", 0, &handle1, &mnt_id1, &error);
|
||||
g_assert_false (ok);
|
||||
g_assert_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND);
|
||||
g_clear_error (&error);
|
||||
}
|
||||
|
||||
static void
|
||||
test_fd_reopen (void)
|
||||
{
|
||||
@@ -452,6 +557,7 @@ int main (int argc, char **argv)
|
||||
g_test_add_func ("/renameat2-noreplace", test_renameat2_noreplace);
|
||||
g_test_add_func ("/renameat2-exchange", test_renameat2_exchange);
|
||||
g_test_add_func ("/fstat", test_fstatat);
|
||||
g_test_add_func ("/name-to-handle-at", test_name_to_handle_at);
|
||||
g_test_add_func ("/fd-reopen", test_fd_reopen);
|
||||
|
||||
ret = g_test_run();
|
||||
|
||||
Reference in New Issue
Block a user