Update subtree: libglnx 2026-06-18

* backports, local-alloc: Provide a backport of g_autofd
* build: Add meson.override_dependency('libglnx', libglnx_dep)
* fdio: Add support for name_to_handle_at
* chase: Add glnx_chase_and_mkdirat

Signed-off-by: Sebastian Wick <sebastian.wick@redhat.com>
This commit is contained in:
Sebastian Wick
2026-06-18 14:09:38 +02:00
12 changed files with 818 additions and 61 deletions

View File

@@ -80,6 +80,40 @@ g_clear_fd (int *fd_ptr,
return _glnx_close (fd, error);
G_GNUC_END_IGNORE_DEPRECATIONS
}
#else
static inline gboolean
_glnx_clear_fd (int *fd_ptr,
GError **error)
{
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
return g_clear_fd (fd_ptr, error);
G_GNUC_END_IGNORE_DEPRECATIONS
}
#define g_clear_fd _glnx_clear_fd
#endif
/* This is part of the backport of g_autofd, but we define it
* unconditionally because it's also used to implement glnx_close_fd() */
static inline void
_glnx_clear_fd_ignore_error (int *fd_ptr)
{
/* Don't overwrite thread-local errno if closing the fd fails */
int errsv = errno;
if (!g_clear_fd (fd_ptr, NULL))
{
/* Do nothing: we ignore all errors, except for EBADF which
* is a programming error, checked for by g_close(). */
}
errno = errsv;
}
#if !GLIB_CHECK_VERSION(2, 76, 0)
#define g_autofd __attribute__((cleanup(_glnx_clear_fd_ignore_error)))
#endif
#if !GLIB_CHECK_VERSION(2, 40, 0)

View File

