mirror of
https://github.com/flatpak/flatpak.git
synced 2026-05-16 12:50:01 -04:00
Update subtree: libglnx 2026-04-07
* fdio: Avoid relying on VLAs or gcc-specific constant-folding * errors: Fix URL to an old libgsystem commit * lockfile: Assert non-null path in make_lock_file for analyzers * backports: Add g_clear_fd * glnx-errors.h: add glnx_fd_throw[_*] variants * fdio: Add glnx_fd_reopen * local-alloc: Remove duplicate definition of glnx_unref_object * fdio: Add glnx_statx * chase: Add glnx_chaseat which functions similar to openat2 * chase: Add glnx_chase_and_statxat Signed-off-by: Simon McVittie <smcv@collabora.com>
This commit is contained in:
@@ -37,6 +37,8 @@ libglnx_la_SOURCES = \
|
||||
$(libglnx_srcpath)/glnx-backport-testutils.c \
|
||||
$(libglnx_srcpath)/glnx-backports.h \
|
||||
$(libglnx_srcpath)/glnx-backports.c \
|
||||
$(libglnx_srcpath)/glnx-chase.h \
|
||||
$(libglnx_srcpath)/glnx-chase.c \
|
||||
$(libglnx_srcpath)/glnx-local-alloc.h \
|
||||
$(libglnx_srcpath)/glnx-local-alloc.c \
|
||||
$(libglnx_srcpath)/glnx-errors.h \
|
||||
|
||||
@@ -106,6 +106,78 @@ _glnx_strv_equal (const gchar * const *strv1,
|
||||
}
|
||||
#endif
|
||||
|
||||
#if !GLIB_CHECK_VERSION(2, 76, 0)
|
||||
gboolean
|
||||
_glnx_close (gint fd,
|
||||
GError **error)
|
||||
{
|
||||
int res;
|
||||
|
||||
/* Important: if @error is NULL, we must not do anything that is
|
||||
* not async-signal-safe.
|
||||
*/
|
||||
res = close (fd);
|
||||
|
||||
if (res == -1)
|
||||
{
|
||||
int errsv = errno;
|
||||
|
||||
if (errsv == EINTR)
|
||||
{
|
||||
/* Just ignore EINTR for now; a retry loop is the wrong thing to do
|
||||
* on Linux at least. Anyone who wants to add a conditional check
|
||||
* for e.g. HP-UX is welcome to do so later...
|
||||
*
|
||||
* close_func_with_invalid_fds() in gspawn.c has similar logic.
|
||||
*
|
||||
* https://lwn.net/Articles/576478/
|
||||
* http://lkml.indiana.edu/hypermail/linux/kernel/0509.1/0877.html
|
||||
* https://bugzilla.gnome.org/show_bug.cgi?id=682819
|
||||
* http://utcc.utoronto.ca/~cks/space/blog/unix/CloseEINTR
|
||||
* https://sites.google.com/site/michaelsafyan/software-engineering/checkforeintrwheninvokingclosethinkagain
|
||||
*
|
||||
* `close$NOCANCEL()` in gstdioprivate.h, on macOS, ensures that the fd is
|
||||
* closed even if it did return EINTR.
|
||||
*/
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
if (error)
|
||||
{
|
||||
g_set_error_literal (error, G_FILE_ERROR,
|
||||
g_file_error_from_errno (errsv),
|
||||
g_strerror (errsv));
|
||||
}
|
||||
|
||||
if (errsv == EBADF)
|
||||
{
|
||||
/* There is a bug. Fail an assertion. Note that this function is supposed to be
|
||||
* async-signal-safe, but in case an assertion fails, all bets are already off. */
|
||||
if (fd >= 0)
|
||||
{
|
||||
/* Closing an non-negative, invalid file descriptor is a bug. The bug is
|
||||
* not necessarily in the caller of _glnx_close(), but somebody else
|
||||
* might have wrongly closed fd. In any case, there is a serious bug
|
||||
* somewhere. */
|
||||
g_critical ("_glnx_close(fd:%d) failed with EBADF. The tracking of file descriptors got messed up", fd);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Closing a negative "file descriptor" is less problematic. It's still a nonsensical action
|
||||
* from the caller. Assert against that too. */
|
||||
g_critical ("_glnx_close(fd:%d) failed with EBADF. This is not a valid file descriptor", fd);
|
||||
}
|
||||
}
|
||||
|
||||
errno = errsv;
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if !GLIB_CHECK_VERSION(2, 80, 0)
|
||||
/* This function is called between fork() and exec() and hence must be
|
||||
* async-signal-safe (see signal-safety(7)). */
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
#include <string.h>
|
||||
|
||||
#include <glib-unix.h>
|
||||
#include <glib/gstdio.h>
|
||||
#include <gio/gio.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
@@ -53,6 +54,34 @@ G_BEGIN_DECLS
|
||||
} G_STMT_END
|
||||
#endif
|
||||
|
||||
#if !GLIB_CHECK_VERSION(2, 76, 0)
|
||||
gboolean _glnx_close (gint fd,
|
||||
GError **error);
|
||||
#else
|
||||
#define _glnx_close g_close
|
||||
#endif
|
||||
|
||||
#if !GLIB_CHECK_VERSION(2, 76, 0)
|
||||
static inline gboolean
|
||||
g_clear_fd (int *fd_ptr,
|
||||
GError **error)
|
||||
{
|
||||
int fd = *fd_ptr;
|
||||
|
||||
*fd_ptr = -1;
|
||||
|
||||
if (fd < 0)
|
||||
return TRUE;
|
||||
|
||||
/* Suppress "Not available before" warning */
|
||||
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
|
||||
/* This importantly calls _glnx_close to always get async-signal-safe if
|
||||
* error == NULL */
|
||||
return _glnx_close (fd, error);
|
||||
G_GNUC_END_IGNORE_DEPRECATIONS
|
||||
}
|
||||
#endif
|
||||
|
||||
#if !GLIB_CHECK_VERSION(2, 40, 0)
|
||||
#define g_info(...) g_log (G_LOG_DOMAIN, G_LOG_LEVEL_INFO, __VA_ARGS__)
|
||||
#endif
|
||||
|
||||
789
subprojects/libglnx/glnx-chase.c
Normal file
789
subprojects/libglnx/glnx-chase.c
Normal file
@@ -0,0 +1,789 @@
|
||||
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
|
||||
*
|
||||
* Copyright (C) 2026 Red Hat, Inc.
|
||||
* SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
*
|
||||
* glnx_chaseat was inspired by systemd's chase
|
||||
*/
|
||||
|
||||
#include "libglnx-config.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <stdint.h>
|
||||
#include <sys/mount.h>
|
||||
#include <sys/statfs.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <sys/vfs.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <glnx-backports.h>
|
||||
#include <glnx-errors.h>
|
||||
#include <glnx-fdio.h>
|
||||
#include <glnx-local-alloc.h>
|
||||
#include <glnx-missing.h>
|
||||
|
||||
#include <glnx-chase.h>
|
||||
|
||||
#define AUTOFS_SUPER_MAGIC 0x0187 /* man fstatfs */
|
||||
|
||||
#define GLNX_CHASE_DEBUG_NO_OPENAT2 (1U << 31)
|
||||
#define GLNX_CHASE_DEBUG_NO_OPEN_TREE (1U << 30)
|
||||
|
||||
#define GLNX_CHASE_ALL_DEBUG_FLAGS \
|
||||
(GLNX_CHASE_DEBUG_NO_OPENAT2 | \
|
||||
GLNX_CHASE_DEBUG_NO_OPEN_TREE)
|
||||
|
||||
#define GLNX_CHASE_ALL_REGULAR_FLAGS \
|
||||
(GLNX_CHASE_NO_AUTOMOUNT | \
|
||||
GLNX_CHASE_NOFOLLOW | \
|
||||
GLNX_CHASE_RESOLVE_BENEATH | \
|
||||
GLNX_CHASE_RESOLVE_IN_ROOT | \
|
||||
GLNX_CHASE_RESOLVE_NO_SYMLINKS | \
|
||||
GLNX_CHASE_MUST_BE_REGULAR | \
|
||||
GLNX_CHASE_MUST_BE_DIRECTORY | \
|
||||
GLNX_CHASE_MUST_BE_SOCKET)
|
||||
|
||||
#define GLNX_CHASE_ALL_FLAGS \
|
||||
(GLNX_CHASE_ALL_DEBUG_FLAGS | GLNX_CHASE_ALL_REGULAR_FLAGS)
|
||||
|
||||
typedef GQueue GlnxStatxQueue;
|
||||
|
||||
static void
|
||||
glnx_statx_queue_push (GlnxStatxQueue *queue,
|
||||
const struct glnx_statx *st)
|
||||
{
|
||||
struct glnx_statx *copy;
|
||||
|
||||
copy = g_memdup2 (st, sizeof (*st));
|
||||
g_queue_push_tail (queue, copy);
|
||||
}
|
||||
|
||||
static void
|
||||
glnx_statx_queue_free_element (gpointer element,
|
||||
G_GNUC_UNUSED gpointer userdata)
|
||||
{
|
||||
g_free (element);
|
||||
}
|
||||
|
||||
static void
|
||||
glnx_statx_queue_free (GlnxStatxQueue *squeue)
|
||||
{
|
||||
GQueue *queue = (GQueue *) squeue;
|
||||
|
||||
/* Same as g_queue_clear_full (queue, g_free), but works for <2.60 */
|
||||
g_queue_foreach (queue, glnx_statx_queue_free_element, NULL);
|
||||
g_queue_clear (queue);
|
||||
}
|
||||
|
||||
G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC(GlnxStatxQueue, glnx_statx_queue_free)
|
||||
|
||||
static gboolean
|
||||
glnx_statx_inode_same (const struct glnx_statx *a,
|
||||
const struct glnx_statx *b)
|
||||
{
|
||||
g_assert ((a->stx_mask & (GLNX_STATX_TYPE | GLNX_STATX_INO)) ==
|
||||
(GLNX_STATX_TYPE | GLNX_STATX_INO));
|
||||
g_assert ((b->stx_mask & (GLNX_STATX_TYPE | GLNX_STATX_INO)) ==
|
||||
(GLNX_STATX_TYPE | GLNX_STATX_INO));
|
||||
|
||||
return ((a->stx_mode ^ b->stx_mode) & S_IFMT) == 0 &&
|
||||
a->stx_dev_major == b->stx_dev_major &&
|
||||
a->stx_dev_minor == b->stx_dev_minor &&
|
||||
a->stx_ino == b->stx_ino;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
glnx_statx_mount_same (const struct glnx_statx *a,
|
||||
const struct glnx_statx *b)
|
||||
{
|
||||
g_assert ((a->stx_mask & (GLNX_STATX_MNT_ID | GLNX_STATX_MNT_ID_UNIQUE)) != 0);
|
||||
g_assert ((b->stx_mask & (GLNX_STATX_MNT_ID | GLNX_STATX_MNT_ID_UNIQUE)) != 0);
|
||||
|
||||
return a->stx_mnt_id == b->stx_mnt_id;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
glnx_chase_statx (int dfd,
|
||||
int additional_flags,
|
||||
struct glnx_statx *buf,
|
||||
GError **error)
|
||||
{
|
||||
if (!glnx_statx (dfd, "",
|
||||
AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW | additional_flags,
|
||||
GLNX_STATX_TYPE | GLNX_STATX_INO |
|
||||
GLNX_STATX_MNT_ID | GLNX_STATX_MNT_ID_UNIQUE,
|
||||
buf,
|
||||
error))
|
||||
return FALSE;
|
||||
|
||||
if ((buf->stx_mask & (GLNX_STATX_TYPE | GLNX_STATX_INO)) !=
|
||||
(GLNX_STATX_TYPE | GLNX_STATX_INO) ||
|
||||
(buf->stx_mask & (GLNX_STATX_MNT_ID | GLNX_STATX_MNT_ID_UNIQUE)) == 0)
|
||||
{
|
||||
errno = ENODATA;
|
||||
return glnx_throw_errno_prefix (error,
|
||||
"statx didn't return all required fields");
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* TODO: procfs magiclinks handling */
|
||||
|
||||
/* open_tree subset which transparently falls back to openat.
|
||||
*
|
||||
* Returned fd is always OPATH and CLOEXEC.
|
||||
*
|
||||
* With NO_AUTOMOUNT this function never triggers automounts. Otherwise, it only
|
||||
* guarantees to trigger an automount which is on last segment of the path!
|
||||
*
|
||||
* flags can be a combinations of:
|
||||
* - GLNX_CHASE_NO_AUTOMOUNT
|
||||
* - GLNX_CHASE_NOFOLLOW
|
||||
*/
|
||||
static int
|
||||
chase_open_tree (int dirfd,
|
||||
const char *path,
|
||||
GlnxChaseFlags flags,
|
||||
GError **error)
|
||||
{
|
||||
glnx_autofd int fd = -1;
|
||||
static gboolean can_open_tree = TRUE;
|
||||
unsigned int openat_flags = 0;
|
||||
|
||||
g_assert ((flags & ~(GLNX_CHASE_NO_AUTOMOUNT |
|
||||
GLNX_CHASE_NOFOLLOW |
|
||||
GLNX_CHASE_ALL_DEBUG_FLAGS)) == 0);
|
||||
|
||||
/* First we try to actually use open_tree, and then fall back to the impl
|
||||
* using openat.
|
||||
* Technically racy (static, not synced), but both paths work fine so it
|
||||
* doesn't matter. */
|
||||
if (can_open_tree && (flags & GLNX_CHASE_DEBUG_NO_OPEN_TREE) == 0)
|
||||
{
|
||||
unsigned int open_tree_flags = 0;
|
||||
|
||||
open_tree_flags = OPEN_TREE_CLOEXEC;
|
||||
if ((flags & GLNX_CHASE_NOFOLLOW) != 0)
|
||||
open_tree_flags |= AT_SYMLINK_NOFOLLOW;
|
||||
if ((flags & GLNX_CHASE_NO_AUTOMOUNT) != 0)
|
||||
open_tree_flags |= AT_NO_AUTOMOUNT;
|
||||
|
||||
fd = open_tree (dirfd, path, open_tree_flags);
|
||||
|
||||
/* If open_tree is not supported, or blocked (EPERM), we fall back to
|
||||
* openat */
|
||||
if (fd < 0 && G_IN_SET (errno,
|
||||
EOPNOTSUPP,
|
||||
ENOTTY,
|
||||
ENOSYS,
|
||||
EAFNOSUPPORT,
|
||||
EPFNOSUPPORT,
|
||||
EPROTONOSUPPORT,
|
||||
ESOCKTNOSUPPORT,
|
||||
ENOPROTOOPT,
|
||||
EPERM))
|
||||
can_open_tree = FALSE;
|
||||
else if (fd < 0)
|
||||
return glnx_fd_throw_errno_prefix (error, "open_tree");
|
||||
else
|
||||
return g_steal_fd (&fd);
|
||||
}
|
||||
|
||||
openat_flags = O_CLOEXEC | O_PATH;
|
||||
if ((flags & GLNX_CHASE_NOFOLLOW) != 0)
|
||||
openat_flags |= O_NOFOLLOW;
|
||||
|
||||
fd = openat (dirfd, path, openat_flags);
|
||||
if (fd < 0)
|
||||
return glnx_fd_throw_errno_prefix (error, "openat in open_tree fallback");
|
||||
|
||||
/* openat does not trigger automounts, so we have to manually do so
|
||||
* unless NO_AUTOMOUNT was specified */
|
||||
if ((flags & GLNX_CHASE_NO_AUTOMOUNT) == 0)
|
||||
{
|
||||
struct statfs stfs;
|
||||
|
||||
if (fstatfs (fd, &stfs) < 0)
|
||||
return glnx_fd_throw_errno_prefix (error, "fstatfs in open_tree fallback");
|
||||
|
||||
/* fstatfs(2) can then be used to determine if it is, in fact, an
|
||||
* untriggered automount point (.f_type == AUTOFS_SUPER_MAGIC). */
|
||||
if (stfs.f_type == AUTOFS_SUPER_MAGIC)
|
||||
{
|
||||
glnx_autofd int new_fd = -1;
|
||||
|
||||
new_fd = openat (fd, ".", openat_flags | O_DIRECTORY);
|
||||
/* For some reason, openat with O_PATH | O_DIRECTORY does trigger
|
||||
* automounts, without us having to actually open the file, so let's
|
||||
* use this here. It only works for directories though. */
|
||||
if (new_fd >= 0)
|
||||
return g_steal_fd (&new_fd);
|
||||
|
||||
if (errno != ENOTDIR)
|
||||
return glnx_fd_throw_errno_prefix (error, "openat(O_DIRECTORY) in autofs mount open_tree fallback");
|
||||
|
||||
/* The automount is a directory, so let's try to open the file,
|
||||
* which can fail because we are missing permissions, but that's
|
||||
* okay, we only need to trigger automount. */
|
||||
new_fd = openat (fd, ".", (openat_flags & ~O_PATH) |
|
||||
O_RDONLY | O_NONBLOCK | O_CLOEXEC | O_NOCTTY);
|
||||
glnx_close_fd (&new_fd);
|
||||
|
||||
/* And try again with O_PATH */
|
||||
new_fd = openat (dirfd, path, openat_flags);
|
||||
if (new_fd < 0)
|
||||
return glnx_fd_throw_errno_prefix (error, "reopening in autofs mount open_tree fallback");
|
||||
|
||||
if (fstatfs (new_fd, &stfs) < 0)
|
||||
return glnx_fd_throw_errno_prefix (error, "fstatfs in autofs mount open_tree fallback");
|
||||
|
||||
/* bail if we didn't manage to trigger the automount */
|
||||
if (stfs.f_type == AUTOFS_SUPER_MAGIC)
|
||||
{
|
||||
errno = EOPNOTSUPP;
|
||||
return glnx_fd_throw_errno_prefix (error, "unable to trigger automount");
|
||||
}
|
||||
|
||||
return g_steal_fd (&new_fd);
|
||||
}
|
||||
}
|
||||
|
||||
return g_steal_fd (&fd);
|
||||
}
|
||||
|
||||
static int
|
||||
open_cwd (GlnxChaseFlags flags,
|
||||
GError **error)
|
||||
{
|
||||
GLNX_AUTO_PREFIX_ERROR ("cannot open working directory", error);
|
||||
|
||||
/* NO_AUTOMOUNT should be fine here because automount must have been
|
||||
* triggered already for the CWD */
|
||||
return chase_open_tree (AT_FDCWD, ".",
|
||||
(flags & GLNX_CHASE_ALL_DEBUG_FLAGS) |
|
||||
GLNX_CHASE_NO_AUTOMOUNT |
|
||||
GLNX_CHASE_NOFOLLOW,
|
||||
error);
|
||||
}
|
||||
|
||||
static int
|
||||
open_root (GlnxChaseFlags flags,
|
||||
GError **error)
|
||||
{
|
||||
GLNX_AUTO_PREFIX_ERROR ("cannot open root directory", error);
|
||||
|
||||
/* NO_AUTOMOUNT should be fine here because automount must have been
|
||||
* triggered already for the root */
|
||||
return chase_open_tree (AT_FDCWD, "/",
|
||||
(flags & GLNX_CHASE_ALL_DEBUG_FLAGS) |
|
||||
GLNX_CHASE_NO_AUTOMOUNT |
|
||||
GLNX_CHASE_NOFOLLOW,
|
||||
error);
|
||||
}
|
||||
|
||||
/* This returns the next segment of a path and tells us if it is the last
|
||||
* segment.
|
||||
*
|
||||
* Importantly, a segment is anything after a "/", even if it is empty or ".".
|
||||
*
|
||||
* For example:
|
||||
* "" -> ""
|
||||
* "/" -> ""
|
||||
* "////" -> ""
|
||||
* "foo/bar" -> "foo", "bar"
|
||||
* "foo//bar" -> "foo", "bar"
|
||||
* "///foo//bar" -> "foo", "bar"
|
||||
* "///foo//bar/" -> "foo", "bar", ""
|
||||
* "///foo//bar/." -> "foo", "bar", "."
|
||||
*/
|
||||
static char *
|
||||
extract_next_segment (const char **remaining,
|
||||
gboolean *is_last)
|
||||
{
|
||||
const char *r = *remaining;
|
||||
const char *s;
|
||||
size_t len = 0;
|
||||
|
||||
while (r[0] != '\0' && G_IS_DIR_SEPARATOR (r[0]))
|
||||
r++;
|
||||
|
||||
s = r;
|
||||
|
||||
while (r[0] != '\0' && !G_IS_DIR_SEPARATOR (r[0]))
|
||||
{
|
||||
r++;
|
||||
len++;
|
||||
}
|
||||
|
||||
*is_last = (r[0] == '\0');
|
||||
*remaining = r;
|
||||
return g_strndup (s, len);
|
||||
}
|
||||
|
||||
/* This iterates over the segments of path and opens the corresponding
|
||||
* directories or files. This gives us the opportunity to implement openat2
|
||||
* like RESOLVE_ semantics, without actually needing openat2.
|
||||
* It also allows us to implement features which openat2 does not have because
|
||||
* we're in full control over the resolving.
|
||||
*/
|
||||
static int
|
||||
chase_manual (int dirfd,
|
||||
const char *path,
|
||||
GlnxChaseFlags flags,
|
||||
GError **error)
|
||||
{
|
||||
gboolean is_absolute;
|
||||
g_autofree char *buffer = NULL;
|
||||
const char *remaining;
|
||||
glnx_autofd int owned_root_fd = -1;
|
||||
int root_fd;
|
||||
glnx_autofd int owned_fd = -1;
|
||||
int fd;
|
||||
int remaining_follows = GLNX_CHASE_MAX;
|
||||
struct glnx_statx st;
|
||||
g_auto(GlnxStatxQueue) path_st = G_QUEUE_INIT;
|
||||
int no_automount;
|
||||
|
||||
/* Take a shortcut if
|
||||
* - 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)
|
||||
*
|
||||
* TODO: if we have a guarantee that the open_tree syscall works, we can
|
||||
* shortcut even without GLNX_CHASE_NO_AUTOMOUNT
|
||||
*/
|
||||
if ((flags & (GLNX_CHASE_NO_AUTOMOUNT |
|
||||
GLNX_CHASE_RESOLVE_BENEATH |
|
||||
GLNX_CHASE_RESOLVE_IN_ROOT |
|
||||
GLNX_CHASE_RESOLVE_NO_SYMLINKS)) == GLNX_CHASE_NO_AUTOMOUNT)
|
||||
{
|
||||
GlnxChaseFlags open_tree_flags =
|
||||
(flags & (GLNX_CHASE_NOFOLLOW | GLNX_CHASE_ALL_DEBUG_FLAGS));
|
||||
|
||||
return chase_open_tree (dirfd, path, open_tree_flags, error);
|
||||
}
|
||||
|
||||
no_automount = (flags & GLNX_CHASE_NO_AUTOMOUNT) != 0 ? AT_NO_AUTOMOUNT : 0;
|
||||
|
||||
is_absolute = g_path_is_absolute (path);
|
||||
|
||||
if (is_absolute && (flags & GLNX_CHASE_RESOLVE_BENEATH) != 0)
|
||||
{
|
||||
/* Absolute paths always get rejected with RESOLVE_BENEATH with errno
|
||||
* EXDEV */
|
||||
|
||||
errno = EXDEV;
|
||||
return glnx_fd_throw_errno_prefix (error, "absolute path not allowed for RESOLVE_BENEATH");
|
||||
}
|
||||
else if (!is_absolute ||
|
||||
(is_absolute && (flags & GLNX_CHASE_RESOLVE_IN_ROOT) != 0))
|
||||
{
|
||||
/* The absolute path is relative to dirfd with GLNX_CHASE_RESOLVE_IN_ROOT,
|
||||
* and a relative path is always relative. */
|
||||
|
||||
/* In both cases we use dirfd as our chase root */
|
||||
if (dirfd == AT_FDCWD)
|
||||
{
|
||||
owned_root_fd = root_fd = open_cwd (flags, error);
|
||||
if (root_fd < 0)
|
||||
return -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
root_fd = dirfd;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* For absolute paths, we ignore dirfd, we use the actual root / for our
|
||||
* chase root */
|
||||
g_assert (is_absolute);
|
||||
|
||||
owned_root_fd = root_fd = open_root (flags, error);
|
||||
if (root_fd < 0)
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* At this point, we always have (a relative) path, relative to root_fd */
|
||||
is_absolute = FALSE;
|
||||
g_assert (root_fd >= 0);
|
||||
|
||||
/* Add root to path_st, so we can verify if we get back to it */
|
||||
if (!glnx_chase_statx (root_fd, no_automount, &st, error))
|
||||
return -1;
|
||||
|
||||
glnx_statx_queue_push (&path_st, &st);
|
||||
|
||||
/* Let's start walking the path! */
|
||||
buffer = g_strdup (path);
|
||||
remaining = buffer;
|
||||
fd = root_fd;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
g_autofree char *segment = NULL;
|
||||
gboolean is_last;
|
||||
glnx_autofd int next_fd = -1;
|
||||
|
||||
segment = extract_next_segment (&remaining, &is_last);
|
||||
|
||||
/* If we encounter an empty segment ("", "."), we stay where we are and
|
||||
* ignore the segment, or just exit if it is the last segment. */
|
||||
if (g_strcmp0 (segment, "") == 0 || g_strcmp0 (segment, ".") == 0)
|
||||
{
|
||||
if (is_last)
|
||||
break;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Special handling for going down the tree with RESOLVE_ flags */
|
||||
if (g_strcmp0 (segment, "..") == 0)
|
||||
{
|
||||
/* path_st contains the stat of the root if we're at root, so the
|
||||
* length is 1 in that case, and going lower than the root is not
|
||||
* allowed here! */
|
||||
|
||||
if (path_st.length <= 1 && (flags & GLNX_CHASE_RESOLVE_BENEATH) != 0)
|
||||
{
|
||||
/* With RESOLVE_BENEATH, error out if we would end up above the
|
||||
* root fd */
|
||||
errno = EXDEV;
|
||||
return glnx_fd_throw_errno_prefix (error, "attempted to traverse above root path via \"..\"");
|
||||
}
|
||||
else if (path_st.length <= 1 && (flags & GLNX_CHASE_RESOLVE_IN_ROOT) != 0)
|
||||
{
|
||||
/* With RESOLVE_IN_ROOT, we pretend that we hit the real root,
|
||||
* and stay there, just like the kernel does. */
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
/* Open the next segment. We always use GLNX_CHASE_NOFOLLOW here to be
|
||||
* able to ensure the RESOLVE flags, and automount behavior. */
|
||||
|
||||
GlnxChaseFlags open_tree_flags =
|
||||
GLNX_CHASE_NOFOLLOW |
|
||||
(flags & (GLNX_CHASE_NO_AUTOMOUNT | GLNX_CHASE_ALL_DEBUG_FLAGS));
|
||||
|
||||
next_fd = chase_open_tree (fd, segment, open_tree_flags, error);
|
||||
if (next_fd < 0)
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!glnx_chase_statx (next_fd, no_automount, &st, error))
|
||||
return -1;
|
||||
|
||||
/* We resolve links if: they are not in the last component, or if they
|
||||
* are the last component and NOFOLLOW is not set. */
|
||||
if (S_ISLNK (st.stx_mode) &&
|
||||
(!is_last || (flags & GLNX_CHASE_NOFOLLOW) == 0))
|
||||
{
|
||||
g_autofree char *link = NULL;
|
||||
g_autofree char *new_buffer = NULL;
|
||||
|
||||
/* ...however, we do not resolve symlinks with NO_SYMLINKS, and use
|
||||
* remaining_follows to ensure we don't loop forever. */
|
||||
if ((flags & GLNX_CHASE_RESOLVE_NO_SYMLINKS) != 0 ||
|
||||
--remaining_follows <= 0)
|
||||
{
|
||||
errno = ELOOP;
|
||||
return glnx_fd_throw_errno_prefix (error, "followed too many symlinks");
|
||||
}
|
||||
|
||||
/* AT_EMPTY_PATH is implied for readlinkat */
|
||||
link = glnx_readlinkat_malloc (next_fd, "", NULL, error);
|
||||
if (!link)
|
||||
return -1;
|
||||
|
||||
if (g_path_is_absolute (link) &&
|
||||
(flags & GLNX_CHASE_RESOLVE_BENEATH) != 0)
|
||||
{
|
||||
errno = EXDEV;
|
||||
return glnx_fd_throw_errno_prefix (error, "absolute symlink not allowed for RESOLVE_BENEATH");
|
||||
}
|
||||
|
||||
/* The link can be absolute, and we handle that below, by changing the
|
||||
* dirfd. The path *remains* and absolute path internally, but that is
|
||||
* okay because we always interpret any path (even absolute ones) as
|
||||
* being relative to the dirfd */
|
||||
new_buffer = g_strdup_printf ("%s/%s", link, remaining);
|
||||
g_clear_pointer (&buffer, g_free);
|
||||
buffer = g_steal_pointer (&new_buffer);
|
||||
remaining = buffer;
|
||||
|
||||
if (g_path_is_absolute (link))
|
||||
{
|
||||
if ((flags & GLNX_CHASE_RESOLVE_IN_ROOT) != 0)
|
||||
{
|
||||
/* If the path was absolute, and RESOLVE_IN_ROOT is set, we
|
||||
* will resolve the remaining path relative to root_fd */
|
||||
|
||||
g_clear_fd (&owned_fd, NULL);
|
||||
fd = root_fd;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* If the path was absolute, we will resolve the remaining
|
||||
* path relative to the real root */
|
||||
|
||||
g_clear_fd (&owned_fd, NULL);
|
||||
fd = owned_fd = open_root (flags, error);
|
||||
if (fd < 0)
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* path_st must only contain the new root at this point */
|
||||
if (!glnx_chase_statx (fd, no_automount, &st, error))
|
||||
return -1;
|
||||
|
||||
glnx_statx_queue_free (&path_st);
|
||||
g_queue_init (&path_st);
|
||||
glnx_statx_queue_push (&path_st, &st);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Either adds an element to path_st or removes one if we got down the
|
||||
* tree. This also checks that going down the tree ends up at the inode
|
||||
* we saw before (if we saw it before). */
|
||||
if (g_strcmp0 (segment, "..") == 0)
|
||||
{
|
||||
g_autofree struct glnx_statx *old_tail = NULL;
|
||||
struct glnx_statx *lower_st;
|
||||
|
||||
old_tail = g_queue_pop_tail (&path_st);
|
||||
|
||||
lower_st = g_queue_peek_tail (&path_st);
|
||||
if (lower_st &&
|
||||
(!glnx_statx_mount_same (&st, lower_st) ||
|
||||
!glnx_statx_inode_same (&st, lower_st)))
|
||||
{
|
||||
errno = EXDEV;
|
||||
return glnx_fd_throw_errno_prefix (error, "a parent directory changed while traversing");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
glnx_statx_queue_push (&path_st, &st);
|
||||
}
|
||||
|
||||
/* There is still another path component, but the next fd is not a
|
||||
* a directory. We need the fd to be a directory though, to open the next
|
||||
* segment from. So bail with the appropriate error. */
|
||||
if (!is_last && !S_ISDIR (st.stx_mode))
|
||||
{
|
||||
errno = ENOTDIR;
|
||||
return glnx_fd_throw_errno_prefix (error, "a non-final path segment is not a directory");
|
||||
}
|
||||
|
||||
g_clear_fd (&owned_fd, NULL);
|
||||
fd = owned_fd = g_steal_fd (&next_fd);
|
||||
|
||||
if (is_last)
|
||||
break;
|
||||
}
|
||||
|
||||
/* We need an owned fd to return. Only having fd and not owned_fd can happen
|
||||
* if we never finished a single iteration, or if an absolute path with
|
||||
* RESOLVE_IN_ROOT makes us point at root_fd.
|
||||
* We just re-open fd to always get an owned fd.
|
||||
* Note that this only works because in all cases where owned_fd does not
|
||||
* exists, fd is a directory. */
|
||||
if (owned_fd < 0)
|
||||
{
|
||||
owned_fd = openat (fd, ".", O_PATH | O_CLOEXEC | O_NOFOLLOW);
|
||||
if (owned_fd < 0)
|
||||
return glnx_fd_throw_errno_prefix (error, "reopening failed");
|
||||
}
|
||||
|
||||
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 gboolean can_openat2 = TRUE;
|
||||
glnx_autofd int fd = -1;
|
||||
|
||||
g_return_val_if_fail (dirfd >= 0 || dirfd == AT_FDCWD, -1);
|
||||
g_return_val_if_fail (path != NULL, -1);
|
||||
g_return_val_if_fail ((flags & ~(GLNX_CHASE_ALL_FLAGS)) == 0, -1);
|
||||
g_return_val_if_fail (error == NULL || *error == NULL, -1);
|
||||
|
||||
{
|
||||
int must_flags = flags & (GLNX_CHASE_MUST_BE_REGULAR |
|
||||
GLNX_CHASE_MUST_BE_DIRECTORY |
|
||||
GLNX_CHASE_MUST_BE_SOCKET);
|
||||
/* check that no more than one bit is set (= power of two) */
|
||||
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)
|
||||
{
|
||||
uint64_t openat2_flags = 0;
|
||||
uint64_t openat2_resolve = 0;
|
||||
struct open_how how;
|
||||
|
||||
openat2_flags = O_PATH | O_CLOEXEC;
|
||||
if ((flags & GLNX_CHASE_NOFOLLOW) != 0)
|
||||
openat2_flags |= O_NOFOLLOW;
|
||||
|
||||
openat2_resolve |= RESOLVE_NO_MAGICLINKS;
|
||||
if ((flags & GLNX_CHASE_RESOLVE_BENEATH) != 0)
|
||||
openat2_resolve |= RESOLVE_BENEATH;
|
||||
if ((flags & GLNX_CHASE_RESOLVE_IN_ROOT) != 0)
|
||||
openat2_resolve |= RESOLVE_IN_ROOT;
|
||||
if ((flags & GLNX_CHASE_RESOLVE_NO_SYMLINKS) != 0)
|
||||
openat2_resolve |= RESOLVE_NO_SYMLINKS;
|
||||
|
||||
how = (struct open_how) {
|
||||
.flags = openat2_flags,
|
||||
.mode = 0,
|
||||
.resolve = openat2_resolve,
|
||||
};
|
||||
|
||||
fd = openat2 (dirfd, path, &how, sizeof (how));
|
||||
if (fd < 0)
|
||||
{
|
||||
/* If the syscall is not implemented (ENOSYS) or blocked by
|
||||
* seccomp (EPERM), we need to fall back to the manual path chasing
|
||||
* via open_tree. */
|
||||
if (G_IN_SET (errno, ENOSYS, EPERM))
|
||||
can_openat2 = FALSE;
|
||||
else
|
||||
return glnx_fd_throw_errno (error);
|
||||
}
|
||||
}
|
||||
|
||||
if (fd < 0)
|
||||
{
|
||||
fd = chase_manual (dirfd, path, flags, error);
|
||||
if (fd < 0)
|
||||
return -1;
|
||||
}
|
||||
|
||||
if ((flags & (GLNX_CHASE_MUST_BE_REGULAR |
|
||||
GLNX_CHASE_MUST_BE_DIRECTORY |
|
||||
GLNX_CHASE_MUST_BE_SOCKET)) != 0)
|
||||
{
|
||||
struct glnx_statx st;
|
||||
|
||||
if (!glnx_statx (fd, "",
|
||||
AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW |
|
||||
((flags & GLNX_CHASE_NO_AUTOMOUNT) ? AT_NO_AUTOMOUNT : 0),
|
||||
GLNX_STATX_TYPE,
|
||||
&st,
|
||||
error))
|
||||
return -1;
|
||||
|
||||
if ((st.stx_mask & GLNX_STATX_TYPE) == 0)
|
||||
{
|
||||
errno = ENODATA;
|
||||
return glnx_fd_throw_errno_prefix (error, "unable to get file type");
|
||||
}
|
||||
|
||||
if ((flags & GLNX_CHASE_MUST_BE_REGULAR) != 0 &&
|
||||
!S_ISREG (st.stx_mode))
|
||||
{
|
||||
if (S_ISDIR (st.stx_mode))
|
||||
errno = EISDIR;
|
||||
else
|
||||
errno = EBADFD;
|
||||
|
||||
return glnx_fd_throw_errno_prefix (error, "not a regular file");
|
||||
}
|
||||
|
||||
if ((flags & GLNX_CHASE_MUST_BE_DIRECTORY) != 0 &&
|
||||
!S_ISDIR (st.stx_mode))
|
||||
{
|
||||
errno = ENOTDIR;
|
||||
return glnx_fd_throw_errno_prefix (error, "not a directory");
|
||||
}
|
||||
|
||||
if ((flags & GLNX_CHASE_MUST_BE_SOCKET) != 0 &&
|
||||
!S_ISSOCK (st.stx_mode))
|
||||
{
|
||||
errno = ENOTSOCK;
|
||||
return glnx_fd_throw_errno_prefix (error, "not a socket");
|
||||
}
|
||||
}
|
||||
|
||||
return g_steal_fd (&fd);
|
||||
}
|
||||
|
||||
/**
|
||||
* glnx_chase_and_statxat:
|
||||
* @dirfd: a directory file descriptor
|
||||
* @path: a path
|
||||
* @flags: combination of GlnxChaseFlags flags
|
||||
* @mask: combination of GLNX_STATX_ flags
|
||||
* @statbuf: a pointer to a struct glnx_statx which will be filled out
|
||||
* @error: a #GError
|
||||
*
|
||||
* Stats the file at @path relative to @dirfd and fills out @statbuf with the
|
||||
* result according to the interest mask @mask.
|
||||
*
|
||||
* See glnx_chaseat for the meaning of @dirfd, @path, and @flags.
|
||||
*
|
||||
* Returns: the chased file, or -1 with @error set on error
|
||||
*/
|
||||
int
|
||||
glnx_chase_and_statxat (int dirfd,
|
||||
const char *path,
|
||||
GlnxChaseFlags flags,
|
||||
unsigned int mask,
|
||||
struct glnx_statx *statbuf,
|
||||
GError **error)
|
||||
{
|
||||
glnx_autofd int fd = -1;
|
||||
|
||||
/* other args are checked by glnx_chaseat */
|
||||
g_return_val_if_fail (statbuf != NULL, FALSE);
|
||||
|
||||
fd = glnx_chaseat (dirfd, path, flags, error);
|
||||
if (fd < 0)
|
||||
return -1;
|
||||
|
||||
if (!glnx_statx (fd, "",
|
||||
AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW |
|
||||
((flags & GLNX_CHASE_NO_AUTOMOUNT) ? AT_NO_AUTOMOUNT : 0),
|
||||
mask,
|
||||
statbuf,
|
||||
error))
|
||||
return -1;
|
||||
|
||||
return g_steal_fd (&fd);
|
||||
}
|
||||
51
subprojects/libglnx/glnx-chase.h
Normal file
51
subprojects/libglnx/glnx-chase.h
Normal file
@@ -0,0 +1,51 @@
|
||||
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
|
||||
*
|
||||
* Copyright (C) 2026 Red Hat, Inc.
|
||||
* SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
typedef enum _GlnxChaseFlags {
|
||||
/* Default */
|
||||
GLNX_CHASE_DEFAULT = 0,
|
||||
/* Disable triggering of automounts */
|
||||
GLNX_CHASE_NO_AUTOMOUNT = 1 << 1,
|
||||
/* Do not follow the path's right-most component. When the path's right-most
|
||||
* component refers to symlink, return O_PATH fd of the symlink. */
|
||||
GLNX_CHASE_NOFOLLOW = 1 << 2,
|
||||
/* Do not permit the path resolution to succeed if any component of the
|
||||
* resolution is not a descendant of the directory indicated by dirfd. */
|
||||
GLNX_CHASE_RESOLVE_BENEATH = 1 << 3,
|
||||
/* Symlinks are resolved relative to the given dirfd instead of root. */
|
||||
GLNX_CHASE_RESOLVE_IN_ROOT = 1 << 4,
|
||||
/* Fail if any symlink is encountered. */
|
||||
GLNX_CHASE_RESOLVE_NO_SYMLINKS = 1 << 5,
|
||||
/* Fail if the path's right-most component is not a regular file */
|
||||
GLNX_CHASE_MUST_BE_REGULAR = 1 << 6,
|
||||
/* Fail if the path's right-most component is not a directory */
|
||||
GLNX_CHASE_MUST_BE_DIRECTORY = 1 << 7,
|
||||
/* Fail if the path's right-most component is not a socket */
|
||||
GLNX_CHASE_MUST_BE_SOCKET = 1 << 8,
|
||||
} GlnxChaseFlags;
|
||||
|
||||
/* How many iterations to execute before returning ELOOP */
|
||||
#define GLNX_CHASE_MAX 32
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
int glnx_chaseat (int dirfd,
|
||||
const char *path,
|
||||
GlnxChaseFlags flags,
|
||||
GError **error);
|
||||
|
||||
int glnx_chase_and_statxat (int dirfd,
|
||||
const char *path,
|
||||
GlnxChaseFlags flags,
|
||||
unsigned int mask,
|
||||
struct glnx_statx *statbuf,
|
||||
GError **error);
|
||||
|
||||
G_END_DECLS
|
||||
@@ -32,6 +32,10 @@ gboolean glnx_throw (GError **error, const char *fmt, ...) G_GNUC_PRINTF (2,3);
|
||||
#define glnx_null_throw(error, args...) \
|
||||
({glnx_throw (error, args); NULL;})
|
||||
|
||||
/* Like glnx_throw(), but yields -1 (invalid fd). */
|
||||
#define glnx_fd_throw(error, args...) \
|
||||
({glnx_throw (error, args); -1;})
|
||||
|
||||
/* Implementation detail of glnx_throw_prefix() */
|
||||
void glnx_real_set_prefix_error_va (GError *error,
|
||||
const char *format,
|
||||
@@ -97,7 +101,7 @@ glnx_throw_errno (GError **error)
|
||||
g_strerror (errsv));
|
||||
/* We also restore the value of errno, since that's
|
||||
* what was done in a long-ago libgsystem commit
|
||||
* https://git.gnome.org/browse/libgsystem/commit/?id=ed106741f7a0596dc8b960b31fdae671d31d666d
|
||||
* https://gitlab.gnome.org/Archive/libgsystem/-/commit/ed106741f7a0596dc8b960b31fdae671d31d666d
|
||||
* but I certainly can't remember now why I did that.
|
||||
*/
|
||||
errno = errsv;
|
||||
@@ -108,6 +112,10 @@ glnx_throw_errno (GError **error)
|
||||
#define glnx_null_throw_errno(error) \
|
||||
({glnx_throw_errno (error); NULL;})
|
||||
|
||||
/* Like glnx_throw_errno(), but yields -1 (invalid fd). */
|
||||
#define glnx_fd_throw_errno(error) \
|
||||
({glnx_throw_errno (error); -1;})
|
||||
|
||||
/* Implementation detail of glnx_throw_errno_prefix() */
|
||||
void glnx_real_set_prefix_error_from_errno_va (GError **error,
|
||||
gint errsv,
|
||||
@@ -120,6 +128,10 @@ gboolean glnx_throw_errno_prefix (GError **error, const char *fmt, ...) G_GNUC_P
|
||||
#define glnx_null_throw_errno_prefix(error, args...) \
|
||||
({glnx_throw_errno_prefix (error, args); NULL;})
|
||||
|
||||
/* Like glnx_throw_errno_prefix(), but yields -1 (invalid fd). */
|
||||
#define glnx_fd_throw_errno_prefix(error, args...) \
|
||||
({glnx_throw_errno_prefix (error, args); -1;})
|
||||
|
||||
/* BEGIN LEGACY APIS */
|
||||
|
||||
#define glnx_set_error_from_errno(error) \
|
||||
|
||||
@@ -321,6 +321,8 @@ glnx_open_anonymous_tmpfile (int flags,
|
||||
error);
|
||||
}
|
||||
|
||||
static const char proc_self_fd_slash[] = "/proc/self/fd/";
|
||||
|
||||
/* Use this after calling glnx_open_tmpfile_linkable_at() to give
|
||||
* the file its final name (link into place).
|
||||
*/
|
||||
@@ -367,8 +369,8 @@ glnx_link_tmpfile_at (GLnxTmpfile *tmpf,
|
||||
else
|
||||
{
|
||||
/* This case we have O_TMPFILE, so our reference to it is via /proc/self/fd */
|
||||
char proc_fd_path[strlen("/proc/self/fd/") + DECIMAL_STR_MAX(tmpf->fd) + 1];
|
||||
snprintf (proc_fd_path, sizeof (proc_fd_path), "/proc/self/fd/%i", tmpf->fd);
|
||||
char proc_fd_path[sizeof (proc_self_fd_slash) + DECIMAL_STR_MAX(tmpf->fd)];
|
||||
snprintf (proc_fd_path, sizeof (proc_fd_path), "%s%i", proc_self_fd_slash, tmpf->fd);
|
||||
|
||||
if (replace)
|
||||
{
|
||||
@@ -455,8 +457,8 @@ glnx_tmpfile_reopen_rdonly (GLnxTmpfile *tmpf,
|
||||
else
|
||||
{
|
||||
/* This case we have O_TMPFILE, so our reference to it is via /proc/self/fd */
|
||||
char proc_fd_path[strlen("/proc/self/fd/") + DECIMAL_STR_MAX(tmpf->fd) + 1];
|
||||
snprintf (proc_fd_path, sizeof (proc_fd_path), "/proc/self/fd/%i", tmpf->fd);
|
||||
char proc_fd_path[sizeof (proc_self_fd_slash) + DECIMAL_STR_MAX(tmpf->fd)];
|
||||
snprintf (proc_fd_path, sizeof (proc_fd_path), "%s%i", proc_self_fd_slash, tmpf->fd);
|
||||
|
||||
if (!glnx_openat_rdonly (AT_FDCWD, proc_fd_path, TRUE, &rdonly_fd, error))
|
||||
return FALSE;
|
||||
@@ -1205,3 +1207,75 @@ glnx_file_replace_contents_with_perms_at (int dfd,
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* glnx_fd_reopen:
|
||||
* @fd: a file descriptor
|
||||
* @flags: combination of openat flags
|
||||
* @error: a #GError
|
||||
*
|
||||
* Reopens the specified fd with new flags. This is useful for converting an
|
||||
* O_PATH fd into a regular one, or to turn O_RDWR fds into O_RDONLY fds.
|
||||
*
|
||||
* This implicitly sets `O_CLOEXEC | O_NOCTTY` in @flags.
|
||||
*
|
||||
* `O_CREAT` isn't allowed in @flags.
|
||||
*
|
||||
* This doesn't work on sockets (since they cannot be open()ed, ever).
|
||||
*
|
||||
* This implicitly resets the file read index to 0.
|
||||
*
|
||||
* If AT_FDCWD is specified as file descriptor, the function returns an fd to
|
||||
* the current working directory.
|
||||
*
|
||||
* If the specified file descriptor refers to a symlink via O_PATH, then this
|
||||
* function cannot be used to follow that symlink. Because we cannot have
|
||||
* non-O_PATH fds to symlinks reopening it without O_PATH will always result in
|
||||
* ELOOP. Or in other words: if you have an O_PATH fd to a symlink you can
|
||||
* reopen it only if you pass O_PATH again.
|
||||
*/
|
||||
int
|
||||
glnx_fd_reopen (int fd,
|
||||
int flags,
|
||||
GError **error)
|
||||
{
|
||||
glnx_autofd int new_fd = -1;
|
||||
|
||||
g_return_val_if_fail (fd >= 0 || fd == AT_FDCWD, -1);
|
||||
g_return_val_if_fail ((flags & O_CREAT) == 0, -1);
|
||||
|
||||
/* */
|
||||
flags |= O_CLOEXEC | O_NOCTTY;
|
||||
|
||||
/* O_NOFOLLOW is not allowed in fd_reopen(), because after all this is
|
||||
* primarily implemented via a symlink-based interface in /proc/self/fd. Let's
|
||||
* refuse this here early. Note that the kernel would generate ELOOP here too,
|
||||
* hence this manual check is mostly redundant – the only reason we add it
|
||||
* here is so that the O_DIRECTORY special case (see below) behaves the same
|
||||
* way as the non-O_DIRECTORY case. */
|
||||
if ((flags & O_NOFOLLOW) != 0)
|
||||
{
|
||||
errno = ELOOP;
|
||||
return glnx_fd_throw_errno (error);
|
||||
}
|
||||
|
||||
if ((flags & O_DIRECTORY) != 0 || fd == AT_FDCWD)
|
||||
{
|
||||
/* If we shall reopen the fd as directory we can just go via "." and thus
|
||||
* bypass the whole magic /proc/ directory, and make ourselves independent
|
||||
* of that being mounted. */
|
||||
new_fd = openat (fd, ".", flags | O_DIRECTORY);
|
||||
}
|
||||
else
|
||||
{
|
||||
g_autofree char *proc_fd_path = NULL;
|
||||
|
||||
proc_fd_path = g_strdup_printf ("/proc/self/fd/%d", fd);
|
||||
new_fd = open (proc_fd_path, flags);
|
||||
}
|
||||
|
||||
if (new_fd < 0)
|
||||
return glnx_fd_throw_errno (error);
|
||||
|
||||
return g_steal_fd (&new_fd);
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <glnx-backport-autocleanups.h>
|
||||
#include <glnx-missing.h>
|
||||
#include <gio/gfiledescriptorbased.h>
|
||||
#include <limits.h>
|
||||
#include <dirent.h>
|
||||
@@ -313,6 +314,37 @@ glnx_fstatat (int dfd,
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* glnx_statx:
|
||||
* @dfd: Directory FD to stat beneath
|
||||
* @path: Path to stat beneath @dfd
|
||||
* @flags: Flags to pass to statx()
|
||||
* @mask: Mask to pass to statx()
|
||||
* @buf: (out caller-allocates): Return location for statx details
|
||||
* @error: Return location for a #GError, or %NULL
|
||||
*
|
||||
* Wrapper around statx() which adds #GError support and ensures that it
|
||||
* retries on %EINTR.
|
||||
*
|
||||
* The mask to pass must be a combination of GLNX_STATX_* flags which are
|
||||
* defined by glnx, which map up with the struct glnx_statx.
|
||||
*
|
||||
* Returns: %TRUE on success, or %FALSE setting both @error and `errno`
|
||||
* Since: UNRELEASED
|
||||
*/
|
||||
static inline gboolean
|
||||
glnx_statx (int dfd,
|
||||
const char *path,
|
||||
unsigned flags,
|
||||
unsigned int mask,
|
||||
struct glnx_statx *buf,
|
||||
GError **error)
|
||||
{
|
||||
if (TEMP_FAILURE_RETRY (glnx_statx_syscall (dfd, path, flags, mask, buf)) != 0)
|
||||
return glnx_throw_errno_prefix (error, "statx(%s)", path);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* glnx_fstatat_allow_noent:
|
||||
* @dfd: Directory FD to stat beneath
|
||||
@@ -383,4 +415,8 @@ glnx_unlinkat (int dfd,
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
int glnx_fd_reopen (int fd,
|
||||
int flags,
|
||||
GError **error);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
@@ -43,7 +43,6 @@ glnx_local_obj_unref (void *v)
|
||||
if (o)
|
||||
g_object_unref (o);
|
||||
}
|
||||
#define glnx_unref_object __attribute__ ((cleanup(glnx_local_obj_unref)))
|
||||
|
||||
/* Backwards-compat with older libglnx */
|
||||
#define glnx_steal_fd g_steal_fd
|
||||
|
||||
@@ -66,6 +66,8 @@ glnx_make_lock_file(int dfd, const char *p, int operation, GLnxLockFile *out_loc
|
||||
g_autofree char *t = NULL;
|
||||
int r;
|
||||
|
||||
g_return_val_if_fail (p != NULL, FALSE);
|
||||
|
||||
/*
|
||||
* We use UNPOSIX locks if they are available. They have nice
|
||||
* semantics, and are mostly compatible with NFS. However,
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
|
||||
#include "libglnx-config.h"
|
||||
#include <glib.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#if !HAVE_DECL_RENAMEAT2
|
||||
# ifndef __NR_renameat2
|
||||
@@ -236,3 +237,355 @@ inline_close_range (unsigned int low,
|
||||
#define close_range(low, high, flags) inline_close_range(low, high, flags)
|
||||
#define HAVE_CLOSE_RANGE
|
||||
#endif
|
||||
|
||||
#ifndef __IGNORE_statx
|
||||
# if defined(__aarch64__)
|
||||
# define systemd_NR_statx 291
|
||||
# elif defined(__alpha__)
|
||||
# define systemd_NR_statx 522
|
||||
# elif defined(__arc__) || defined(__tilegx__)
|
||||
# define systemd_NR_statx 291
|
||||
# elif defined(__arm__)
|
||||
# define systemd_NR_statx 397
|
||||
# elif defined(__i386__)
|
||||
# define systemd_NR_statx 383
|
||||
# elif defined(__ia64__)
|
||||
# define systemd_NR_statx 1350
|
||||
# elif defined(__loongarch_lp64)
|
||||
# define systemd_NR_statx 291
|
||||
# elif defined(__m68k__)
|
||||
# define systemd_NR_statx 379
|
||||
# elif defined(_MIPS_SIM)
|
||||
# if _MIPS_SIM == _MIPS_SIM_ABI32
|
||||
# define systemd_NR_statx 4366
|
||||
# elif _MIPS_SIM == _MIPS_SIM_NABI32
|
||||
# define systemd_NR_statx 6330
|
||||
# elif _MIPS_SIM == _MIPS_SIM_ABI64
|
||||
# define systemd_NR_statx 5326
|
||||
# else
|
||||
# error "Unknown MIPS ABI"
|
||||
# endif
|
||||
# elif defined(__hppa__)
|
||||
# define systemd_NR_statx 349
|
||||
# elif defined(__powerpc__)
|
||||
# define systemd_NR_statx 383
|
||||
# elif defined(__riscv)
|
||||
# if __riscv_xlen == 32
|
||||
# define systemd_NR_statx 291
|
||||
# elif __riscv_xlen == 64
|
||||
# define systemd_NR_statx 291
|
||||
# else
|
||||
# error "Unknown RISC-V ABI"
|
||||
# endif
|
||||
# elif defined(__s390__)
|
||||
# define systemd_NR_statx 379
|
||||
# elif defined(__sparc__)
|
||||
# define systemd_NR_statx 360
|
||||
# elif defined(__x86_64__)
|
||||
# if defined(__ILP32__)
|
||||
# define systemd_NR_statx (332 | /* __X32_SYSCALL_BIT */ 0x40000000)
|
||||
# else
|
||||
# define systemd_NR_statx 332
|
||||
# endif
|
||||
# elif !defined(missing_arch_template)
|
||||
# warning "statx() syscall number is unknown for your architecture"
|
||||
# endif
|
||||
|
||||
/* may be an (invalid) negative number due to libseccomp, see PR 13319 */
|
||||
# if defined __NR_statx && __NR_statx >= 0
|
||||
# if defined systemd_NR_statx
|
||||
G_STATIC_ASSERT (__NR_statx == systemd_NR_statx);
|
||||
# endif
|
||||
# else
|
||||
# if defined __NR_statx
|
||||
# undef __NR_statx
|
||||
# endif
|
||||
# if defined systemd_NR_statx && systemd_NR_statx >= 0
|
||||
# define __NR_statx systemd_NR_statx
|
||||
# endif
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#if !defined(HAVE_GLNX_STATX) && defined(__NR_statx)
|
||||
#define GLNX_STATX_TYPE 0x00000001U /* Want/got stx_mode & S_IFMT */
|
||||
#define GLNX_STATX_MODE 0x00000002U /* Want/got stx_mode & ~S_IFMT */
|
||||
#define GLNX_STATX_NLINK 0x00000004U /* Want/got stx_nlink */
|
||||
#define GLNX_STATX_UID 0x00000008U /* Want/got stx_uid */
|
||||
#define GLNX_STATX_GID 0x00000010U /* Want/got stx_gid */
|
||||
#define GLNX_STATX_ATIME 0x00000020U /* Want/got stx_atime */
|
||||
#define GLNX_STATX_MTIME 0x00000040U /* Want/got stx_mtime */
|
||||
#define GLNX_STATX_CTIME 0x00000080U /* Want/got stx_ctime */
|
||||
#define GLNX_STATX_INO 0x00000100U /* Want/got stx_ino */
|
||||
#define GLNX_STATX_SIZE 0x00000200U /* Want/got stx_size */
|
||||
#define GLNX_STATX_BLOCKS 0x00000400U /* Want/got stx_blocks */
|
||||
#define GLNX_STATX_BASIC_STATS 0x000007ffU /* The stuff in the normal stat struct */
|
||||
#define GLNX_STATX_BTIME 0x00000800U /* Want/got stx_btime */
|
||||
#define GLNX_STATX_MNT_ID 0x00001000U /* Got stx_mnt_id */
|
||||
#define GLNX_STATX_DIOALIGN 0x00002000U /* Want/got direct I/O alignment info */
|
||||
#define GLNX_STATX_MNT_ID_UNIQUE 0x00004000U /* Want/got extended stx_mount_id */
|
||||
#define GLNX_STATX_SUBVOL 0x00008000U /* Want/got stx_subvol */
|
||||
#define GLNX_STATX_WRITE_ATOMIC 0x00010000U /* Want/got atomic_write_* fields */
|
||||
#define GLNX_STATX_DIO_READ_ALIGN 0x00020000U /* Want/got dio read alignment info */
|
||||
#define GLNX_STATX__RESERVED 0x80000000U /* Reserved for future struct statx expansion */
|
||||
|
||||
struct glnx_statx_timestamp
|
||||
{
|
||||
int64_t tv_sec;
|
||||
uint32_t tv_nsec;
|
||||
int32_t __reserved;
|
||||
};
|
||||
|
||||
struct glnx_statx
|
||||
{
|
||||
uint32_t stx_mask;
|
||||
uint32_t stx_blksize;
|
||||
uint64_t stx_attributes;
|
||||
uint32_t stx_nlink;
|
||||
uint32_t stx_uid;
|
||||
uint32_t stx_gid;
|
||||
uint16_t stx_mode;
|
||||
uint16_t __spare0[1];
|
||||
uint64_t stx_ino;
|
||||
uint64_t stx_size;
|
||||
uint64_t stx_blocks;
|
||||
uint64_t stx_attributes_mask;
|
||||
struct glnx_statx_timestamp stx_atime;
|
||||
struct glnx_statx_timestamp stx_btime;
|
||||
struct glnx_statx_timestamp stx_ctime;
|
||||
struct glnx_statx_timestamp stx_mtime;
|
||||
uint32_t stx_rdev_major;
|
||||
uint32_t stx_rdev_minor;
|
||||
uint32_t stx_dev_major;
|
||||
uint32_t stx_dev_minor;
|
||||
uint64_t stx_mnt_id;
|
||||
uint32_t stx_dio_mem_align;
|
||||
uint32_t stx_dio_offset_align;
|
||||
uint64_t stx_subvol;
|
||||
uint32_t stx_atomic_write_unit_min;
|
||||
uint32_t stx_atomic_write_unit_max;
|
||||
uint32_t stx_atomic_write_segments_max;
|
||||
uint32_t stx_dio_read_offset_align;
|
||||
uint32_t stx_atomic_write_unit_max_opt;
|
||||
uint32_t __spare2[1];
|
||||
uint64_t __spare3[8];
|
||||
};
|
||||
|
||||
static inline int
|
||||
glnx_statx_syscall (int dfd,
|
||||
const char *filename,
|
||||
unsigned flags,
|
||||
unsigned int mask,
|
||||
struct glnx_statx *buf)
|
||||
{
|
||||
memset (buf, 0xbf, sizeof (*buf));
|
||||
return syscall (__NR_statx, dfd, filename, flags, mask, buf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define HAVE_GLNX_STATX
|
||||
#endif
|
||||
|
||||
/* Copied from systemd git: ff83795469 ("boot: Improve log message")
|
||||
* - open_tree
|
||||
* - openat2
|
||||
*/
|
||||
|
||||
#ifndef __IGNORE_open_tree
|
||||
# if defined(__aarch64__)
|
||||
# define systemd_NR_open_tree 428
|
||||
# elif defined(__alpha__)
|
||||
# define systemd_NR_open_tree 538
|
||||
# elif defined(__arc__) || defined(__tilegx__)
|
||||
# define systemd_NR_open_tree 428
|
||||
# elif defined(__arm__)
|
||||
# define systemd_NR_open_tree 428
|
||||
# elif defined(__i386__)
|
||||
# define systemd_NR_open_tree 428
|
||||
# elif defined(__ia64__)
|
||||
# define systemd_NR_open_tree 1452
|
||||
# elif defined(__loongarch_lp64)
|
||||
# define systemd_NR_open_tree 428
|
||||
# elif defined(__m68k__)
|
||||
# define systemd_NR_open_tree 428
|
||||
# elif defined(_MIPS_SIM)
|
||||
# if _MIPS_SIM == _MIPS_SIM_ABI32
|
||||
# define systemd_NR_open_tree 4428
|
||||
# elif _MIPS_SIM == _MIPS_SIM_NABI32
|
||||
# define systemd_NR_open_tree 6428
|
||||
# elif _MIPS_SIM == _MIPS_SIM_ABI64
|
||||
# define systemd_NR_open_tree 5428
|
||||
# else
|
||||
# error "Unknown MIPS ABI"
|
||||
# endif
|
||||
# elif defined(__hppa__)
|
||||
# define systemd_NR_open_tree 428
|
||||
# elif defined(__powerpc__)
|
||||
# define systemd_NR_open_tree 428
|
||||
# elif defined(__riscv)
|
||||
# if __riscv_xlen == 32
|
||||
# define systemd_NR_open_tree 428
|
||||
# elif __riscv_xlen == 64
|
||||
# define systemd_NR_open_tree 428
|
||||
# else
|
||||
# error "Unknown RISC-V ABI"
|
||||
# endif
|
||||
# elif defined(__s390__)
|
||||
# define systemd_NR_open_tree 428
|
||||
# elif defined(__sparc__)
|
||||
# define systemd_NR_open_tree 428
|
||||
# elif defined(__x86_64__)
|
||||
# if defined(__ILP32__)
|
||||
# define systemd_NR_open_tree (428 | /* __X32_SYSCALL_BIT */ 0x40000000)
|
||||
# else
|
||||
# define systemd_NR_open_tree 428
|
||||
# endif
|
||||
# elif !defined(missing_arch_template)
|
||||
# warning "open_tree() syscall number is unknown for your architecture"
|
||||
# endif
|
||||
|
||||
/* may be an (invalid) negative number due to libseccomp, see PR 13319 */
|
||||
# if defined __NR_open_tree && __NR_open_tree >= 0
|
||||
# if defined systemd_NR_open_tree
|
||||
G_STATIC_ASSERT (__NR_open_tree == systemd_NR_open_tree);
|
||||
# endif
|
||||
# else
|
||||
# if defined __NR_open_tree
|
||||
# undef __NR_open_tree
|
||||
# endif
|
||||
# if defined systemd_NR_open_tree && systemd_NR_open_tree >= 0
|
||||
# define __NR_open_tree systemd_NR_open_tree
|
||||
# endif
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#if !defined(HAVE_OPEN_TREE) && defined(__NR_open_tree)
|
||||
#ifndef OPEN_TREE_CLONE
|
||||
#define OPEN_TREE_CLONE 1
|
||||
#endif
|
||||
|
||||
#ifndef OPEN_TREE_CLOEXEC
|
||||
#define OPEN_TREE_CLOEXEC O_CLOEXEC
|
||||
#endif
|
||||
|
||||
static inline int
|
||||
inline_open_tree (int dfd,
|
||||
const char *filename,
|
||||
unsigned flags)
|
||||
{
|
||||
return syscall(__NR_open_tree, dfd, filename, flags);
|
||||
}
|
||||
#define open_tree inline_open_tree
|
||||
#define HAVE_OPEN_TREE
|
||||
#endif
|
||||
|
||||
#ifndef __IGNORE_openat2
|
||||
# if defined(__aarch64__)
|
||||
# define systemd_NR_openat2 437
|
||||
# elif defined(__alpha__)
|
||||
# define systemd_NR_openat2 547
|
||||
# elif defined(__arc__) || defined(__tilegx__)
|
||||
# define systemd_NR_openat2 437
|
||||
# elif defined(__arm__)
|
||||
# define systemd_NR_openat2 437
|
||||
# elif defined(__i386__)
|
||||
# define systemd_NR_openat2 437
|
||||
# elif defined(__ia64__)
|
||||
# define systemd_NR_openat2 1461
|
||||
# elif defined(__loongarch_lp64)
|
||||
# define systemd_NR_openat2 437
|
||||
# elif defined(__m68k__)
|
||||
# define systemd_NR_openat2 437
|
||||
# elif defined(_MIPS_SIM)
|
||||
# if _MIPS_SIM == _MIPS_SIM_ABI32
|
||||
# define systemd_NR_openat2 4437
|
||||
# elif _MIPS_SIM == _MIPS_SIM_NABI32
|
||||
# define systemd_NR_openat2 6437
|
||||
# elif _MIPS_SIM == _MIPS_SIM_ABI64
|
||||
# define systemd_NR_openat2 5437
|
||||
# else
|
||||
# error "Unknown MIPS ABI"
|
||||
# endif
|
||||
# elif defined(__hppa__)
|
||||
# define systemd_NR_openat2 437
|
||||
# elif defined(__powerpc__)
|
||||
# define systemd_NR_openat2 437
|
||||
# elif defined(__riscv)
|
||||
# if __riscv_xlen == 32
|
||||
# define systemd_NR_openat2 437
|
||||
# elif __riscv_xlen == 64
|
||||
# define systemd_NR_openat2 437
|
||||
# else
|
||||
# error "Unknown RISC-V ABI"
|
||||
# endif
|
||||
# elif defined(__s390__)
|
||||
# define systemd_NR_openat2 437
|
||||
# elif defined(__sparc__)
|
||||
# define systemd_NR_openat2 437
|
||||
# elif defined(__x86_64__)
|
||||
# if defined(__ILP32__)
|
||||
# define systemd_NR_openat2 (437 | /* __X32_SYSCALL_BIT */ 0x40000000)
|
||||
# else
|
||||
# define systemd_NR_openat2 437
|
||||
# endif
|
||||
# elif !defined(missing_arch_template)
|
||||
# warning "openat2() syscall number is unknown for your architecture"
|
||||
# endif
|
||||
|
||||
/* may be an (invalid) negative number due to libseccomp, see PR 13319 */
|
||||
# if defined __NR_openat2 && __NR_openat2 >= 0
|
||||
# if defined systemd_NR_openat2
|
||||
G_STATIC_ASSERT (__NR_openat2 == systemd_NR_openat2);
|
||||
# endif
|
||||
# else
|
||||
# if defined __NR_openat2
|
||||
# undef __NR_openat2
|
||||
# endif
|
||||
# if defined systemd_NR_openat2 && systemd_NR_openat2 >= 0
|
||||
# define __NR_openat2 systemd_NR_openat2
|
||||
# endif
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#if !defined(HAVE_OPENAT2) && defined(__NR_openat2)
|
||||
#ifndef RESOLVE_NO_XDEV
|
||||
#define RESOLVE_NO_XDEV 0x01
|
||||
#endif
|
||||
|
||||
#ifndef RESOLVE_NO_MAGICLINKS
|
||||
#define RESOLVE_NO_MAGICLINKS 0x02
|
||||
#endif
|
||||
|
||||
#ifndef RESOLVE_NO_SYMLINKS
|
||||
#define RESOLVE_NO_SYMLINKS 0x04
|
||||
#endif
|
||||
|
||||
#ifndef RESOLVE_BENEATH
|
||||
#define RESOLVE_BENEATH 0x08
|
||||
#endif
|
||||
|
||||
#ifndef RESOLVE_IN_ROOT
|
||||
#define RESOLVE_IN_ROOT 0x10
|
||||
#endif
|
||||
|
||||
#ifndef RESOLVE_CACHED
|
||||
#define RESOLVE_CACHED 0x20
|
||||
#endif
|
||||
|
||||
struct inline_open_how {
|
||||
uint64_t flags;
|
||||
uint64_t mode;
|
||||
uint64_t resolve;
|
||||
};
|
||||
#define open_how inline_open_how
|
||||
|
||||
static inline int
|
||||
inline_openat2 (int dfd,
|
||||
const char *filename,
|
||||
void *buffer,
|
||||
size_t size)
|
||||
{
|
||||
return syscall(__NR_openat2, dfd, filename, buffer, size);
|
||||
}
|
||||
#define openat2 inline_openat2
|
||||
#define HAVE_OPENAT2
|
||||
#endif
|
||||
|
||||
@@ -31,6 +31,7 @@ G_BEGIN_DECLS
|
||||
#include <glnx-backport-autocleanups.h>
|
||||
#include <glnx-backport-testutils.h>
|
||||
#include <glnx-backports.h>
|
||||
#include <glnx-chase.h>
|
||||
#include <glnx-lockfile.h>
|
||||
#include <glnx-errors.h>
|
||||
#include <glnx-dirfd.h>
|
||||
|
||||
@@ -76,6 +76,8 @@ libglnx_sources = [
|
||||
'glnx-backport-testutils.h',
|
||||
'glnx-backports.c',
|
||||
'glnx-backports.h',
|
||||
'glnx-chase.c',
|
||||
'glnx-chase.h',
|
||||
'glnx-console.c',
|
||||
'glnx-console.h',
|
||||
'glnx-dirfd.c',
|
||||
|
||||
@@ -34,6 +34,7 @@ if get_option('tests')
|
||||
|
||||
test_names = [
|
||||
'backports',
|
||||
'chase',
|
||||
'errors',
|
||||
'fdio',
|
||||
'macros',
|
||||
|
||||
609
subprojects/libglnx/tests/test-libglnx-chase.c
Normal file
609
subprojects/libglnx/tests/test-libglnx-chase.c
Normal file
@@ -0,0 +1,609 @@
|
||||
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
|
||||
*
|
||||
* Copyright (C) 2026 Red Hat, Inc.
|
||||
* SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#include "libglnx-config.h"
|
||||
#include "libglnx.h"
|
||||
#include <glib.h>
|
||||
#include <stdlib.h>
|
||||
#include <gio/gio.h>
|
||||
#include <err.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "libglnx-testlib.h"
|
||||
|
||||
#define GLNX_CHASE_DEBUG_NO_OPENAT2 (1U << 31)
|
||||
#define GLNX_CHASE_DEBUG_NO_OPEN_TREE (1U << 30)
|
||||
|
||||
const char *test_paths[] = {
|
||||
"file/baz",
|
||||
"file/baz/",
|
||||
"file/baz/.",
|
||||
"file/baz/../baz",
|
||||
"file////baz/..//baz",
|
||||
"file////baz/..//../file/baz",
|
||||
};
|
||||
|
||||
static ino_t
|
||||
get_ino (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_ino;
|
||||
}
|
||||
|
||||
static ino_t
|
||||
path_get_ino (const char *path)
|
||||
{
|
||||
int r;
|
||||
struct stat st;
|
||||
|
||||
r = fstatat (AT_FDCWD, path, &st, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
|
||||
g_assert_cmpint (r, >=, 0);
|
||||
|
||||
return st.st_ino;
|
||||
}
|
||||
|
||||
static char *
|
||||
get_abspath (int dfd,
|
||||
const char *path)
|
||||
{
|
||||
g_autofree char *proc_fd_path = NULL;
|
||||
g_autofree char *abs = NULL;
|
||||
g_autoptr(GError) error = NULL;
|
||||
|
||||
proc_fd_path = g_strdup_printf ("/proc/self/fd/%d", dfd);
|
||||
abs = glnx_readlinkat_malloc (AT_FDCWD, proc_fd_path, NULL, &error);
|
||||
g_assert_no_error (error);
|
||||
g_assert_nonnull (abs);
|
||||
|
||||
return g_strdup_printf ("%s/%s", abs, path);
|
||||
}
|
||||
|
||||
static void
|
||||
check_chase (int dfd,
|
||||
const char *path,
|
||||
GlnxChaseFlags flags,
|
||||
int expected_ino)
|
||||
{
|
||||
g_autoptr(GError) error = NULL;
|
||||
glnx_autofd int chase_fd = -1;
|
||||
|
||||
/* let's try to test the openat2 impl */
|
||||
chase_fd = glnx_chaseat (dfd, path, flags, &error);
|
||||
g_assert_no_error (error);
|
||||
g_assert_cmpint (chase_fd, >=, 0);
|
||||
g_assert_cmpint (get_ino (chase_fd), ==, expected_ino);
|
||||
g_clear_fd (&chase_fd, NULL);
|
||||
|
||||
/* let's try to test the open_tree impl */
|
||||
chase_fd = glnx_chaseat (dfd, path,
|
||||
flags | GLNX_CHASE_DEBUG_NO_OPENAT2,
|
||||
&error);
|
||||
g_assert_no_error (error);
|
||||
g_assert_cmpint (chase_fd, >=, 0);
|
||||
g_assert_cmpint (get_ino (chase_fd), ==, expected_ino);
|
||||
g_clear_fd (&chase_fd, NULL);
|
||||
|
||||
/* let's try to test the openat impl */
|
||||
chase_fd = glnx_chaseat (dfd, path,
|
||||
flags |
|
||||
GLNX_CHASE_DEBUG_NO_OPENAT2 |
|
||||
GLNX_CHASE_DEBUG_NO_OPEN_TREE,
|
||||
&error);
|
||||
g_assert_no_error (error);
|
||||
g_assert_cmpint (chase_fd, >=, 0);
|
||||
g_assert_cmpint (get_ino (chase_fd), ==, expected_ino);
|
||||
g_clear_fd (&chase_fd, NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
check_chase_error (int dfd,
|
||||
const char *path,
|
||||
GlnxChaseFlags flags,
|
||||
GQuark err_domain,
|
||||
gint err_code)
|
||||
{
|
||||
g_autoptr(GError) error = NULL;
|
||||
glnx_autofd int chase_fd = -1;
|
||||
|
||||
/* let's try to test the openat2 impl */
|
||||
chase_fd = glnx_chaseat (dfd, path, flags, &error);
|
||||
g_assert_cmpint (chase_fd, <, 0);
|
||||
g_assert_error (error, err_domain, err_code);
|
||||
g_clear_error (&error);
|
||||
|
||||
/* let's try to test the open_tree impl */
|
||||
chase_fd = glnx_chaseat (dfd, path,
|
||||
flags | GLNX_CHASE_DEBUG_NO_OPENAT2,
|
||||
&error);
|
||||
g_assert_cmpint (chase_fd, <, 0);
|
||||
g_assert_error (error, err_domain, err_code);
|
||||
g_clear_error (&error);
|
||||
|
||||
/* let's try to test the openat impl */
|
||||
chase_fd = glnx_chaseat (dfd, path,
|
||||
flags |
|
||||
GLNX_CHASE_DEBUG_NO_OPENAT2 |
|
||||
GLNX_CHASE_DEBUG_NO_OPEN_TREE,
|
||||
&error);
|
||||
g_assert_cmpint (chase_fd, <, 0);
|
||||
g_assert_error (error, err_domain, err_code);
|
||||
g_clear_error (&error);
|
||||
}
|
||||
|
||||
static void
|
||||
test_chase_relative (void)
|
||||
{
|
||||
g_autoptr(GError) error = NULL;
|
||||
glnx_autofd int dfd = -1;
|
||||
int expected_ino;
|
||||
|
||||
g_assert_true (glnx_shutil_mkdir_p_at_open (AT_FDCWD, "file/baz", 0755,
|
||||
&dfd,
|
||||
NULL, &error));
|
||||
g_assert_no_error (error);
|
||||
g_assert_cmpint (dfd, >=, 0);
|
||||
|
||||
expected_ino = get_ino (dfd);
|
||||
|
||||
for (size_t i = 0; i < G_N_ELEMENTS (test_paths); i++)
|
||||
check_chase (AT_FDCWD, test_paths[i], 0, expected_ino);
|
||||
|
||||
check_chase_error (AT_FDCWD, "nope", 0, G_IO_ERROR, G_IO_ERROR_NOT_FOUND);
|
||||
}
|
||||
|
||||
static void
|
||||
test_chase_relative_fd (void)
|
||||
{
|
||||
g_autoptr(GError) error = NULL;
|
||||
glnx_autofd int dfd = -1;
|
||||
int expected_ino;
|
||||
glnx_autofd int cwdfd = -1;
|
||||
|
||||
g_assert_true (glnx_shutil_mkdir_p_at_open (AT_FDCWD, "file/baz", 0755,
|
||||
&dfd,
|
||||
NULL, &error));
|
||||
g_assert_no_error (error);
|
||||
g_assert_cmpint (dfd, >=, 0);
|
||||
|
||||
expected_ino = get_ino (dfd);
|
||||
|
||||
cwdfd = openat (AT_FDCWD, ".", O_PATH | O_CLOEXEC | O_NOFOLLOW);
|
||||
g_assert_cmpint (cwdfd, >=, 0);
|
||||
|
||||
for (size_t i = 0; i < G_N_ELEMENTS (test_paths); i++)
|
||||
check_chase (cwdfd, test_paths[i], 0, expected_ino);
|
||||
|
||||
check_chase_error (cwdfd, "nope", 0, G_IO_ERROR, G_IO_ERROR_NOT_FOUND);
|
||||
}
|
||||
|
||||
static void
|
||||
test_chase_absolute (void)
|
||||
{
|
||||
g_autoptr(GError) error = NULL;
|
||||
glnx_autofd int dfd = -1;
|
||||
int expected_ino;
|
||||
glnx_autofd int cwdfd = -1;
|
||||
g_autofree char *proc_fd_path = NULL;
|
||||
g_autofree char *cwd_path = NULL;
|
||||
|
||||
g_assert_true (glnx_shutil_mkdir_p_at_open (AT_FDCWD, "file/baz", 0755,
|
||||
&dfd,
|
||||
NULL, &error));
|
||||
g_assert_no_error (error);
|
||||
g_assert_cmpint (dfd, >=, 0);
|
||||
|
||||
expected_ino = get_ino (dfd);
|
||||
|
||||
cwdfd = openat (AT_FDCWD, ".", O_PATH | O_CLOEXEC | O_NOFOLLOW);
|
||||
g_assert_cmpint (cwdfd, >=, 0);
|
||||
|
||||
cwd_path = get_abspath (cwdfd, "");
|
||||
|
||||
for (size_t i = 0; i < G_N_ELEMENTS (test_paths); i++)
|
||||
{
|
||||
g_autofree char *abspath = NULL;
|
||||
|
||||
abspath = g_strdup_printf ("%s/%s", cwd_path, test_paths[i]);
|
||||
check_chase (AT_FDCWD, abspath, 0, expected_ino);
|
||||
}
|
||||
|
||||
check_chase_error (AT_FDCWD, "/nope/nope/nope/345298308497623012313243543", 0,
|
||||
G_IO_ERROR, G_IO_ERROR_NOT_FOUND);
|
||||
}
|
||||
|
||||
static void
|
||||
test_chase_link (void)
|
||||
{
|
||||
g_autoptr(GError) error = NULL;
|
||||
glnx_autofd int dfd = -1;
|
||||
int link_ino;
|
||||
int target_ino;
|
||||
|
||||
g_assert_true (glnx_shutil_mkdir_p_at_open (AT_FDCWD, "file/baz", 0755,
|
||||
&dfd,
|
||||
NULL, &error));
|
||||
g_assert_no_error (error);
|
||||
g_assert_cmpint (dfd, >=, 0);
|
||||
|
||||
g_assert_cmpint (symlinkat ("file/baz", AT_FDCWD, "link"), ==, 0);
|
||||
|
||||
target_ino = get_ino (dfd);
|
||||
link_ino = path_get_ino ("link");
|
||||
|
||||
check_chase (AT_FDCWD, "link", 0, target_ino);
|
||||
check_chase (AT_FDCWD, "link/", 0, target_ino);
|
||||
check_chase (AT_FDCWD, "link///", 0, target_ino);
|
||||
check_chase (AT_FDCWD, "link/.//.", 0, target_ino);
|
||||
check_chase (AT_FDCWD, "link", 0, target_ino);
|
||||
|
||||
check_chase (AT_FDCWD, "link", GLNX_CHASE_NOFOLLOW, link_ino);
|
||||
check_chase (AT_FDCWD, "./file/../link", GLNX_CHASE_NOFOLLOW, link_ino);
|
||||
check_chase (AT_FDCWD, "link/", GLNX_CHASE_NOFOLLOW, target_ino);
|
||||
check_chase (AT_FDCWD, "././link/.", GLNX_CHASE_NOFOLLOW, target_ino);
|
||||
check_chase (AT_FDCWD, "link/.//", GLNX_CHASE_NOFOLLOW, target_ino);
|
||||
|
||||
check_chase (AT_FDCWD, "link",
|
||||
GLNX_CHASE_NOFOLLOW | GLNX_CHASE_RESOLVE_NO_SYMLINKS,
|
||||
link_ino);
|
||||
check_chase_error (AT_FDCWD, "link",
|
||||
GLNX_CHASE_RESOLVE_NO_SYMLINKS,
|
||||
G_IO_ERROR, G_IO_ERROR_TOO_MANY_LINKS);
|
||||
}
|
||||
|
||||
static void
|
||||
test_chase_resolve (void)
|
||||
{
|
||||
g_autoptr(GError) error = NULL;
|
||||
glnx_autofd int foo_dfd = -1;
|
||||
glnx_autofd int bar_dfd = -1;
|
||||
g_autofree char *foo_abspath = NULL;
|
||||
int ino;
|
||||
|
||||
g_assert_true (glnx_shutil_mkdir_p_at_open (AT_FDCWD, "foo", 0755,
|
||||
&foo_dfd,
|
||||
NULL, &error));
|
||||
g_assert_no_error (error);
|
||||
g_assert_cmpint (foo_dfd, >=, 0);
|
||||
|
||||
g_assert_true (glnx_shutil_mkdir_p_at_open (AT_FDCWD, "foo/bar", 0755,
|
||||
&bar_dfd,
|
||||
NULL, &error));
|
||||
g_assert_no_error (error);
|
||||
g_assert_cmpint (bar_dfd, >=, 0);
|
||||
|
||||
foo_abspath = get_abspath (foo_dfd, "");
|
||||
|
||||
g_assert_cmpint (symlinkat ("..", foo_dfd, "link1"), ==, 0);
|
||||
g_assert_cmpint (symlinkat ("bar/../..", foo_dfd, "link2"), ==, 0);
|
||||
g_assert_cmpint (symlinkat (foo_abspath, foo_dfd, "link3"), ==, 0);
|
||||
g_assert_cmpint (symlinkat ("/bar", foo_dfd, "link4"), ==, 0);
|
||||
g_assert_cmpint (symlinkat ("link1/foo", foo_dfd, "link5"), ==, 0);
|
||||
g_assert_cmpint (symlinkat ("link7", foo_dfd, "link6"), ==, 0);
|
||||
g_assert_cmpint (symlinkat ("link6", foo_dfd, "link7"), ==, 0);
|
||||
|
||||
ino = get_ino (bar_dfd);
|
||||
|
||||
/* A bunch of different ways to get from CWD and foo to bar */
|
||||
check_chase (foo_dfd, "./bar", 0, ino);
|
||||
check_chase (foo_dfd, "../foo/bar", 0, ino);
|
||||
check_chase (foo_dfd, "link1/foo/bar", 0, ino);
|
||||
check_chase (AT_FDCWD, "foo/link1/foo/bar", 0, ino);
|
||||
check_chase (foo_dfd, "link2/foo/bar", 0, ino);
|
||||
check_chase (AT_FDCWD, ".///foo/./link2/foo/bar", 0, ino);
|
||||
check_chase (foo_dfd, "link3/bar", 0, ino);
|
||||
check_chase (AT_FDCWD, ".///foo/./link3/bar", 0, ino);
|
||||
check_chase (foo_dfd, "link5/bar", 0, ino);
|
||||
|
||||
/* check that NO_SYMLINKS works with a component in the middle */
|
||||
check_chase_error (AT_FDCWD, "foo/link3/bar",
|
||||
GLNX_CHASE_RESOLVE_NO_SYMLINKS,
|
||||
G_IO_ERROR, G_IO_ERROR_TOO_MANY_LINKS);
|
||||
|
||||
/* link6 points to link 7, points to link6, ... This should error out! */
|
||||
check_chase_error (foo_dfd, "link6/bar", 0,
|
||||
G_IO_ERROR, G_IO_ERROR_TOO_MANY_LINKS);
|
||||
|
||||
/* Test with links which never go below the dfd */
|
||||
check_chase (AT_FDCWD, "foo/link1/foo/bar",
|
||||
GLNX_CHASE_RESOLVE_BENEATH,
|
||||
ino);
|
||||
check_chase (AT_FDCWD, "foo/link2/foo/bar",
|
||||
GLNX_CHASE_RESOLVE_BENEATH,
|
||||
ino);
|
||||
/* An absolute link is always below the dfd */
|
||||
check_chase_error (AT_FDCWD, "foo/link3/foo/bar",
|
||||
GLNX_CHASE_RESOLVE_BENEATH,
|
||||
G_IO_ERROR, G_IO_ERROR_FAILED);
|
||||
|
||||
/* Same, but from foo instead of cwd */
|
||||
check_chase_error (foo_dfd, "link1/foo/bar",
|
||||
GLNX_CHASE_RESOLVE_BENEATH,
|
||||
G_IO_ERROR, G_IO_ERROR_FAILED);
|
||||
check_chase_error (foo_dfd, "link2/foo/bar",
|
||||
GLNX_CHASE_RESOLVE_BENEATH,
|
||||
G_IO_ERROR, G_IO_ERROR_FAILED);
|
||||
check_chase_error (foo_dfd, "link3/foo/bar",
|
||||
GLNX_CHASE_RESOLVE_BENEATH,
|
||||
G_IO_ERROR, G_IO_ERROR_FAILED);
|
||||
|
||||
/* Check that trying to be below the dfd with RESOLVE_IN_ROOT resolves to the
|
||||
* dfd itself */
|
||||
check_chase (foo_dfd, "link1/bar",
|
||||
GLNX_CHASE_RESOLVE_IN_ROOT,
|
||||
ino);
|
||||
check_chase (foo_dfd, "link2/bar",
|
||||
GLNX_CHASE_RESOLVE_IN_ROOT,
|
||||
ino);
|
||||
/* The absolute link is relative to dfd with RESOLVE_IN_ROOT, so this
|
||||
* fails... */
|
||||
check_chase_error (foo_dfd, "link3",
|
||||
GLNX_CHASE_RESOLVE_IN_ROOT,
|
||||
G_IO_ERROR, G_IO_ERROR_NOT_FOUND);
|
||||
/* ... but the link /bar resolves correctly from foo as dfd. */
|
||||
check_chase (foo_dfd, "link4",
|
||||
GLNX_CHASE_RESOLVE_IN_ROOT,
|
||||
ino);
|
||||
}
|
||||
|
||||
static void
|
||||
test_chase_resolve_in_root_absolute (void)
|
||||
{
|
||||
g_autoptr(GError) error = NULL;
|
||||
glnx_autofd int foo_dfd = -1;
|
||||
glnx_autofd int bar_dfd = -1;
|
||||
glnx_autofd int baz_dfd = -1;
|
||||
|
||||
g_assert_true (glnx_shutil_mkdir_p_at_open (AT_FDCWD, "foo", 0755,
|
||||
&foo_dfd,
|
||||
NULL, &error));
|
||||
g_assert_no_error (error);
|
||||
g_assert_cmpint (foo_dfd, >=, 0);
|
||||
|
||||
g_assert_true (glnx_shutil_mkdir_p_at_open (AT_FDCWD, "foo/bar", 0755,
|
||||
&bar_dfd,
|
||||
NULL, &error));
|
||||
g_assert_no_error (error);
|
||||
g_assert_cmpint (bar_dfd, >=, 0);
|
||||
|
||||
g_assert_true (glnx_shutil_mkdir_p_at_open (AT_FDCWD, "foo/bar/baz", 0755,
|
||||
&baz_dfd,
|
||||
NULL, &error));
|
||||
g_assert_no_error (error);
|
||||
g_assert_cmpint (baz_dfd, >=, 0);
|
||||
|
||||
/* Test the absolute symlink doesn't break tracking of the root level */
|
||||
g_assert_cmpint (symlinkat ("/..", baz_dfd, "link1"), ==, 0);
|
||||
|
||||
/* We should not be able to break out of the root! */
|
||||
check_chase (bar_dfd, "./baz/link1", GLNX_CHASE_RESOLVE_IN_ROOT, get_ino (bar_dfd));
|
||||
}
|
||||
|
||||
static void
|
||||
check_chase_and_statxat (int dfd,
|
||||
const char *path,
|
||||
GlnxChaseFlags flags,
|
||||
ino_t expected_ino,
|
||||
mode_t expected_type)
|
||||
{
|
||||
g_autoptr(GError) error = NULL;
|
||||
glnx_autofd int chase_fd = -1;
|
||||
struct glnx_statx stx;
|
||||
|
||||
/* let's try to test the openat2 impl */
|
||||
chase_fd = glnx_chase_and_statxat (dfd, path, flags,
|
||||
GLNX_STATX_TYPE | GLNX_STATX_INO,
|
||||
&stx, &error);
|
||||
g_assert_cmpint (chase_fd, >=, 0);
|
||||
g_assert_no_error (error);
|
||||
g_assert_cmpint (stx.stx_ino, ==, expected_ino);
|
||||
g_assert_cmpint (stx.stx_mode & S_IFMT, ==, expected_type);
|
||||
g_clear_fd (&chase_fd, NULL);
|
||||
|
||||
/* let's try to test the open_tree impl */
|
||||
chase_fd = glnx_chase_and_statxat (dfd, path,
|
||||
flags | GLNX_CHASE_DEBUG_NO_OPENAT2,
|
||||
GLNX_STATX_TYPE | GLNX_STATX_INO,
|
||||
&stx, &error);
|
||||
g_assert_cmpint (chase_fd, >=, 0);
|
||||
g_assert_no_error (error);
|
||||
g_assert_cmpint (stx.stx_ino, ==, expected_ino);
|
||||
g_assert_cmpint (stx.stx_mode & S_IFMT, ==, expected_type);
|
||||
g_clear_fd (&chase_fd, NULL);
|
||||
|
||||
/* let's try to test the openat impl */
|
||||
chase_fd = glnx_chase_and_statxat (dfd, path,
|
||||
flags |
|
||||
GLNX_CHASE_DEBUG_NO_OPENAT2 |
|
||||
GLNX_CHASE_DEBUG_NO_OPEN_TREE,
|
||||
GLNX_STATX_TYPE | GLNX_STATX_INO,
|
||||
&stx, &error);
|
||||
g_assert_cmpint (chase_fd, >=, 0);
|
||||
g_assert_no_error (error);
|
||||
g_assert_cmpint (stx.stx_ino, ==, expected_ino);
|
||||
g_assert_cmpint (stx.stx_mode & S_IFMT, ==, expected_type);
|
||||
g_clear_fd (&chase_fd, NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
check_chase_and_statxat_error (int dfd,
|
||||
const char *path,
|
||||
GlnxChaseFlags flags,
|
||||
GQuark err_domain,
|
||||
gint err_code)
|
||||
{
|
||||
g_autoptr(GError) error = NULL;
|
||||
glnx_autofd int chase_fd = -1;
|
||||
struct glnx_statx stx;
|
||||
|
||||
/* let's try to test the openat2 impl */
|
||||
chase_fd = glnx_chase_and_statxat (dfd, path, flags,
|
||||
GLNX_STATX_TYPE | GLNX_STATX_INO,
|
||||
&stx, &error);
|
||||
g_assert_cmpint (chase_fd, <, 0);
|
||||
g_assert_error (error, err_domain, err_code);
|
||||
g_clear_error (&error);
|
||||
|
||||
/* let's try to test the open_tree impl */
|
||||
chase_fd = glnx_chase_and_statxat (dfd, path,
|
||||
flags | GLNX_CHASE_DEBUG_NO_OPENAT2,
|
||||
GLNX_STATX_TYPE | GLNX_STATX_INO,
|
||||
&stx, &error);
|
||||
g_assert_cmpint (chase_fd, <, 0);
|
||||
g_assert_error (error, err_domain, err_code);
|
||||
g_clear_error (&error);
|
||||
|
||||
/* let's try to test the openat impl */
|
||||
chase_fd = glnx_chase_and_statxat (dfd, path,
|
||||
flags |
|
||||
GLNX_CHASE_DEBUG_NO_OPENAT2 |
|
||||
GLNX_CHASE_DEBUG_NO_OPEN_TREE,
|
||||
GLNX_STATX_TYPE | GLNX_STATX_INO,
|
||||
&stx, &error);
|
||||
g_assert_cmpint (chase_fd, <, 0);
|
||||
g_assert_error (error, err_domain, err_code);
|
||||
g_clear_error (&error);
|
||||
}
|
||||
|
||||
static void
|
||||
test_chase_and_statxat_basic (void)
|
||||
{
|
||||
g_autoptr(GError) error = NULL;
|
||||
glnx_autofd int dfd = -1;
|
||||
glnx_autofd int file_fd = -1;
|
||||
ino_t expected_ino;
|
||||
|
||||
g_assert_true (glnx_shutil_mkdir_p_at_open (AT_FDCWD, "file/baz", 0755,
|
||||
&dfd,
|
||||
NULL, &error));
|
||||
g_assert_no_error (error);
|
||||
g_assert_cmpint (dfd, >=, 0);
|
||||
|
||||
expected_ino = get_ino (dfd);
|
||||
|
||||
/* Test with various path forms */
|
||||
for (size_t i = 0; i < G_N_ELEMENTS (test_paths); i++)
|
||||
check_chase_and_statxat (AT_FDCWD, test_paths[i], 0, expected_ino, S_IFDIR);
|
||||
|
||||
/* Create a regular file and test it */
|
||||
file_fd = openat (dfd, "testfile", O_WRONLY | O_CREAT | O_CLOEXEC, 0644);
|
||||
g_assert_cmpint (file_fd, >=, 0);
|
||||
g_clear_fd (&file_fd, NULL);
|
||||
|
||||
expected_ino = path_get_ino ("file/baz/testfile");
|
||||
check_chase_and_statxat (AT_FDCWD, "file/baz/testfile", 0, expected_ino, S_IFREG);
|
||||
|
||||
/* Test error cases */
|
||||
check_chase_and_statxat_error (AT_FDCWD, "nope", 0, G_IO_ERROR, G_IO_ERROR_NOT_FOUND);
|
||||
}
|
||||
|
||||
static void
|
||||
test_chase_and_statxat_symlink (void)
|
||||
{
|
||||
g_autoptr(GError) error = NULL;
|
||||
glnx_autofd int dfd = -1;
|
||||
glnx_autofd int chase_fd = -1;
|
||||
ino_t link_ino;
|
||||
ino_t target_ino;
|
||||
struct glnx_statx stx;
|
||||
|
||||
g_assert_true (glnx_shutil_mkdir_p_at_open (AT_FDCWD, "file/baz", 0755,
|
||||
&dfd,
|
||||
NULL, &error));
|
||||
g_assert_no_error (error);
|
||||
g_assert_cmpint (dfd, >=, 0);
|
||||
|
||||
g_assert_cmpint (symlinkat ("file/baz", AT_FDCWD, "fstatlink"), ==, 0);
|
||||
|
||||
target_ino = get_ino (dfd);
|
||||
link_ino = path_get_ino ("fstatlink");
|
||||
|
||||
/* Following symlinks should give us the directory */
|
||||
check_chase_and_statxat (AT_FDCWD, "fstatlink", 0, target_ino, S_IFDIR);
|
||||
check_chase_and_statxat (AT_FDCWD, "fstatlink/", 0, target_ino, S_IFDIR);
|
||||
|
||||
/* With NOFOLLOW, we should get the symlink itself */
|
||||
check_chase_and_statxat (AT_FDCWD, "fstatlink", GLNX_CHASE_NOFOLLOW, link_ino, S_IFLNK);
|
||||
|
||||
/* Verify we can distinguish between regular files, directories, and symlinks */
|
||||
chase_fd = glnx_chase_and_statxat (AT_FDCWD, "fstatlink", GLNX_CHASE_NOFOLLOW,
|
||||
GLNX_STATX_TYPE | GLNX_STATX_INO,
|
||||
&stx, &error);
|
||||
g_assert_cmpint (chase_fd, >=, 0);
|
||||
g_assert_no_error (error);
|
||||
g_assert_true (S_ISLNK (stx.stx_mode));
|
||||
g_clear_fd (&chase_fd, NULL);
|
||||
|
||||
chase_fd = glnx_chase_and_statxat (AT_FDCWD, "fstatlink", 0,
|
||||
GLNX_STATX_TYPE | GLNX_STATX_INO,
|
||||
&stx, &error);
|
||||
g_assert_cmpint (chase_fd, >=, 0);
|
||||
g_assert_no_error (error);
|
||||
g_assert_true (S_ISDIR (stx.stx_mode));
|
||||
g_clear_fd (&chase_fd, NULL);
|
||||
|
||||
/* Test with RESOLVE_NO_SYMLINKS */
|
||||
check_chase_and_statxat_error (AT_FDCWD, "fstatlink",
|
||||
GLNX_CHASE_RESOLVE_NO_SYMLINKS,
|
||||
G_IO_ERROR, G_IO_ERROR_TOO_MANY_LINKS);
|
||||
}
|
||||
|
||||
static void
|
||||
test_chase_and_statxat_permissions (void)
|
||||
{
|
||||
g_autoptr(GError) error = NULL;
|
||||
glnx_autofd int dfd = -1;
|
||||
glnx_autofd int file_fd = -1;
|
||||
glnx_autofd int chase_fd = -1;
|
||||
struct glnx_statx stx;
|
||||
mode_t expected_mode = 0640;
|
||||
|
||||
g_assert_true (glnx_shutil_mkdir_p_at_open (AT_FDCWD, "permtest", 0755,
|
||||
&dfd,
|
||||
NULL, &error));
|
||||
g_assert_no_error (error);
|
||||
|
||||
/* Create a file with specific permissions */
|
||||
file_fd = openat (dfd, "testfile", O_WRONLY | O_CREAT | O_CLOEXEC, expected_mode);
|
||||
g_assert_cmpint (file_fd, >=, 0);
|
||||
g_clear_fd (&file_fd, NULL);
|
||||
|
||||
/* Verify that glnx_chase_and_statxat returns the correct permissions */
|
||||
chase_fd = glnx_chase_and_statxat (dfd, "testfile", 0,
|
||||
GLNX_STATX_TYPE | GLNX_STATX_MODE,
|
||||
&stx, &error);
|
||||
g_assert_cmpint (chase_fd, >=, 0);
|
||||
g_assert_no_error (error);
|
||||
g_assert_cmpint (stx.stx_mode & 0777, ==, expected_mode);
|
||||
g_assert_true (S_ISREG (stx.stx_mode));
|
||||
g_clear_fd (&chase_fd, NULL);
|
||||
}
|
||||
|
||||
int main (int argc, char **argv)
|
||||
{
|
||||
_GLNX_TEST_SCOPED_TEMP_DIR;
|
||||
int ret;
|
||||
|
||||
g_test_init (&argc, &argv, NULL);
|
||||
|
||||
g_test_add_func ("/chase-relative", test_chase_relative);
|
||||
g_test_add_func ("/chase-relative-fd", test_chase_relative_fd);
|
||||
g_test_add_func ("/chase-absolute", test_chase_absolute);
|
||||
g_test_add_func ("/chase-link", test_chase_link);
|
||||
g_test_add_func ("/chase-resolve", test_chase_resolve);
|
||||
g_test_add_func ("/chase-resolve-in-root-absolute", test_chase_resolve_in_root_absolute);
|
||||
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);
|
||||
|
||||
ret = g_test_run();
|
||||
|
||||
return ret;
|
||||
}
|
||||
@@ -286,6 +286,158 @@ test_filecopy_procfs (void)
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
test_fd_reopen (void)
|
||||
{
|
||||
g_autoptr(GError) error = NULL;
|
||||
glnx_autofd int dfd = -1;
|
||||
glnx_autofd int opath_fd = -1;
|
||||
glnx_autofd int regular_fd = -1;
|
||||
glnx_autofd int testfile_fd = -1;
|
||||
glnx_autofd int link_opath_fd = -1;
|
||||
glnx_autofd int reopened_fd = -1;
|
||||
struct stat st1, st2;
|
||||
const char *test_data = "test content";
|
||||
char buf[100];
|
||||
ssize_t n;
|
||||
gboolean ok;
|
||||
int flags;
|
||||
|
||||
/* Create a test directory and file */
|
||||
ok = glnx_shutil_mkdir_p_at_open (AT_FDCWD, "reopen_test", 0755, &dfd, NULL, &error);
|
||||
g_assert_no_error (error);
|
||||
g_assert_true (ok);
|
||||
g_assert_no_errno (dfd);
|
||||
|
||||
glnx_file_replace_contents_at (dfd, "testfile",
|
||||
(const void *) test_data, strlen (test_data),
|
||||
GLNX_FILE_REPLACE_NODATASYNC, NULL, &error);
|
||||
g_assert_no_error (error);
|
||||
|
||||
/* Test 1: Reopen O_PATH fd as regular fd for reading and writing */
|
||||
opath_fd = openat (dfd, "testfile", O_PATH | O_CLOEXEC);
|
||||
g_assert_no_errno (opath_fd);
|
||||
|
||||
regular_fd = glnx_fd_reopen (opath_fd, O_RDWR, &error);
|
||||
g_assert_no_errno (regular_fd);
|
||||
g_assert_no_error (error);
|
||||
|
||||
flags = fcntl (regular_fd, F_GETFL);
|
||||
g_assert_no_errno (flags);
|
||||
g_assert_cmpint (flags & (O_RDONLY | O_WRONLY | O_RDWR), ==, O_RDWR);
|
||||
g_assert_cmpint (flags & (O_PATH | O_DIRECTORY | O_NOFOLLOW), ==, 0);
|
||||
flags = fcntl (regular_fd, F_GETFD);
|
||||
g_assert_no_errno (flags);
|
||||
g_assert_cmpint (flags & FD_CLOEXEC, ==, FD_CLOEXEC);
|
||||
|
||||
/* Verify we can read from the reopened fd */
|
||||
n = read (regular_fd, buf, sizeof (buf));
|
||||
g_assert_cmpmem (buf, n, test_data, strlen (test_data));
|
||||
|
||||
g_clear_fd (®ular_fd, NULL);
|
||||
g_clear_fd (&opath_fd, NULL);
|
||||
|
||||
/* Test 2: Reopen directory fd with O_DIRECTORY */
|
||||
opath_fd = openat (AT_FDCWD, "reopen_test", O_PATH | O_CLOEXEC);
|
||||
g_assert_no_errno (opath_fd);
|
||||
|
||||
reopened_fd = glnx_fd_reopen (opath_fd, O_RDONLY | O_DIRECTORY, &error);
|
||||
g_assert_no_error (error);
|
||||
g_assert_no_errno (reopened_fd);
|
||||
|
||||
flags = fcntl (reopened_fd, F_GETFL);
|
||||
g_assert_no_errno (flags);
|
||||
g_assert_cmpint (flags & (O_RDONLY | O_WRONLY | O_RDWR), ==, O_RDONLY);
|
||||
g_assert_cmpint (flags & (O_PATH | O_DIRECTORY | O_NOFOLLOW), ==, O_DIRECTORY);
|
||||
flags = fcntl (reopened_fd, F_GETFD);
|
||||
g_assert_no_errno (flags);
|
||||
g_assert_cmpint (flags & FD_CLOEXEC, ==, FD_CLOEXEC);
|
||||
|
||||
/* Verify both fds point to the same inode */
|
||||
g_assert_no_errno (fstat (opath_fd, &st1));
|
||||
g_assert_no_errno (fstat (reopened_fd, &st2));
|
||||
g_assert_cmpint (st1.st_ino, ==, st2.st_ino);
|
||||
|
||||
g_clear_fd (&reopened_fd, NULL);
|
||||
g_clear_fd (&opath_fd, NULL);
|
||||
|
||||
/* Test 3: Reopen AT_FDCWD */
|
||||
reopened_fd = glnx_fd_reopen (AT_FDCWD, O_RDONLY | O_DIRECTORY, &error);
|
||||
g_assert_no_error (error);
|
||||
g_assert_no_errno (reopened_fd);
|
||||
|
||||
g_clear_fd (&reopened_fd, NULL);
|
||||
|
||||
/* Test 4: Test that O_NOFOLLOW is rejected */
|
||||
opath_fd = openat (dfd, "testfile", O_PATH | O_CLOEXEC);
|
||||
g_assert_no_errno (opath_fd);
|
||||
|
||||
regular_fd = glnx_fd_reopen (opath_fd, O_RDONLY | O_NOFOLLOW, &error);
|
||||
g_assert_cmpint (regular_fd, <, 0);
|
||||
g_assert_error (error, G_IO_ERROR, G_IO_ERROR_TOO_MANY_LINKS);
|
||||
g_clear_error (&error);
|
||||
|
||||
g_clear_fd (&opath_fd, NULL);
|
||||
|
||||
/* Test 5: Reopen O_PATH fd to symlink with O_PATH (should work) */
|
||||
g_assert_no_errno (symlinkat ("testfile", dfd, "testlink"));
|
||||
|
||||
link_opath_fd = openat (dfd, "testlink", O_PATH | O_NOFOLLOW);
|
||||
g_assert_no_errno (link_opath_fd);
|
||||
|
||||
/* Verify it's a symlink */
|
||||
g_assert_no_errno (fstatat (link_opath_fd, "", &st1, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW));
|
||||
g_assert_true (S_ISLNK (st1.st_mode));
|
||||
|
||||
/* Reopen with O_PATH should work */
|
||||
reopened_fd = glnx_fd_reopen (link_opath_fd, O_PATH, &error);
|
||||
g_assert_no_error (error);
|
||||
g_assert_no_errno (reopened_fd);
|
||||
|
||||
flags = fcntl (reopened_fd, F_GETFL);
|
||||
g_assert_no_errno (flags);
|
||||
g_assert_cmpint (flags & (O_RDONLY | O_WRONLY | O_RDWR), ==, O_RDONLY);
|
||||
g_assert_cmpint (flags & (O_PATH | O_DIRECTORY | O_NOFOLLOW), ==, O_PATH);
|
||||
flags = fcntl (reopened_fd, F_GETFD);
|
||||
g_assert_no_errno (flags);
|
||||
g_assert_cmpint (flags & FD_CLOEXEC, ==, FD_CLOEXEC);
|
||||
|
||||
/* Verify both point to the same symlink */
|
||||
g_assert_no_errno (fstatat (reopened_fd, "", &st2, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW));
|
||||
g_assert_cmpint (st1.st_ino, ==, st2.st_ino);
|
||||
g_assert_true (S_ISLNK (st2.st_mode));
|
||||
|
||||
g_clear_fd (&reopened_fd, NULL);
|
||||
|
||||
/* Test 6: Reopening O_PATH fd to symlink without O_PATH should fail with ELOOP */
|
||||
reopened_fd = glnx_fd_reopen (link_opath_fd, O_RDONLY, &error);
|
||||
g_assert_cmpint (reopened_fd, <, 0);
|
||||
g_assert_error (error, G_IO_ERROR, G_IO_ERROR_TOO_MANY_LINKS);
|
||||
g_clear_error (&error);
|
||||
|
||||
g_clear_fd (&link_opath_fd, NULL);
|
||||
|
||||
/* Test 7: Verify read index is reset */
|
||||
testfile_fd = openat (dfd, "testfile", O_RDONLY | O_CLOEXEC);
|
||||
g_assert_no_errno (testfile_fd);
|
||||
|
||||
/* Read some data to advance the read index */
|
||||
n = read (testfile_fd, buf, 4);
|
||||
g_assert_cmpint (n, ==, 4);
|
||||
|
||||
/* Reopen should reset the read index */
|
||||
reopened_fd = glnx_fd_reopen (testfile_fd, O_RDONLY, &error);
|
||||
g_assert_no_error (error);
|
||||
g_assert_no_errno (reopened_fd);
|
||||
|
||||
/* Should read from the beginning again */
|
||||
n = read (reopened_fd, buf, sizeof (buf));
|
||||
g_assert_cmpmem (buf, n, test_data, strlen (test_data));
|
||||
|
||||
g_clear_fd (&reopened_fd, NULL);
|
||||
g_clear_fd (&testfile_fd, NULL);
|
||||
}
|
||||
|
||||
int main (int argc, char **argv)
|
||||
{
|
||||
_GLNX_TEST_SCOPED_TEMP_DIR;
|
||||
@@ -300,6 +452,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 ("/fd-reopen", test_fd_reopen);
|
||||
|
||||
ret = g_test_run();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user