mirror of
https://github.com/flatpak/flatpak.git
synced 2026-06-26 01:06:57 -04:00
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:
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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')
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user