@@ -17,6 +17,7 @@
#include <unistd.h>
#include <glnx-backports.h>
#include <glnx-dirfd.h>
#include <glnx-errors.h>
#include <glnx-fdio.h>
#include <glnx-local-alloc.h>
@@ -46,6 +47,13 @@
#define GLNX_CHASE_ALL_FLAGS \
(GLNX_CHASE_ALL_DEBUG_FLAGS | GLNX_CHASE_ALL_REGULAR_FLAGS)
typedef int (* GlnxChaseCallback)(int next_fd,
int current_fd,
const char *segment,
int open_tree_flags,
gpointer user_data,
GError **error);
typedef GQueue GlnxStatxQueue;
static void
@@ -328,10 +336,12 @@ extract_next_segment (const char **remaining,
* we're in full control over the resolving.
*/
static int
chase_manual (int dirfd,
const char *path,
GlnxChaseFlags flags,
GError **error)
chase_manual (int dirfd,
const char *path,
GlnxChaseFlags flags,
GlnxChaseCallback callback,
gpointer user_data,
GError **error)
{
gboolean is_absolute;
g_autofree char *buffer = NULL;
@@ -349,6 +359,7 @@ chase_manual (int dirfd,
* - none of the resolve flags are set (they would require work here)
* - NO_AUTOMOUNT is set (chase_open_tree only triggers the automount for
* last component in some cases)
* - there is no callback
*
* TODO: if we have a guarantee that the open_tree syscall works, we can
* shortcut even without GLNX_CHASE_NO_AUTOMOUNT
@@ -356,7 +367,8 @@ chase_manual (int dirfd,
if ((flags & (GLNX_CHASE_NO_AUTOMOUNT |
GLNX_CHASE_RESOLVE_BENEATH |
GLNX_CHASE_RESOLVE_IN_ROOT |
GLNX_CHASE_RESOLVE_NO_SYMLINKS)) == GLNX_CHASE_NO_AUTOMOUNT)
GLNX_CHASE_RESOLVE_NO_SYMLINKS)) == GLNX_CHASE_NO_AUTOMOUNT &&
callback == NULL)
{
GlnxChaseFlags open_tree_flags =
(flags & (GLNX_CHASE_NOFOLLOW | GLNX_CHASE_ALL_DEBUG_FLAGS));
@@ -466,10 +478,25 @@ chase_manual (int dirfd,
GlnxChaseFlags open_tree_flags =
GLNX_CHASE_NOFOLLOW |
(flags & (GLNX_CHASE_NO_AUTOMOUNT | GLNX_CHASE_ALL_DEBUG_FLAGS));
g_autoptr(GError) local_error = NULL;
next_fd = chase_open_tree (fd, segment, open_tree_flags, &local_error);
/* Note that the callback can be called with next_fd < 0.
* If so, the error is already set, but may be cleared by
* the callback if it can recover from an error that already
* occurred. */
if (callback)
{
next_fd = callback (next_fd, fd, segment, open_tree_flags,
user_data, &local_error);
}
next_fd = chase_open_tree (fd, segment, open_tree_flags, error);
if (next_fd < 0)
return -1;
{
g_propagate_error (error, g_steal_pointer (&local_error));
return -1;
}
}
if (!glnx_chase_statx (next_fd, no_automount, &st, error))
@@ -602,30 +629,13 @@ chase_manual (int dirfd,
return g_steal_fd (&owned_fd);
}
/**
* glnx_chaseat:
* @dirfd: a directory file descriptor
* @path: a path
* @flags: combination of GlnxChaseFlags flags
* @error: a #GError
*
* Behaves similar to openat, but with a number of differences:
*
* - All file descriptors which get returned are O_PATH and O_CLOEXEC. If you
* want to actually open the file for reading or writing, use glnx_fd_reopen,
* openat, or other at-style functions.
* - By default, automounts get triggered and the O_PATH fd will point to inodes
* in the newly mounted filesystem if an automount is encountered. This can be
* turned off with GLNX_CHASE_NO_AUTOMOUNT.
* - The GLNX_CHASE_RESOLVE_ flags can be used to safely deal with symlinks.
*
* Returns: the chased file, or -1 with @error set on error
*/
int
glnx_chaseat (int dirfd,
const char *path,
GlnxChaseFlags flags,
GError **error)
static int
glnx_chaseat_full (int dirfd,
const char *path,
GlnxChaseFlags flags,
GlnxChaseCallback callback,
gpointer user_data,
GError **error)
{
static gboolean can_openat2 = TRUE;
glnx_autofd int fd = -1;
@@ -643,15 +653,14 @@ glnx_chaseat (int dirfd,
g_return_val_if_fail ((must_flags & (must_flags - 1)) == 0, -1);
}
/* TODO: Add a callback which is called for every resolved path segment, to
* allow users to verify and expand the functionality safely. */
/* We need the manual impl for NO_AUTOMOUNT, and we can skip this, if we don't
* have openat2 at all.
* Technically racy (static, not synced), but both paths work fine so it
* doesn't matter. */
if (can_openat2 && (flags & GLNX_CHASE_NO_AUTOMOUNT) == 0 &&
(flags & GLNX_CHASE_DEBUG_NO_OPENAT2) == 0)
if (can_openat2 &&
(flags & GLNX_CHASE_NO_AUTOMOUNT) == 0 &&
(flags & GLNX_CHASE_DEBUG_NO_OPENAT2) == 0 &&
callback == NULL)
{
uint64_t openat2_flags = 0;
uint64_t openat2_resolve = 0;
@@ -690,7 +699,7 @@ glnx_chaseat (int dirfd,
if (fd < 0)
{
fd = chase_manual (dirfd, path, flags, error);
fd = chase_manual (dirfd, path, flags, callback, user_data, error);
if (fd < 0)
return -1;
}
@@ -744,6 +753,34 @@ glnx_chaseat (int dirfd,
return g_steal_fd (&fd);
}
/**
* glnx_chaseat:
* @dirfd: a directory file descriptor
* @path: a path
* @flags: combination of GlnxChaseFlags flags
* @error: a #GError
*
* Behaves similar to openat, but with a number of differences:
*
* - All file descriptors which get returned are O_PATH and O_CLOEXEC. If you
* want to actually open the file for reading or writing, use glnx_fd_reopen,
* openat, or other at-style functions.
* - By default, automounts get triggered and the O_PATH fd will point to inodes
* in the newly mounted filesystem if an automount is encountered. This can be
* turned off with GLNX_CHASE_NO_AUTOMOUNT.
* - The GLNX_CHASE_RESOLVE_ flags can be used to safely deal with symlinks.
*
* Returns: the chased file, or -1 with @error set on error
*/
int
glnx_chaseat (int dirfd,
const char *path,
GlnxChaseFlags flags,
GError **error)
{
return glnx_chaseat_full (dirfd, path, flags, NULL, NULL, error);
}
/**
* glnx_chase_and_statxat:
* @dirfd: a directory file descriptor
@@ -787,3 +824,89 @@ glnx_chase_and_statxat (int dirfd,
return g_steal_fd (&fd);
}
static int
chase_and_mkdir (int next_fd,
int current_fd,
const char *segment,
G_GNUC_UNUSED int open_tree_flags,
gpointer user_data,
GError **error)
{
mode_t mode = GPOINTER_TO_INT (user_data);
glnx_autofd int new_fd = -1;
/* if chase managed to get the next segment, we already got our answer */
if (next_fd >= 0)
return next_fd;
/* if the problem isn't that the file doesn't exist, we propagate the error */
g_assert (*error != NULL);
if (!g_error_matches (*error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
return -1;
g_clear_error (error);
/* create the directory with the specified mode */
if (!glnx_ensure_dir (current_fd, segment, mode, error))
return -1;
/* Get a fd to the created dir.
* This is racy, meaning another process can modify the filesystem and we
* might open a directory that we did not create and might have a different
* mode. There isn't a kernel API which returns a fd or an inode which makes
* this race unavoidable. This is fine though because the semantics of
* `glnx_chase_and_mkdirat` accept arbitrary directories (even with different
* modes) when chasing the path.
*/
new_fd = openat (current_fd, segment,
O_CLOEXEC | O_PATH | O_DIRECTORY | O_NOFOLLOW);
if (new_fd < 0)
return glnx_fd_throw_errno_prefix (error, "opening created dir failed");
return g_steal_fd (&new_fd);
}
/**
* glnx_chase_and_mkdirat:
* @dirfd: a directory file descriptor
* @path: a path
* @flags: restricted combination of GlnxChaseFlags flags
* @mode: the mode for new directories
* @error: a #GError
*
* Same as glnx_chase with `GLNX_CHASE_MUST_BE_DIRECTORY`, but when a path
* segment does not exist, a directory is created for the segment with the mode
* @mode.
* This essentially implement a fd-relative equivalent of g_mkdir_with_parents()
* or `mkdir -p`.
* Note, that directories in the path which already exist can have arbitrary
* modes.
*
* See glnx_chaseat for the meaning of @dirfd and @path.
*
* The @flags argument is the same as in glnx_chaseat, but setting
* `GLNX_CHASE_MUST_BE_REGULAR`, `GLNX_CHASE_MUST_BE_SOCKET`, or
* `GLNX_CHASE_MUST_BE_DIRECTORY` is an error.
*
* Returns: the chased file, or -1 with @error set on error
*/
int
glnx_chase_and_mkdirat (int dirfd,
const char *path,
GlnxChaseFlags flags,
mode_t mode,
GError **error)
{
g_return_val_if_fail ((flags & ~(GLNX_CHASE_ALL_FLAGS)) == 0, -1);
g_return_val_if_fail ((flags & (GLNX_CHASE_MUST_BE_REGULAR |
GLNX_CHASE_MUST_BE_SOCKET |
GLNX_CHASE_MUST_BE_DIRECTORY)) == 0, -1);
g_return_val_if_fail ((mode & ~((mode_t) 07777)) == 0, -1);
/* This function always implies GLNX_CHASE_MUST_BE_DIRECTORY */
flags |= GLNX_CHASE_MUST_BE_DIRECTORY;
return glnx_chaseat_full (dirfd, path, flags,
chase_and_mkdir, GINT_TO_POINTER (mode),
error);
}

View File

@@ -8,6 +8,8 @@
#include <glib.h>
#include <glnx-missing.h>
typedef enum _GlnxChaseFlags {
/* Default */
GLNX_CHASE_DEFAULT = 0,
@@ -48,4 +50,10 @@ int glnx_chase_and_statxat (int dirfd,
struct glnx_statx *statbuf,
GError **error);
int glnx_chase_and_mkdirat (int dirfd,
const char *path,
GlnxChaseFlags flags,
mode_t mode,
GError **error);
G_END_DECLS

View File

@@ -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;
}

View File

@@ -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

View File

@@ -51,38 +51,23 @@ glnx_local_obj_unref (void *v)
* glnx_close_fd:
* @fdp: Pointer to fd
*
* Effectively `close (g_steal_fd (&fd))`. Also
* asserts that `close()` did not raise `EBADF` - encountering
* that error is usually a critical bug in the program.
* Same as `g_clear_fd()`, but ignoring the error (if any) and making sure
* not to alter `errno`. As a result, this function can be used for cleanup
* in contexts where `errno` needs to be preserved.
*/
static inline void
glnx_close_fd (int *fdp)
{
int errsv;
g_assert (fdp);
int fd = g_steal_fd (fdp);
if (fd >= 0)
{
errsv = errno;
if (close (fd) < 0)
g_assert (errno != EBADF);
errno = errsv;
}
}
#define glnx_close_fd _glnx_clear_fd_ignore_error
/**
* glnx_fd_close:
*
* Deprecated in favor of `glnx_autofd`.
* Deprecated in favor of `g_autofd`.
*/
#define glnx_fd_close __attribute__((cleanup(glnx_close_fd)))
#define glnx_fd_close g_autofd
/**
* glnx_autofd:
*
* Call close() on a variable location when it goes out of scope.
* Deprecated in favor of `g_autofd`.
*/
#define glnx_autofd __attribute__((cleanup(glnx_close_fd)))
#define glnx_autofd g_autofd
G_END_DECLS

View File

@@ -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

View File

@@ -26,6 +26,7 @@ check_functions = [
'renameat2',
'memfd_create',
'copy_file_range',
'name_to_handle_at',
]
conf = configuration_data()
foreach check_function : check_functions
@@ -110,6 +111,9 @@ libglnx_dep = declare_dependency(
dependencies : libglnx_deps,
include_directories : libglnx_inc,
link_with : libglnx)
if meson.version().version_compare('>= 0.54.0')
meson.override_dependency('libglnx', libglnx_dep)
endif
subdir('tests')

View File

@@ -23,6 +23,7 @@
#include "libglnx-testlib.h"
#include <errno.h>
#include <fcntl.h>
#include <glib/gstdio.h>
@@ -72,3 +73,21 @@ _glnx_test_auto_temp_dir_leave (_GLnxTestAutoTempDir *dir)
g_free (dir->old_cwd);
g_free (dir);
}
void
_glnx_test_assert_fd_was_closed (int fd)
{
/* We can't tell a fd was really closed without behaving as though it
* was still valid */
if (g_test_undefined ())
{
int result;
int errsv;
errno = 0;
result = fcntl (fd, F_GETFD, 0);
errsv = errno;
g_assert_cmpint (result, <, 0);
g_assert_cmpint (errsv, ==, EBADF);
}
}

View File

@@ -1,7 +1,7 @@
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
*
* Copyright (C) 2017 Red Hat, Inc.
* Copyright 2019 Collabora Ltd.
* Copyright 2019-2022 Collabora Ltd.
* SPDX-License-Identifier: LGPL-2.0-or-later
*
* This library is free software; you can redistribute it and/or
@@ -23,6 +23,7 @@
#pragma once
#include <glib.h>
#include <glib/gstdio.h>
#include "glnx-backport-autoptr.h"
@@ -47,3 +48,5 @@ G_DEFINE_AUTOPTR_CLEANUP_FUNC(_GLnxTestAutoTempDir, _glnx_test_auto_temp_dir_lea
#define _GLNX_TEST_SCOPED_TEMP_DIR \
G_GNUC_UNUSED g_autoptr(_GLnxTestAutoTempDir) temp_dir = _glnx_test_auto_temp_dir_enter ()
void _glnx_test_assert_fd_was_closed (int fd);

View File

@@ -50,6 +50,18 @@ path_get_ino (const char *path)
return st.st_ino;
}
static mode_t
get_mode (int fd)
{
int r;
struct stat st;
r = fstatat (fd, "", &st, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
g_assert_cmpint (r, >=, 0);
return st.st_mode & 0777;
}
static char *
get_abspath (int dfd,
const char *path)
@@ -586,6 +598,62 @@ test_chase_and_statxat_permissions (void)
g_clear_fd (&chase_fd, NULL);
}
static void
test_chase_and_mkdir (void)
{
g_autoptr(GError) error = NULL;
glnx_autofd int dfd = -1;
glnx_autofd int file_fd = -1;
glnx_autofd int chase_fd = -1;
ino_t expected_ino;
g_assert_true (glnx_shutil_mkdir_p_at_open (AT_FDCWD, "d1", 0755,
&dfd,
NULL, &error));
g_assert_no_error (error);
chase_fd = glnx_chase_and_mkdirat (AT_FDCWD, "d1/d2/d3",
GLNX_CHASE_DEFAULT,
0755,
&error);
g_assert_cmpint (chase_fd, >=, 0);
g_assert_no_error (error);
file_fd = openat (AT_FDCWD, "d1/d2/d3", O_PATH | O_CLOEXEC | O_NOFOLLOW);
g_assert_cmpint (file_fd, >=, 0);
expected_ino = get_ino (file_fd);
g_assert_cmpint (get_ino (chase_fd), ==, expected_ino);
g_assert_cmpint (get_mode (dfd), ==, 0755);
g_assert_cmpint (get_mode (chase_fd), ==, 0755);
g_clear_fd (&chase_fd, NULL);
g_clear_fd (&file_fd, NULL);
chase_fd = glnx_chase_and_mkdirat (AT_FDCWD, "d1/d4/d5",
GLNX_CHASE_DEFAULT,
0700,
&error);
g_assert_cmpint (chase_fd, >=, 0);
g_assert_no_error (error);
file_fd = openat (AT_FDCWD, "d1", O_PATH | O_CLOEXEC | O_NOFOLLOW);
g_assert_cmpint (file_fd, >=, 0);
g_assert_cmpint (get_mode (file_fd), ==, 0755);
g_clear_fd (&file_fd, NULL);
file_fd = openat (AT_FDCWD, "d1/d4", O_PATH | O_CLOEXEC | O_NOFOLLOW);
g_assert_cmpint (file_fd, >=, 0);
g_assert_cmpint (get_mode (file_fd), ==, 0700);
g_clear_fd (&file_fd, NULL);
file_fd = openat (AT_FDCWD, "d1/d4/d5", O_PATH | O_CLOEXEC | O_NOFOLLOW);
g_assert_cmpint (file_fd, >=, 0);
g_assert_cmpint (get_mode (file_fd), ==, 0700);
g_clear_fd (&file_fd, NULL);
}
int main (int argc, char **argv)
{
_GLNX_TEST_SCOPED_TEMP_DIR;
@@ -602,6 +670,7 @@ int main (int argc, char **argv)
g_test_add_func ("/chase-and-statxat-basic", test_chase_and_statxat_basic);
g_test_add_func ("/chase-and-statxat-symlink", test_chase_and_statxat_symlink);
g_test_add_func ("/chase-and-statxat-permissions", test_chase_and_statxat_permissions);
g_test_add_func ("/chase-and-mkdir", test_chase_and_mkdir);
ret = g_test_run();

View File

@@ -29,6 +29,125 @@
#include "libglnx-testlib.h"
static void
test_close (void)
{
g_autoptr(GError) error = NULL;
int errsv;
int fd = -2;
int fd_borrowed;
g_test_summary ("Exercise glnx_close_fd");
/* Closing a non-fd is a no-op, and preserves errno.
* EILSEQ is an arbitrary valid value of errno that is unlikely
* to be set accidentally as a side-effect of I/O. */
g_test_message ("Closing a non-fd is a no-op and preserves errno...");
errno = EILSEQ;
glnx_close_fd (&fd);
errsv = errno;
g_assert_cmpint (fd, ==, -1);
g_assert_cmpint (errsv, ==, EILSEQ);
/* Closing a valid fd really closes it, and preserves errno. */
g_test_message ("Closing a valid fd preserves errno...");
glnx_opendirat (AT_FDCWD, "/", TRUE, &fd, &error);
g_assert_no_error (error);
g_assert_cmpint (fd, >=, 0);
fd_borrowed = fd;
errno = EILSEQ;
glnx_close_fd (&fd);
errsv = errno;
g_assert_cmpint (fd, ==, -1);
g_assert_cmpint (errsv, ==, EILSEQ);
_glnx_test_assert_fd_was_closed (fd_borrowed);
}
/* Exercise glnx_close_fd in the case where close() fails.
* The only convenient way we can arrange for this to happen is to use
* an invalid fd, which is a programming error.
*
* This function is only run under g_test_undefined(), and assumes the
* implementation detail that GLib responds to that programming error
* with a critical warning rather than a fatal error. */
static void
test_close_ebadf_subprocess (void)
{
g_autoptr(GError) error = NULL;
int errsv;
int fd = -2;
int non_fd;
/* Preparation: Open a fd, and close it, leaving non_fd set to the
* file descriptor number. */
glnx_opendirat (AT_FDCWD, "/", TRUE, &fd, &error);
g_assert_no_error (error);
g_assert_cmpint (fd, >=, 0);
close (fd);
non_fd = fd;
_glnx_test_assert_fd_was_closed (non_fd);
/* "Closing" the non-fd provokes a critical warning. */
g_log_set_always_fatal (G_LOG_FATAL_MASK);
g_log_set_fatal_mask ("GLib", G_LOG_FATAL_MASK);
errno = EILSEQ;
glnx_close_fd (&non_fd);
errsv = errno;
g_assert_cmpint (non_fd, ==, -1);
/* We preserved errno. */
g_assert_cmpint (errsv, ==, EILSEQ);
g_print ("Closing invalid fd preserved errno\n");
}
static void
test_close_ebadf (void)
{
g_test_summary ("Exercise glnx_close_fd when close() fails");
/* If close() fails, it still preserves errno.
* The only convenient way to make close() fail on-demand is EBADF. */
if (g_test_subprocess ())
{
test_close_ebadf_subprocess ();
return;
}
if (g_test_undefined ())
{
g_test_message ("Closing invalid fd preserves errno...");
#if GLIB_CHECK_VERSION (2, 38, 0)
g_test_trap_subprocess (NULL, 0, G_TEST_SUBPROCESS_DEFAULT);
#else
if (g_test_trap_fork (0, 0))
{
test_close_ebadf_subprocess ();
_exit (0);
}
#endif
g_test_trap_assert_passed ();
g_test_trap_assert_stdout ("*Closing invalid fd preserved errno*");
#if !GLIB_CHECK_VERSION(2, 76, 0)
/* We can assert that our backport emits this message */
g_test_trap_assert_stderr ("*_glnx_close(fd:*) failed with EBADF*");
#else
/* We can't assert anything this specific about GLib's,
* but as an implementation detail, it's currently very similar */
# if 0
g_test_trap_assert_stderr ("*g_close(fd:*) failed with EBADF*");
# endif
#endif
}
else
{
g_test_skip ("Can't test this without provoking undefined behaviour");
}
}
static gboolean
renameat_test_setup (int *out_srcfd, int *out_destfd,
GError **error)
@@ -286,6 +405,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)
{
@@ -445,6 +669,8 @@ int main (int argc, char **argv)
g_test_init (&argc, &argv, NULL);
g_test_add_func ("/close", test_close);
g_test_add_func ("/close/ebadf", test_close_ebadf);
g_test_add_func ("/tmpfile", test_tmpfile);
g_test_add_func ("/stdio-file", test_stdio_file);
g_test_add_func ("/filecopy", test_filecopy);
@@ -452,6 +678,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();