mirror of
git://sourceware.org/git/glibc.git
synced 2025-03-06 20:58:33 +01:00
support: Add FUSE-based file system test framework to support/
This allows to monitor the exact file system operations performed by glibc and inject errors. Hurd does not have <sys/mount.h>. To get the sources to compile at least, the same approach as in support/test-container.c is used. Reviewed-by: DJ Delorie <dj@redhat.com>
This commit is contained in:
parent
61f2c2e1d1
commit
f169509ded
4 changed files with 1270 additions and 0 deletions
|
@ -64,6 +64,7 @@ libsupport-routines = \
|
||||||
support_format_herrno \
|
support_format_herrno \
|
||||||
support_format_hostent \
|
support_format_hostent \
|
||||||
support_format_netent \
|
support_format_netent \
|
||||||
|
support_fuse \
|
||||||
support_isolate_in_subprocess \
|
support_isolate_in_subprocess \
|
||||||
support_mutex_pi_monotonic \
|
support_mutex_pi_monotonic \
|
||||||
support_need_proc \
|
support_need_proc \
|
||||||
|
@ -327,6 +328,7 @@ tests = \
|
||||||
tst-support_capture_subprocess \
|
tst-support_capture_subprocess \
|
||||||
tst-support_descriptors \
|
tst-support_descriptors \
|
||||||
tst-support_format_dns_packet \
|
tst-support_format_dns_packet \
|
||||||
|
tst-support_fuse \
|
||||||
tst-support_quote_blob \
|
tst-support_quote_blob \
|
||||||
tst-support_quote_blob_wide \
|
tst-support_quote_blob_wide \
|
||||||
tst-support_quote_string \
|
tst-support_quote_string \
|
||||||
|
|
215
support/fuse.h
Normal file
215
support/fuse.h
Normal file
|
@ -0,0 +1,215 @@
|
||||||
|
/* Facilities for FUSE-backed file system tests.
|
||||||
|
Copyright (C) 2024 Free Software Foundation, Inc.
|
||||||
|
This file is part of the GNU C Library.
|
||||||
|
|
||||||
|
The GNU C Library is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the GNU Lesser General Public
|
||||||
|
License as published by the Free Software Foundation; either
|
||||||
|
version 2.1 of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
The GNU C Library is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public
|
||||||
|
License along with the GNU C Library; if not, see
|
||||||
|
<https://www.gnu.org/licenses/>. */
|
||||||
|
|
||||||
|
/* Before using this functionality, use support_enter_mount_namespace
|
||||||
|
to ensure that mounts do not impact the overall system. */
|
||||||
|
|
||||||
|
#ifndef SUPPORT_FUSE_H
|
||||||
|
#define SUPPORT_FUSE_H
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#include <support/bundled/linux/include/uapi/linux/fuse.h>
|
||||||
|
|
||||||
|
/* This function must be called furst, before support_fuse_mount, to
|
||||||
|
prepare unprivileged mounting. */
|
||||||
|
void support_fuse_init (void);
|
||||||
|
|
||||||
|
/* This function can be called instead of support_fuse_init. It does
|
||||||
|
not use mount and user namespaces, so it requires root privileges,
|
||||||
|
and cleanup after testing may be incomplete. This is intended only
|
||||||
|
for test development. */
|
||||||
|
void support_fuse_init_no_namespace (void);
|
||||||
|
|
||||||
|
/* Opaque type for tracking FUSE mount state. */
|
||||||
|
struct support_fuse;
|
||||||
|
|
||||||
|
/* This function disables a mount point created using
|
||||||
|
support_fuse_mount. */
|
||||||
|
void support_fuse_unmount (struct support_fuse *) __nonnull ((1));
|
||||||
|
|
||||||
|
/* This function is called on a separate thread after calling
|
||||||
|
support_fuse_mount. F is the mount state, and CLOSURE the argument
|
||||||
|
that was passed to support_fuse_mount. The callback function is
|
||||||
|
expected to call support_fuse_next to read packets from the kernel
|
||||||
|
and handle them according to the test's need. */
|
||||||
|
typedef void (*support_fuse_callback) (struct support_fuse *f, void *closure);
|
||||||
|
|
||||||
|
/* This function creates a new mount point, implemented by CALLBACK.
|
||||||
|
CLOSURE is passed to CALLBACK as the second argument. */
|
||||||
|
struct support_fuse *support_fuse_mount (support_fuse_callback callback,
|
||||||
|
void *closure)
|
||||||
|
__nonnull ((1)) __attr_dealloc (support_fuse_unmount, 1);
|
||||||
|
|
||||||
|
/* This function returns the path to the mount point for F. The
|
||||||
|
returned string is valid until support_fuse_unmount (F) is called. */
|
||||||
|
const char * support_fuse_mountpoint (struct support_fuse *f) __nonnull ((1));
|
||||||
|
|
||||||
|
|
||||||
|
/* Renders the OPCODE as a string (FUSE_* constant. The caller must
|
||||||
|
free the returned string. */
|
||||||
|
char * support_fuse_opcode (uint32_t opcode) __attr_dealloc_free;
|
||||||
|
|
||||||
|
/* Use to provide a checked cast facility. Use the
|
||||||
|
support_fuse_in_cast macro below. */
|
||||||
|
void *support_fuse_cast_internal (struct fuse_in_header *, uint32_t)
|
||||||
|
__nonnull ((1));
|
||||||
|
void *support_fuse_cast_name_internal (struct fuse_in_header *, uint32_t,
|
||||||
|
size_t skip, char **name)
|
||||||
|
__nonnull ((1));
|
||||||
|
|
||||||
|
/* The macro expansion support_fuse_in_cast (P, TYPE) casts the
|
||||||
|
pointer INH to the appropriate type corresponding to the FUSE_TYPE
|
||||||
|
opcode. It fails (terminates the process) if INH->opcode does not
|
||||||
|
match FUSE_TYPE. The type of the returned pointer matches that of
|
||||||
|
the FUSE_* constant.
|
||||||
|
|
||||||
|
Maintenance note: Adding support for additional struct fuse_*_in
|
||||||
|
types is generally easy, except when there is trailing data after
|
||||||
|
the struct (see below for support_fuse_cast_name, for example), and
|
||||||
|
the kernel has changed struct sizes over time. This has happened
|
||||||
|
recently with struct fuse_setxattr_in, and would require special
|
||||||
|
handling if implemented. */
|
||||||
|
#define support_fuse_payload_type_INIT struct fuse_init_in
|
||||||
|
#define support_fuse_payload_type_LOOKUP char
|
||||||
|
#define support_fuse_payload_type_OPEN struct fuse_open_in
|
||||||
|
#define support_fuse_payload_type_READ struct fuse_read_in
|
||||||
|
#define support_fuse_payload_type_SETATTR struct fuse_setattr_in
|
||||||
|
#define support_fuse_payload_type_WRITE struct fuse_write_in
|
||||||
|
#define support_fuse_cast(typ, inh) \
|
||||||
|
((support_fuse_payload_type_##typ *) \
|
||||||
|
support_fuse_cast_internal ((inh), FUSE_##typ))
|
||||||
|
|
||||||
|
/* Same as support_fuse_cast, but also writes the passed name to *NAMEP. */
|
||||||
|
#define support_fuse_payload_name_type_CREATE struct fuse_create_in
|
||||||
|
#define support_fuse_payload_name_type_MKDIR struct fuse_mkdir_in
|
||||||
|
#define support_fuse_cast_name(typ, inh, namep) \
|
||||||
|
((support_fuse_payload_name_type_##typ *) \
|
||||||
|
support_fuse_cast_name_internal \
|
||||||
|
((inh), FUSE_##typ, sizeof (support_fuse_payload_name_type_##typ), \
|
||||||
|
(namep)))
|
||||||
|
|
||||||
|
/* This function should be called from the callback function. It
|
||||||
|
returns NULL if the mount point has been unmounted. The result can
|
||||||
|
be cast using support_fuse_in_cast. The pointer is invalidated
|
||||||
|
with the next call to support_fuse_next.
|
||||||
|
|
||||||
|
Typical use involves handling some basics using the
|
||||||
|
support_fuse_handle_* building blocks, following by a switch
|
||||||
|
statement on the result member of the returned struct, to implement
|
||||||
|
what a particular test needs. Casts to payload data should be made
|
||||||
|
using support_fuse_in_cast.
|
||||||
|
|
||||||
|
By default, FUSE_FORGET responses are filtered. See
|
||||||
|
support_fuse_filter_forget for turning that off. */
|
||||||
|
struct fuse_in_header *support_fuse_next (struct support_fuse *f)
|
||||||
|
__nonnull ((1));
|
||||||
|
|
||||||
|
/* This function can be called from a callback function to handle
|
||||||
|
basic aspects of directories (OPENDIR, GETATTR, RELEASEDIR).
|
||||||
|
inh->nodeid is used as the inode number for the directory. This
|
||||||
|
function must be called after support_fuse_next. */
|
||||||
|
bool support_fuse_handle_directory (struct support_fuse *f) __nonnull ((1));
|
||||||
|
|
||||||
|
/* This function can be called from a callback function to handle
|
||||||
|
access to the mount point itself, after call support_fuse_next. */
|
||||||
|
bool support_fuse_handle_mountpoint (struct support_fuse *f) __nonnull ((1));
|
||||||
|
|
||||||
|
/* If FILTER_ENABLED, future support_fuse_next calls will not return
|
||||||
|
FUSE_FORGET events (and simply discared them, as they require no
|
||||||
|
reply). If !FILTER_ENABLED, the callback needs to handle
|
||||||
|
FUSE_FORGET events and call support_fuse_no_reply. */
|
||||||
|
void support_fuse_filter_forget (struct support_fuse *f, bool filter_enabled)
|
||||||
|
__nonnull ((1));
|
||||||
|
|
||||||
|
/* This function should be called from the callback function after
|
||||||
|
support_fuse_next returned a non-null pointer. It sends out a
|
||||||
|
response packet on the FUSE device with the supplied payload data. */
|
||||||
|
void support_fuse_reply (struct support_fuse *f,
|
||||||
|
const void *payload, size_t payload_size)
|
||||||
|
__nonnull ((1)) __attr_access ((__read_only__, 2, 3));
|
||||||
|
|
||||||
|
/* This function should be called from the callback function. It
|
||||||
|
replies to a request with an error indicator. ERROR must be positive. */
|
||||||
|
void support_fuse_reply_error (struct support_fuse *f, uint32_t error)
|
||||||
|
__nonnull ((1));
|
||||||
|
|
||||||
|
/* This function should be called from the callback function. It
|
||||||
|
sends out an empty (but success-indicating) reply packet. */
|
||||||
|
void support_fuse_reply_empty (struct support_fuse *f) __nonnull ((1));
|
||||||
|
|
||||||
|
/* Do not send a reply. Only to be used after a support_fuse_next
|
||||||
|
call that returned a FUSE_FORGET event. */
|
||||||
|
void support_fuse_no_reply (struct support_fuse *f) __nonnull ((1));
|
||||||
|
|
||||||
|
/* Specific reponse preparation functions. The returned object can be
|
||||||
|
updated as needed. If a NODEID argument is present, it will be
|
||||||
|
used to set the inode and FUSE nodeid fields. Without such an
|
||||||
|
argument, it is initialized from the current request (if the reply
|
||||||
|
requires this field). This function must be called after
|
||||||
|
support_fuse_next. The actual response must be sent using
|
||||||
|
support_fuse_reply_prepared (or a support_fuse_reply_error call can
|
||||||
|
be used to cancel the response). */
|
||||||
|
struct fuse_entry_out *support_fuse_prepare_entry (struct support_fuse *f,
|
||||||
|
uint64_t nodeid)
|
||||||
|
__nonnull ((1));
|
||||||
|
struct fuse_attr_out *support_fuse_prepare_attr (struct support_fuse *f)
|
||||||
|
__nonnull ((1));
|
||||||
|
|
||||||
|
/* Similar to the other support_fuse_prepare_* functions, but it
|
||||||
|
prepares for two response packets. They can be updated through the
|
||||||
|
pointers written to *OUT_ENTRY and *OUT_OPEN prior to calling
|
||||||
|
support_fuse_reply_prepared. */
|
||||||
|
void support_fuse_prepare_create (struct support_fuse *f,
|
||||||
|
uint64_t nodeid,
|
||||||
|
struct fuse_entry_out **out_entry,
|
||||||
|
struct fuse_open_out **out_open)
|
||||||
|
__nonnull ((1, 3, 4));
|
||||||
|
|
||||||
|
|
||||||
|
/* Prepare sending a directory stream. Must be called after
|
||||||
|
support_fuse_next and before support_fuse_dirstream_add. */
|
||||||
|
struct support_fuse_dirstream;
|
||||||
|
struct support_fuse_dirstream *support_fuse_prepare_readdir (struct
|
||||||
|
support_fuse *f);
|
||||||
|
|
||||||
|
/* Adds directory using D_INO, D_OFF, D_TYPE, D_NAME to the directory
|
||||||
|
stream D. Must be called after support_fuse_prepare_readdir.
|
||||||
|
|
||||||
|
D_OFF is the offset of the next directory entry, not the current
|
||||||
|
one. The first entry has offset zero. The first requested offset
|
||||||
|
can be obtained from the READ payload (struct fuse_read_in) prior
|
||||||
|
to calling this function.
|
||||||
|
|
||||||
|
Returns true if the entry could be added to the buffer, or false if
|
||||||
|
there was insufficient room. Sending the buffer is delayed until
|
||||||
|
support_fuse_reply_prepared is called. */
|
||||||
|
bool support_fuse_dirstream_add (struct support_fuse_dirstream *d,
|
||||||
|
uint64_t d_ino, uint64_t d_off,
|
||||||
|
uint32_t d_type,
|
||||||
|
const char *d_name);
|
||||||
|
|
||||||
|
/* Send a prepared response. Must be called after one of the
|
||||||
|
support_fuse_prepare_* functions and before the next
|
||||||
|
support_fuse_next call. */
|
||||||
|
void support_fuse_reply_prepared (struct support_fuse *f) __nonnull ((1));
|
||||||
|
|
||||||
|
#endif /* SUPPORT_FUSE_H */
|
705
support/support_fuse.c
Normal file
705
support/support_fuse.c
Normal file
|
@ -0,0 +1,705 @@
|
||||||
|
/* Facilities for FUSE-backed file system tests.
|
||||||
|
Copyright (C) 2024 Free Software Foundation, Inc.
|
||||||
|
This file is part of the GNU C Library.
|
||||||
|
|
||||||
|
The GNU C Library is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the GNU Lesser General Public
|
||||||
|
License as published by the Free Software Foundation; either
|
||||||
|
version 2.1 of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
The GNU C Library is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public
|
||||||
|
License along with the GNU C Library; if not, see
|
||||||
|
<https://www.gnu.org/licenses/>. */
|
||||||
|
|
||||||
|
#include <support/fuse.h>
|
||||||
|
|
||||||
|
#include <dirent.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/sysmacros.h>
|
||||||
|
#include <sys/uio.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <array_length.h>
|
||||||
|
#include <support/check.h>
|
||||||
|
#include <support/namespace.h>
|
||||||
|
#include <support/support.h>
|
||||||
|
#include <support/test-driver.h>
|
||||||
|
#include <support/xdirent.h>
|
||||||
|
#include <support/xthread.h>
|
||||||
|
#include <support/xunistd.h>
|
||||||
|
|
||||||
|
#ifdef __linux__
|
||||||
|
# include <sys/mount.h>
|
||||||
|
#else
|
||||||
|
/* Fallback definitions that mark the test as unsupported. */
|
||||||
|
# define mount(...) ({ FAIL_UNSUPPORTED ("mount"); -1; })
|
||||||
|
# define umount(...) ({ FAIL_UNSUPPORTED ("mount"); -1; })
|
||||||
|
#endif
|
||||||
|
|
||||||
|
struct support_fuse
|
||||||
|
{
|
||||||
|
char *mountpoint;
|
||||||
|
void *buffer_start; /* Begin of allocation. */
|
||||||
|
void *buffer_next; /* Next read position. */
|
||||||
|
void *buffer_limit; /* End of buffered data. */
|
||||||
|
void *buffer_end; /* End of allocation. */
|
||||||
|
struct fuse_in_header *inh; /* Most recent request (support_fuse_next). */
|
||||||
|
union /* Space for prepared responses. */
|
||||||
|
{
|
||||||
|
struct fuse_attr_out attr;
|
||||||
|
struct fuse_entry_out entry;
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
struct fuse_entry_out entry;
|
||||||
|
struct fuse_open_out open;
|
||||||
|
} create;
|
||||||
|
} prepared;
|
||||||
|
void *prepared_pointer; /* NULL if inactive. */
|
||||||
|
size_t prepared_size; /* 0 if inactive. */
|
||||||
|
|
||||||
|
/* Used for preparing readdir responses. Already used-up area for
|
||||||
|
the current request is counted by prepared_size. */
|
||||||
|
void *readdir_buffer;
|
||||||
|
size_t readdir_buffer_size;
|
||||||
|
|
||||||
|
pthread_t handler; /* Thread handling requests. */
|
||||||
|
uid_t uid; /* Cached value for the current process. */
|
||||||
|
uid_t gid; /* Cached value for the current process. */
|
||||||
|
int fd; /* FUSE file descriptor. */
|
||||||
|
int connection; /* Entry under /sys/fs/fuse/connections. */
|
||||||
|
bool filter_forget; /* Controls FUSE_FORGET event dropping. */
|
||||||
|
_Atomic bool disconnected;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct fuse_thread_wrapper_args
|
||||||
|
{
|
||||||
|
struct support_fuse *f;
|
||||||
|
support_fuse_callback callback;
|
||||||
|
void *closure;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Set by support_fuse_init to indicate that support_fuse_mount may be
|
||||||
|
called. */
|
||||||
|
static bool support_fuse_init_called;
|
||||||
|
|
||||||
|
/* Allocate the read buffer in F with SIZE bytes capacity. Does not
|
||||||
|
free the previously allocated buffer. */
|
||||||
|
static void support_fuse_allocate (struct support_fuse *f, size_t size)
|
||||||
|
__nonnull ((1));
|
||||||
|
|
||||||
|
/* Internal mkdtemp replacement */
|
||||||
|
static char * support_fuse_mkdir (const char *prefix) __nonnull ((1));
|
||||||
|
|
||||||
|
/* Low-level allocation function for support_fuse_mount. Does not
|
||||||
|
perform the mount. */
|
||||||
|
static struct support_fuse *support_fuse_open (void);
|
||||||
|
|
||||||
|
/* Thread wrapper function for use with pthread_create. Uses struct
|
||||||
|
fuse_thread_wrapper_args. */
|
||||||
|
static void *support_fuse_thread_wrapper (void *closure) __nonnull ((1));
|
||||||
|
|
||||||
|
/* Initial step before preparing a reply. SIZE must be the size of
|
||||||
|
the F->prepared member that is going to be used. */
|
||||||
|
static void support_fuse_prepare_1 (struct support_fuse *f, size_t size);
|
||||||
|
|
||||||
|
/* Similar to support_fuse_reply_error, but not check that ERROR is
|
||||||
|
not zero. */
|
||||||
|
static void support_fuse_reply_error_1 (struct support_fuse *f,
|
||||||
|
uint32_t error) __nonnull ((1));
|
||||||
|
|
||||||
|
/* Path to the directory containing mount points. Initialized by an
|
||||||
|
ELF constructor. All mountpoints are collected there so that the
|
||||||
|
test wrapper can clean them up without keeping track of them
|
||||||
|
individually. */
|
||||||
|
static char *support_fuse_mountpoints;
|
||||||
|
|
||||||
|
/* PID of the process that should clean up the mount points in the ELF
|
||||||
|
destructor. */
|
||||||
|
static pid_t support_fuse_cleanup_pid;
|
||||||
|
|
||||||
|
static void
|
||||||
|
support_fuse_allocate (struct support_fuse *f, size_t size)
|
||||||
|
{
|
||||||
|
f->buffer_start = xmalloc (size);
|
||||||
|
f->buffer_end = f->buffer_start + size;
|
||||||
|
f->buffer_limit = f->buffer_start;
|
||||||
|
f->buffer_next = f->buffer_limit;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
support_fuse_filter_forget (struct support_fuse *f, bool filter)
|
||||||
|
{
|
||||||
|
f->filter_forget = filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
void *
|
||||||
|
support_fuse_cast_internal (struct fuse_in_header *p, uint32_t expected)
|
||||||
|
{
|
||||||
|
if (expected != p->opcode
|
||||||
|
&& !(expected == FUSE_READ && p->opcode == FUSE_READDIR))
|
||||||
|
{
|
||||||
|
char *expected1 = support_fuse_opcode (expected);
|
||||||
|
char *actual = support_fuse_opcode (p->opcode);
|
||||||
|
FAIL_EXIT1 ("attempt to cast %s to %s", actual, expected1);
|
||||||
|
}
|
||||||
|
return p + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void *
|
||||||
|
support_fuse_cast_name_internal (struct fuse_in_header *p, uint32_t expected,
|
||||||
|
size_t skip, char **name)
|
||||||
|
{
|
||||||
|
char *result = support_fuse_cast_internal (p, expected);
|
||||||
|
*name = result + skip;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
support_fuse_dirstream_add (struct support_fuse_dirstream *d,
|
||||||
|
uint64_t d_ino, uint64_t d_off,
|
||||||
|
uint32_t d_type, const char *d_name)
|
||||||
|
{
|
||||||
|
struct support_fuse *f = (struct support_fuse *) d;
|
||||||
|
size_t structlen = offsetof (struct fuse_dirent, name);
|
||||||
|
size_t namelen = strlen (d_name); /* No null termination. */
|
||||||
|
size_t required_size = FUSE_DIRENT_ALIGN (structlen + namelen);
|
||||||
|
if (f->readdir_buffer_size - f->prepared_size < required_size)
|
||||||
|
return false;
|
||||||
|
struct fuse_dirent entry =
|
||||||
|
{
|
||||||
|
.ino = d_ino,
|
||||||
|
.off = d_off,
|
||||||
|
.type = d_type,
|
||||||
|
.namelen = namelen,
|
||||||
|
};
|
||||||
|
memcpy (f->readdir_buffer + f->prepared_size, &entry, structlen);
|
||||||
|
/* Use strncpy to write padding and avoid passing uninitialized
|
||||||
|
bytes to the read system call. */
|
||||||
|
strncpy (f->readdir_buffer + f->prepared_size + structlen, d_name,
|
||||||
|
required_size - structlen);
|
||||||
|
f->prepared_size += required_size;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
support_fuse_handle_directory (struct support_fuse *f)
|
||||||
|
{
|
||||||
|
TEST_VERIFY (f->inh != NULL);
|
||||||
|
switch (f->inh->opcode)
|
||||||
|
{
|
||||||
|
case FUSE_OPENDIR:
|
||||||
|
{
|
||||||
|
struct fuse_open_out out =
|
||||||
|
{
|
||||||
|
};
|
||||||
|
support_fuse_reply (f, &out, sizeof (out));
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
case FUSE_RELEASEDIR:
|
||||||
|
support_fuse_reply_empty (f);
|
||||||
|
return true;
|
||||||
|
case FUSE_GETATTR:
|
||||||
|
{
|
||||||
|
struct fuse_attr_out *out = support_fuse_prepare_attr (f);
|
||||||
|
out->attr.mode = S_IFDIR | 0700;
|
||||||
|
support_fuse_reply_prepared (f);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
support_fuse_handle_mountpoint (struct support_fuse *f)
|
||||||
|
{
|
||||||
|
TEST_VERIFY (f->inh != NULL);
|
||||||
|
/* 1 is the root node. */
|
||||||
|
if (f->inh->opcode == FUSE_GETATTR && f->inh->nodeid == 1)
|
||||||
|
return support_fuse_handle_directory (f);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
support_fuse_init (void)
|
||||||
|
{
|
||||||
|
support_fuse_init_called = true;
|
||||||
|
|
||||||
|
support_become_root ();
|
||||||
|
if (!support_enter_mount_namespace ())
|
||||||
|
FAIL_UNSUPPORTED ("mount namespaces not supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
support_fuse_init_no_namespace (void)
|
||||||
|
{
|
||||||
|
support_fuse_init_called = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *
|
||||||
|
support_fuse_mkdir (const char *prefix)
|
||||||
|
{
|
||||||
|
/* Do not use mkdtemp to avoid interfering with its tests. */
|
||||||
|
unsigned int counter = 1;
|
||||||
|
unsigned int pid = getpid ();
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
char *path = xasprintf ("%s%u.%u/", prefix, pid, counter);
|
||||||
|
if (mkdir (path, 0700) == 0)
|
||||||
|
return path;
|
||||||
|
if (errno != EEXIST)
|
||||||
|
FAIL_EXIT1 ("mkdir (\"%s\"): %m", path);
|
||||||
|
free (path);
|
||||||
|
++counter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct support_fuse *
|
||||||
|
support_fuse_mount (support_fuse_callback callback, void *closure)
|
||||||
|
{
|
||||||
|
TEST_VERIFY_EXIT (support_fuse_init_called);
|
||||||
|
|
||||||
|
/* Request at least minor version 12 because it changed struct sizes. */
|
||||||
|
enum { min_version = 12 };
|
||||||
|
|
||||||
|
struct support_fuse *f = support_fuse_open ();
|
||||||
|
char *mount_options
|
||||||
|
= xasprintf ("fd=%d,rootmode=040700,user_id=%u,group_id=%u",
|
||||||
|
f->fd, f->uid, f->gid);
|
||||||
|
if (mount ("fuse", f->mountpoint, "fuse.glibc",
|
||||||
|
MS_NOSUID|MS_NODEV, mount_options)
|
||||||
|
!= 0)
|
||||||
|
FAIL_EXIT1 ("FUSE mount on %s: %m", f->mountpoint);
|
||||||
|
free (mount_options);
|
||||||
|
|
||||||
|
/* Retry with an older FUSE version. */
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
struct fuse_in_header *inh = support_fuse_next (f);
|
||||||
|
struct fuse_init_in *init_in = support_fuse_cast (INIT, inh);
|
||||||
|
if (init_in->major < 7
|
||||||
|
|| (init_in->major == 7 && init_in->minor < min_version))
|
||||||
|
FAIL_UNSUPPORTED ("kernel FUSE version is %u.%u, too old",
|
||||||
|
init_in->major, init_in->minor);
|
||||||
|
if (init_in->major > 7)
|
||||||
|
{
|
||||||
|
uint32_t major = 7;
|
||||||
|
support_fuse_reply (f, &major, sizeof (major));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
TEST_VERIFY (init_in->flags & FUSE_DONT_MASK);
|
||||||
|
struct fuse_init_out out =
|
||||||
|
{
|
||||||
|
.major = 7,
|
||||||
|
.minor = min_version,
|
||||||
|
/* Request that the kernel does not apply umask. */
|
||||||
|
.flags = FUSE_DONT_MASK,
|
||||||
|
};
|
||||||
|
support_fuse_reply (f, &out, sizeof (out));
|
||||||
|
|
||||||
|
{
|
||||||
|
struct fuse_thread_wrapper_args args =
|
||||||
|
{
|
||||||
|
.f = f,
|
||||||
|
.callback = callback,
|
||||||
|
.closure = closure,
|
||||||
|
};
|
||||||
|
f->handler = xpthread_create (NULL,
|
||||||
|
support_fuse_thread_wrapper, &args);
|
||||||
|
struct stat64 st;
|
||||||
|
xstat64 (f->mountpoint, &st);
|
||||||
|
f->connection = minor (st.st_dev);
|
||||||
|
/* Got a reply from the thread, safe to deallocate args. */
|
||||||
|
}
|
||||||
|
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *
|
||||||
|
support_fuse_mountpoint (struct support_fuse *f)
|
||||||
|
{
|
||||||
|
return f->mountpoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
support_fuse_no_reply (struct support_fuse *f)
|
||||||
|
{
|
||||||
|
TEST_VERIFY (f->inh != NULL);
|
||||||
|
TEST_COMPARE (f->inh->opcode, FUSE_FORGET);
|
||||||
|
f->inh = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *
|
||||||
|
support_fuse_opcode (uint32_t op)
|
||||||
|
{
|
||||||
|
const char *result;
|
||||||
|
switch (op)
|
||||||
|
{
|
||||||
|
#define X(n) case n: result = #n; break
|
||||||
|
X(FUSE_LOOKUP);
|
||||||
|
X(FUSE_FORGET);
|
||||||
|
X(FUSE_GETATTR);
|
||||||
|
X(FUSE_SETATTR);
|
||||||
|
X(FUSE_READLINK);
|
||||||
|
X(FUSE_SYMLINK);
|
||||||
|
X(FUSE_MKNOD);
|
||||||
|
X(FUSE_MKDIR);
|
||||||
|
X(FUSE_UNLINK);
|
||||||
|
X(FUSE_RMDIR);
|
||||||
|
X(FUSE_RENAME);
|
||||||
|
X(FUSE_LINK);
|
||||||
|
X(FUSE_OPEN);
|
||||||
|
X(FUSE_READ);
|
||||||
|
X(FUSE_WRITE);
|
||||||
|
X(FUSE_STATFS);
|
||||||
|
X(FUSE_RELEASE);
|
||||||
|
X(FUSE_FSYNC);
|
||||||
|
X(FUSE_SETXATTR);
|
||||||
|
X(FUSE_GETXATTR);
|
||||||
|
X(FUSE_LISTXATTR);
|
||||||
|
X(FUSE_REMOVEXATTR);
|
||||||
|
X(FUSE_FLUSH);
|
||||||
|
X(FUSE_INIT);
|
||||||
|
X(FUSE_OPENDIR);
|
||||||
|
X(FUSE_READDIR);
|
||||||
|
X(FUSE_RELEASEDIR);
|
||||||
|
X(FUSE_FSYNCDIR);
|
||||||
|
X(FUSE_GETLK);
|
||||||
|
X(FUSE_SETLK);
|
||||||
|
X(FUSE_SETLKW);
|
||||||
|
X(FUSE_ACCESS);
|
||||||
|
X(FUSE_CREATE);
|
||||||
|
X(FUSE_INTERRUPT);
|
||||||
|
X(FUSE_BMAP);
|
||||||
|
X(FUSE_DESTROY);
|
||||||
|
X(FUSE_IOCTL);
|
||||||
|
X(FUSE_POLL);
|
||||||
|
X(FUSE_NOTIFY_REPLY);
|
||||||
|
X(FUSE_BATCH_FORGET);
|
||||||
|
X(FUSE_FALLOCATE);
|
||||||
|
X(FUSE_READDIRPLUS);
|
||||||
|
X(FUSE_RENAME2);
|
||||||
|
X(FUSE_LSEEK);
|
||||||
|
X(FUSE_COPY_FILE_RANGE);
|
||||||
|
X(FUSE_SETUPMAPPING);
|
||||||
|
X(FUSE_REMOVEMAPPING);
|
||||||
|
X(FUSE_SYNCFS);
|
||||||
|
X(FUSE_TMPFILE);
|
||||||
|
X(FUSE_STATX);
|
||||||
|
#undef X
|
||||||
|
default:
|
||||||
|
return xasprintf ("FUSE_unknown_%u", op);
|
||||||
|
}
|
||||||
|
return xstrdup (result);
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct support_fuse *
|
||||||
|
support_fuse_open (void)
|
||||||
|
{
|
||||||
|
struct support_fuse *result = xmalloc (sizeof (*result));
|
||||||
|
result->mountpoint = support_fuse_mkdir (support_fuse_mountpoints);
|
||||||
|
result->inh = NULL;
|
||||||
|
result->prepared_pointer = NULL;
|
||||||
|
result->prepared_size = 0;
|
||||||
|
result->readdir_buffer = NULL;
|
||||||
|
result->readdir_buffer_size = 0;
|
||||||
|
result->uid = getuid ();
|
||||||
|
result->gid = getgid ();
|
||||||
|
result->fd = open ("/dev/fuse", O_RDWR, 0);
|
||||||
|
if (result->fd < 0)
|
||||||
|
{
|
||||||
|
if (errno == ENOENT || errno == ENODEV || errno == EPERM
|
||||||
|
|| errno == EACCES)
|
||||||
|
FAIL_UNSUPPORTED ("cannot open /dev/fuse: %m");
|
||||||
|
else
|
||||||
|
FAIL_EXIT1 ("cannot open /dev/fuse: %m");
|
||||||
|
}
|
||||||
|
result->connection = -1;
|
||||||
|
result->filter_forget = true;
|
||||||
|
result->disconnected = false;
|
||||||
|
support_fuse_allocate (result, FUSE_MIN_READ_BUFFER);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
support_fuse_prepare_1 (struct support_fuse *f, size_t size)
|
||||||
|
{
|
||||||
|
TEST_VERIFY (f->prepared_pointer == NULL);
|
||||||
|
f->prepared_size = size;
|
||||||
|
memset (&f->prepared, 0, size);
|
||||||
|
f->prepared_pointer = &f->prepared;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct fuse_attr_out *
|
||||||
|
support_fuse_prepare_attr (struct support_fuse *f)
|
||||||
|
{
|
||||||
|
support_fuse_prepare_1 (f, sizeof (f->prepared.attr));
|
||||||
|
f->prepared.attr.attr.uid = f->uid;
|
||||||
|
f->prepared.attr.attr.gid = f->gid;
|
||||||
|
f->prepared.attr.attr.ino = f->inh->nodeid;
|
||||||
|
return &f->prepared.attr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
support_fuse_prepare_create (struct support_fuse *f,
|
||||||
|
uint64_t nodeid,
|
||||||
|
struct fuse_entry_out **out_entry,
|
||||||
|
struct fuse_open_out **out_open)
|
||||||
|
{
|
||||||
|
support_fuse_prepare_1 (f, sizeof (f->prepared.create));
|
||||||
|
f->prepared.create.entry.nodeid = nodeid;
|
||||||
|
f->prepared.create.entry.attr.uid = f->uid;
|
||||||
|
f->prepared.create.entry.attr.gid = f->gid;
|
||||||
|
f->prepared.create.entry.attr.ino = nodeid;
|
||||||
|
*out_entry = &f->prepared.create.entry;
|
||||||
|
*out_open = &f->prepared.create.open;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct fuse_entry_out *
|
||||||
|
support_fuse_prepare_entry (struct support_fuse *f, uint64_t nodeid)
|
||||||
|
{
|
||||||
|
support_fuse_prepare_1 (f, sizeof (f->prepared.entry));
|
||||||
|
f->prepared.entry.nodeid = nodeid;
|
||||||
|
f->prepared.entry.attr.uid = f->uid;
|
||||||
|
f->prepared.entry.attr.gid = f->gid;
|
||||||
|
f->prepared.entry.attr.ino = nodeid;
|
||||||
|
return &f->prepared.entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct support_fuse_dirstream *
|
||||||
|
support_fuse_prepare_readdir (struct support_fuse *f)
|
||||||
|
{
|
||||||
|
support_fuse_prepare_1 (f, 0);
|
||||||
|
struct fuse_read_in *p = support_fuse_cast (READ, f->inh);
|
||||||
|
if (p->size > f->readdir_buffer_size)
|
||||||
|
{
|
||||||
|
free (f->readdir_buffer);
|
||||||
|
f->readdir_buffer = xmalloc (p->size);
|
||||||
|
f->readdir_buffer_size = p->size;
|
||||||
|
}
|
||||||
|
f->prepared_pointer = f->readdir_buffer;
|
||||||
|
return (struct support_fuse_dirstream *) f;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct fuse_in_header *
|
||||||
|
support_fuse_next (struct support_fuse *f)
|
||||||
|
{
|
||||||
|
TEST_VERIFY (f->inh == NULL);
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
if (f->buffer_next < f->buffer_limit)
|
||||||
|
{
|
||||||
|
f->inh = f->buffer_next;
|
||||||
|
f->buffer_next = (void *) f->buffer_next + f->inh->len;
|
||||||
|
/* Suppress FUSE_FORGET responses if requested. */
|
||||||
|
if (f->filter_forget && f->inh->opcode == FUSE_FORGET)
|
||||||
|
{
|
||||||
|
f->inh = NULL;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return f->inh;
|
||||||
|
}
|
||||||
|
ssize_t ret = read (f->fd, f->buffer_start,
|
||||||
|
f->buffer_end - f->buffer_start);
|
||||||
|
if (ret == 0)
|
||||||
|
FAIL_EXIT (1, "unexpected EOF on FUSE device");
|
||||||
|
if (ret < 0 && errno == EINVAL)
|
||||||
|
{
|
||||||
|
/* Increase buffer size. */
|
||||||
|
size_t new_size = 2 * (size_t) (f->buffer_end - f->buffer_start);
|
||||||
|
free (f->buffer_start);
|
||||||
|
support_fuse_allocate (f, new_size);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (ret < 0)
|
||||||
|
{
|
||||||
|
if (f->disconnected)
|
||||||
|
/* Unmount detected. */
|
||||||
|
return NULL;
|
||||||
|
FAIL_EXIT1 ("read error on FUSE device: %m");
|
||||||
|
}
|
||||||
|
/* Read was successful, make [next, limit) the active buffer area. */
|
||||||
|
f->buffer_next = f->buffer_start;
|
||||||
|
f->buffer_limit = (void *) f->buffer_start + ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
support_fuse_reply (struct support_fuse *f,
|
||||||
|
const void *payload, size_t payload_size)
|
||||||
|
{
|
||||||
|
TEST_VERIFY_EXIT (f->inh != NULL);
|
||||||
|
TEST_VERIFY (f->prepared_pointer == NULL);
|
||||||
|
struct fuse_out_header outh =
|
||||||
|
{
|
||||||
|
.len = sizeof (outh) + payload_size,
|
||||||
|
.unique = f->inh->unique,
|
||||||
|
};
|
||||||
|
struct iovec iov[] =
|
||||||
|
{
|
||||||
|
{ &outh, sizeof (outh) },
|
||||||
|
{ (void *) payload, payload_size },
|
||||||
|
};
|
||||||
|
ssize_t ret = writev (f->fd, iov, array_length (iov));
|
||||||
|
if (ret < 0)
|
||||||
|
{
|
||||||
|
if (!f->disconnected)
|
||||||
|
/* Some kernels produce write errors upon disconnect. */
|
||||||
|
FAIL_EXIT1 ("FUSE write failed for %s response"
|
||||||
|
" (%zu bytes payload): %m",
|
||||||
|
support_fuse_opcode (f->inh->opcode), payload_size);
|
||||||
|
}
|
||||||
|
else if (ret != sizeof (outh) + payload_size)
|
||||||
|
FAIL_EXIT1 ("FUSE write short for %s response (%zu bytes payload):"
|
||||||
|
" %zd bytes",
|
||||||
|
support_fuse_opcode (f->inh->opcode), payload_size, ret);
|
||||||
|
f->inh = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
support_fuse_reply_empty (struct support_fuse *f)
|
||||||
|
{
|
||||||
|
support_fuse_reply_error_1 (f, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
support_fuse_reply_error_1 (struct support_fuse *f, uint32_t error)
|
||||||
|
{
|
||||||
|
TEST_VERIFY_EXIT (f->inh != NULL);
|
||||||
|
struct fuse_out_header outh =
|
||||||
|
{
|
||||||
|
.len = sizeof (outh),
|
||||||
|
.error = -error,
|
||||||
|
.unique = f->inh->unique,
|
||||||
|
};
|
||||||
|
ssize_t ret = write (f->fd, &outh, sizeof (outh));
|
||||||
|
if (ret < 0)
|
||||||
|
{
|
||||||
|
/* Some kernels produce write errors upon disconnect. */
|
||||||
|
if (!f->disconnected)
|
||||||
|
FAIL_EXIT1 ("FUSE write failed for %s error response: %m",
|
||||||
|
support_fuse_opcode (f->inh->opcode));
|
||||||
|
}
|
||||||
|
else if (ret != sizeof (outh))
|
||||||
|
FAIL_EXIT1 ("FUSE write short for %s error response: %zd bytes",
|
||||||
|
support_fuse_opcode (f->inh->opcode), ret);
|
||||||
|
f->inh = NULL;
|
||||||
|
f->prepared_pointer = NULL;
|
||||||
|
f->prepared_size = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
support_fuse_reply_error (struct support_fuse *f, uint32_t error)
|
||||||
|
{
|
||||||
|
TEST_VERIFY (error > 0);
|
||||||
|
support_fuse_reply_error_1 (f, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
support_fuse_reply_prepared (struct support_fuse *f)
|
||||||
|
{
|
||||||
|
TEST_VERIFY_EXIT (f->prepared_pointer != NULL);
|
||||||
|
/* Re-use the non-prepared reply function. It requires
|
||||||
|
f->prepared_* to be non-null, so reset the fields before the call. */
|
||||||
|
void *prepared_pointer = f->prepared_pointer;
|
||||||
|
size_t prepared_size = f->prepared_size;
|
||||||
|
f->prepared_pointer = NULL;
|
||||||
|
f->prepared_size = 0;
|
||||||
|
support_fuse_reply (f, prepared_pointer, prepared_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void *
|
||||||
|
support_fuse_thread_wrapper (void *closure)
|
||||||
|
{
|
||||||
|
struct fuse_thread_wrapper_args args
|
||||||
|
= *(struct fuse_thread_wrapper_args *) closure;
|
||||||
|
|
||||||
|
/* Handle the initial stat call. */
|
||||||
|
struct fuse_in_header *inh = support_fuse_next (args.f);
|
||||||
|
if (inh == NULL || !support_fuse_handle_mountpoint (args.f))
|
||||||
|
{
|
||||||
|
support_fuse_reply_error (args.f, EIO);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
args.callback (args.f, args.closure);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
support_fuse_unmount (struct support_fuse *f)
|
||||||
|
{
|
||||||
|
/* Signal the unmount to the handler thread. Some kernels report
|
||||||
|
not just ENODEV errors on read. */
|
||||||
|
f->disconnected = true;
|
||||||
|
|
||||||
|
{
|
||||||
|
char *path = xasprintf ("/sys/fs/fuse/connections/%d/abort",
|
||||||
|
f->connection);
|
||||||
|
/* Some kernels do not support these files under /sys. */
|
||||||
|
int fd = open (path, O_RDWR | O_TRUNC);
|
||||||
|
if (fd >= 0)
|
||||||
|
{
|
||||||
|
TEST_COMPARE (write (fd, "1", 1), 1);
|
||||||
|
xclose (fd);
|
||||||
|
}
|
||||||
|
free (path);
|
||||||
|
}
|
||||||
|
if (umount (f->mountpoint) != 0)
|
||||||
|
FAIL ("FUSE: umount (\"%s\"): %m", f->mountpoint);
|
||||||
|
xpthread_join (f->handler);
|
||||||
|
if (rmdir (f->mountpoint) != 0)
|
||||||
|
FAIL ("FUSE: rmdir (\"%s\"): %m", f->mountpoint);
|
||||||
|
xclose (f->fd);
|
||||||
|
free (f->mountpoint);
|
||||||
|
free (f->readdir_buffer);
|
||||||
|
free (f);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void __attribute__ ((constructor))
|
||||||
|
init (void)
|
||||||
|
{
|
||||||
|
/* The test_dir test driver variable is not yet set at this point. */
|
||||||
|
const char *tmpdir = getenv ("TMPDIR");
|
||||||
|
if (tmpdir == NULL || tmpdir[0] == '\0')
|
||||||
|
tmpdir = "/tmp";
|
||||||
|
|
||||||
|
char *prefix = xasprintf ("%s/glibc-tst-fuse.", tmpdir);
|
||||||
|
support_fuse_mountpoints = support_fuse_mkdir (prefix);
|
||||||
|
free (prefix);
|
||||||
|
support_fuse_cleanup_pid = getpid ();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void __attribute__ ((destructor))
|
||||||
|
fini (void)
|
||||||
|
{
|
||||||
|
if (support_fuse_cleanup_pid != getpid ()
|
||||||
|
|| support_fuse_mountpoints == NULL)
|
||||||
|
return;
|
||||||
|
DIR *dir = xopendir (support_fuse_mountpoints);
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
struct dirent64 *e = readdir64 (dir);
|
||||||
|
if (e == NULL)
|
||||||
|
/* Ignore errors. */
|
||||||
|
break;
|
||||||
|
if (*e->d_name == '.')
|
||||||
|
/* Skip "." and "..". No hidden files expected. */
|
||||||
|
continue;
|
||||||
|
if (unlinkat (dirfd (dir), e->d_name, AT_REMOVEDIR) != 0)
|
||||||
|
break;
|
||||||
|
rewinddir (dir);
|
||||||
|
}
|
||||||
|
xclosedir (dir);
|
||||||
|
rmdir (support_fuse_mountpoints);
|
||||||
|
free (support_fuse_mountpoints);
|
||||||
|
support_fuse_mountpoints = NULL;
|
||||||
|
}
|
348
support/tst-support_fuse.c
Normal file
348
support/tst-support_fuse.c
Normal file
|
@ -0,0 +1,348 @@
|
||||||
|
/* Facilities for FUSE-backed file system tests.
|
||||||
|
Copyright (C) 2024 Free Software Foundation, Inc.
|
||||||
|
This file is part of the GNU C Library.
|
||||||
|
|
||||||
|
The GNU C Library is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the GNU Lesser General Public
|
||||||
|
License as published by the Free Software Foundation; either
|
||||||
|
version 2.1 of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
The GNU C Library is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public
|
||||||
|
License along with the GNU C Library; if not, see
|
||||||
|
<https://www.gnu.org/licenses/>. */
|
||||||
|
|
||||||
|
#include <support/fuse.h>
|
||||||
|
|
||||||
|
#include <dirent.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <support/check.h>
|
||||||
|
#include <support/support.h>
|
||||||
|
#include <support/xdirent.h>
|
||||||
|
#include <support/xunistd.h>
|
||||||
|
|
||||||
|
static void
|
||||||
|
fuse_thread (struct support_fuse *f, void *closure)
|
||||||
|
{
|
||||||
|
/* Turn on returning FUSE_FORGET responses. */
|
||||||
|
support_fuse_filter_forget (f, false);
|
||||||
|
|
||||||
|
/* Inode and nodeid for "file" and "new". */
|
||||||
|
enum { NODE_FILE = 2, NODE_NEW, NODE_SUBDIR, NODE_SYMLINK };
|
||||||
|
struct fuse_in_header *inh;
|
||||||
|
while ((inh = support_fuse_next (f)) != NULL)
|
||||||
|
{
|
||||||
|
{
|
||||||
|
char *opcode = support_fuse_opcode (inh->opcode);
|
||||||
|
printf ("info: (T) event %s(%llu) len=%u nodeid=%llu\n",
|
||||||
|
opcode, (unsigned long long int) inh->unique, inh->len,
|
||||||
|
(unsigned long long int) inh->nodeid);
|
||||||
|
free (opcode);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Handle mountpoint and basic directory operation for the root (1). */
|
||||||
|
if (support_fuse_handle_mountpoint (f)
|
||||||
|
|| (inh->nodeid == 1 && support_fuse_handle_directory (f)))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
switch (inh->opcode)
|
||||||
|
{
|
||||||
|
case FUSE_READDIR:
|
||||||
|
/* Implementation of getdents64. */
|
||||||
|
if (inh->nodeid == 1)
|
||||||
|
{
|
||||||
|
struct support_fuse_dirstream *d
|
||||||
|
= support_fuse_prepare_readdir (f);
|
||||||
|
TEST_COMPARE (support_fuse_cast (READ, inh)->offset, 0);
|
||||||
|
TEST_VERIFY (support_fuse_dirstream_add (d, 1, 1, DT_DIR, "."));
|
||||||
|
TEST_VERIFY (support_fuse_dirstream_add (d, 1, 2, DT_DIR, ".."));
|
||||||
|
TEST_VERIFY (support_fuse_dirstream_add (d, NODE_FILE, 3, DT_REG,
|
||||||
|
"file"));
|
||||||
|
support_fuse_reply_prepared (f);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
support_fuse_reply_error (f, EIO);
|
||||||
|
break;
|
||||||
|
case FUSE_LOOKUP:
|
||||||
|
/* Part of the implementation of open. */
|
||||||
|
{
|
||||||
|
char *name = support_fuse_cast (LOOKUP, inh);
|
||||||
|
printf (" name: %s\n", name);
|
||||||
|
if (inh->nodeid == 1 && strcmp (name, "file") == 0)
|
||||||
|
{
|
||||||
|
struct fuse_entry_out *out
|
||||||
|
= support_fuse_prepare_entry (f, NODE_FILE);
|
||||||
|
out->attr.mode = S_IFREG | 0600;
|
||||||
|
support_fuse_reply_prepared (f);
|
||||||
|
}
|
||||||
|
else if (inh->nodeid == 1 && strcmp (name, "symlink") == 0)
|
||||||
|
{
|
||||||
|
struct fuse_entry_out *out
|
||||||
|
= support_fuse_prepare_entry (f, NODE_SYMLINK);
|
||||||
|
out->attr.mode = S_IFLNK | 0777;
|
||||||
|
support_fuse_reply_prepared (f);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
support_fuse_reply_error (f, ENOENT);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case FUSE_OPEN:
|
||||||
|
/* Implementation of open. */
|
||||||
|
{
|
||||||
|
struct fuse_open_in *p = support_fuse_cast (OPEN, inh);
|
||||||
|
if (inh->nodeid == NODE_FILE)
|
||||||
|
{
|
||||||
|
TEST_VERIFY (!(p->flags & O_EXCL));
|
||||||
|
struct fuse_open_out out = { 0, };
|
||||||
|
support_fuse_reply (f, &out, sizeof (out));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
support_fuse_reply_error (f, ENOENT);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case FUSE_GETATTR:
|
||||||
|
/* Happens after open. */
|
||||||
|
if (inh->nodeid == NODE_FILE)
|
||||||
|
{
|
||||||
|
struct fuse_attr_out *out = support_fuse_prepare_attr (f);
|
||||||
|
out->attr.mode = S_IFREG | 0600;
|
||||||
|
out->attr.size = strlen ("Hello, world!");
|
||||||
|
support_fuse_reply_prepared (f);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
support_fuse_reply_error (f, ENOENT);
|
||||||
|
break;
|
||||||
|
case FUSE_READ:
|
||||||
|
/* Implementation of read. */
|
||||||
|
if (inh->nodeid == NODE_FILE)
|
||||||
|
{
|
||||||
|
struct fuse_read_in *p = support_fuse_cast (READ, inh);
|
||||||
|
TEST_COMPARE (p->offset, 0);
|
||||||
|
TEST_VERIFY (p->size >= strlen ("Hello, world!"));
|
||||||
|
support_fuse_reply (f,
|
||||||
|
"Hello, world!", strlen ("Hello, world!"));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
support_fuse_reply_error (f, EIO);
|
||||||
|
break;
|
||||||
|
case FUSE_FLUSH:
|
||||||
|
/* Sent in response to close. */
|
||||||
|
support_fuse_reply_empty (f);
|
||||||
|
break;
|
||||||
|
case FUSE_GETXATTR:
|
||||||
|
/* This happens as part of a open-for-write operation.
|
||||||
|
Signal no support for extended attributes. */
|
||||||
|
support_fuse_reply_error (f, ENOSYS);
|
||||||
|
break;
|
||||||
|
case FUSE_SETATTR:
|
||||||
|
/* This happens as part of a open-for-write operation to
|
||||||
|
implement O_TRUNC. */
|
||||||
|
if (inh->nodeid == NODE_FILE)
|
||||||
|
{
|
||||||
|
struct fuse_setattr_in *p = support_fuse_cast (SETATTR, inh);
|
||||||
|
/* FATTR_LOCKOWNER may also be set. */
|
||||||
|
TEST_COMPARE ((p->valid) & ~ FATTR_LOCKOWNER, FATTR_SIZE);
|
||||||
|
TEST_COMPARE (p->size, 0);
|
||||||
|
struct fuse_attr_out *out = support_fuse_prepare_attr (f);
|
||||||
|
out->attr.mode = S_IFREG | 0600;
|
||||||
|
support_fuse_reply_prepared (f);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
support_fuse_reply_error (f, EIO);
|
||||||
|
break;
|
||||||
|
case FUSE_WRITE:
|
||||||
|
/* Implementation of write. */
|
||||||
|
if (inh->nodeid == NODE_FILE)
|
||||||
|
{
|
||||||
|
struct fuse_write_in *p = support_fuse_cast (WRITE, inh);
|
||||||
|
TEST_COMPARE (p->offset, 0);
|
||||||
|
/* Write payload follows after struct fuse_write_in. */
|
||||||
|
TEST_COMPARE_BLOB (p + 1, p->size,
|
||||||
|
"Good day to you too.",
|
||||||
|
strlen ("Good day to you too."));
|
||||||
|
struct fuse_write_out out =
|
||||||
|
{
|
||||||
|
.size = p->size,
|
||||||
|
};
|
||||||
|
support_fuse_reply (f, &out, sizeof (out));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
support_fuse_reply_error (f, EIO);
|
||||||
|
break;
|
||||||
|
case FUSE_CREATE:
|
||||||
|
/* Implementation of O_CREAT. */
|
||||||
|
if (inh->nodeid == 1)
|
||||||
|
{
|
||||||
|
char *name;
|
||||||
|
struct fuse_create_in *p
|
||||||
|
= support_fuse_cast_name (CREATE, inh, &name);
|
||||||
|
TEST_VERIFY (S_ISREG (p->mode));
|
||||||
|
TEST_COMPARE (p->mode & 07777, 0600);
|
||||||
|
TEST_COMPARE_STRING (name, "new");
|
||||||
|
struct fuse_entry_out *out_entry;
|
||||||
|
struct fuse_open_out *out_open;
|
||||||
|
support_fuse_prepare_create (f, NODE_NEW, &out_entry, &out_open);
|
||||||
|
out_entry->attr.mode = S_IFREG | 0600;
|
||||||
|
support_fuse_reply_prepared (f);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
support_fuse_reply_error (f, EIO);
|
||||||
|
break;
|
||||||
|
case FUSE_MKDIR:
|
||||||
|
/* Implementation of mkdir. */
|
||||||
|
{
|
||||||
|
if (inh->nodeid == 1)
|
||||||
|
{
|
||||||
|
char *name;
|
||||||
|
struct fuse_mkdir_in *p
|
||||||
|
= support_fuse_cast_name (MKDIR, inh, &name);
|
||||||
|
TEST_COMPARE (p->mode, 01234);
|
||||||
|
TEST_COMPARE_STRING (name, "subdir");
|
||||||
|
struct fuse_entry_out *out
|
||||||
|
= support_fuse_prepare_entry (f, NODE_SUBDIR);
|
||||||
|
out->attr.mode = S_IFDIR | p->mode;
|
||||||
|
support_fuse_reply_prepared (f);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
support_fuse_reply_error (f, EIO);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case FUSE_READLINK:
|
||||||
|
/* Implementation of readlink. */
|
||||||
|
TEST_COMPARE (inh->nodeid, NODE_SYMLINK);
|
||||||
|
if (inh->nodeid == NODE_SYMLINK)
|
||||||
|
support_fuse_reply (f, "target-of-symbolic-link",
|
||||||
|
strlen ("target-of-symbolic-link"));
|
||||||
|
else
|
||||||
|
support_fuse_reply_error (f, EINVAL);
|
||||||
|
break;
|
||||||
|
case FUSE_FORGET:
|
||||||
|
support_fuse_no_reply (f);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
support_fuse_reply_error (f, EIO);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
do_test (void)
|
||||||
|
{
|
||||||
|
support_fuse_init ();
|
||||||
|
|
||||||
|
struct support_fuse *f = support_fuse_mount (fuse_thread, NULL);
|
||||||
|
|
||||||
|
printf ("info: Attributes of mountpoint/root directory %s\n",
|
||||||
|
support_fuse_mountpoint (f));
|
||||||
|
{
|
||||||
|
struct statx st;
|
||||||
|
xstatx (AT_FDCWD, support_fuse_mountpoint (f), 0, STATX_BASIC_STATS, &st);
|
||||||
|
TEST_COMPARE (st.stx_uid, getuid ());
|
||||||
|
TEST_COMPARE (st.stx_gid, getgid ());
|
||||||
|
TEST_VERIFY (S_ISDIR (st.stx_mode));
|
||||||
|
TEST_COMPARE (st.stx_mode & 07777, 0700);
|
||||||
|
}
|
||||||
|
|
||||||
|
printf ("info: List directory %s\n", support_fuse_mountpoint (f));
|
||||||
|
{
|
||||||
|
DIR *dir = xopendir (support_fuse_mountpoint (f));
|
||||||
|
|
||||||
|
struct dirent *e = xreaddir (dir);
|
||||||
|
TEST_COMPARE (e->d_ino, 1);
|
||||||
|
#ifdef _DIRENT_HAVE_D_OFF
|
||||||
|
TEST_COMPARE (e->d_off, 1);
|
||||||
|
#endif
|
||||||
|
TEST_COMPARE (e->d_type, DT_DIR);
|
||||||
|
TEST_COMPARE_STRING (e->d_name, ".");
|
||||||
|
|
||||||
|
e = xreaddir (dir);
|
||||||
|
TEST_COMPARE (e->d_ino, 1);
|
||||||
|
#ifdef _DIRENT_HAVE_D_OFF
|
||||||
|
TEST_COMPARE (e->d_off, 2);
|
||||||
|
#endif
|
||||||
|
TEST_COMPARE (e->d_type, DT_DIR);
|
||||||
|
TEST_COMPARE_STRING (e->d_name, "..");
|
||||||
|
|
||||||
|
e = xreaddir (dir);
|
||||||
|
TEST_COMPARE (e->d_ino, 2);
|
||||||
|
#ifdef _DIRENT_HAVE_D_OFF
|
||||||
|
TEST_COMPARE (e->d_off, 3);
|
||||||
|
#endif
|
||||||
|
TEST_COMPARE (e->d_type, DT_REG);
|
||||||
|
TEST_COMPARE_STRING (e->d_name, "file");
|
||||||
|
|
||||||
|
TEST_COMPARE (closedir (dir), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
char *file_path = xasprintf ("%s/file", support_fuse_mountpoint (f));
|
||||||
|
|
||||||
|
printf ("info: Attributes of file %s\n", file_path);
|
||||||
|
{
|
||||||
|
struct statx st;
|
||||||
|
xstatx (AT_FDCWD, file_path, 0, STATX_BASIC_STATS, &st);
|
||||||
|
TEST_COMPARE (st.stx_uid, getuid ());
|
||||||
|
TEST_COMPARE (st.stx_gid, getgid ());
|
||||||
|
TEST_VERIFY (S_ISREG (st.stx_mode));
|
||||||
|
TEST_COMPARE (st.stx_mode & 07777, 0600);
|
||||||
|
TEST_COMPARE (st.stx_size, strlen ("Hello, world!"));
|
||||||
|
}
|
||||||
|
|
||||||
|
printf ("info: Read from %s\n", file_path);
|
||||||
|
{
|
||||||
|
int fd = xopen (file_path, O_RDONLY, 0);
|
||||||
|
char buf[64];
|
||||||
|
ssize_t len = read (fd, buf, sizeof (buf));
|
||||||
|
if (len < 0)
|
||||||
|
FAIL_EXIT1 ("read: %m");
|
||||||
|
TEST_COMPARE_BLOB (buf, len, "Hello, world!", strlen ("Hello, world!"));
|
||||||
|
xclose (fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
printf ("info: Write to %s\n", file_path);
|
||||||
|
{
|
||||||
|
int fd = xopen (file_path, O_WRONLY | O_TRUNC, 0);
|
||||||
|
xwrite (fd, "Good day to you too.", strlen ("Good day to you too."));
|
||||||
|
xclose (fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
printf ("info: Attempt O_EXCL creation of existing %s\n", file_path);
|
||||||
|
/* O_EXCL creation shall fail. */
|
||||||
|
errno = 0;
|
||||||
|
TEST_COMPARE (open64 (file_path, O_RDWR | O_EXCL | O_CREAT, 0600), -1);
|
||||||
|
TEST_COMPARE (errno, EEXIST);
|
||||||
|
|
||||||
|
free (file_path);
|
||||||
|
|
||||||
|
{
|
||||||
|
char *new_path = xasprintf ("%s/new", support_fuse_mountpoint (f));
|
||||||
|
printf ("info: Test successful O_EXCL creation at %s\n", new_path);
|
||||||
|
int fd = xopen (new_path, O_RDWR | O_EXCL | O_CREAT, 0600);
|
||||||
|
xclose (fd);
|
||||||
|
free (new_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
char *subdir_path = xasprintf ("%s/subdir", support_fuse_mountpoint (f));
|
||||||
|
xmkdir (subdir_path, 01234);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
char *symlink_path = xasprintf ("%s/symlink", support_fuse_mountpoint (f));
|
||||||
|
char *target = xreadlink (symlink_path);
|
||||||
|
TEST_COMPARE_STRING (target, "target-of-symbolic-link");
|
||||||
|
free (target);
|
||||||
|
free (symlink_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
support_fuse_unmount (f);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#include <support/test-driver.c>
|
Loading…
Add table
Reference in a new issue