diff options
Diffstat (limited to 'lib/libcuse')
-rw-r--r-- | lib/libcuse/Makefile | 68 | ||||
-rw-r--r-- | lib/libcuse/Makefile.depend | 16 | ||||
-rw-r--r-- | lib/libcuse/cuse.3 | 391 | ||||
-rw-r--r-- | lib/libcuse/cuse.h | 96 | ||||
-rw-r--r-- | lib/libcuse/cuse_lib.c | 794 |
5 files changed, 1365 insertions, 0 deletions
diff --git a/lib/libcuse/Makefile b/lib/libcuse/Makefile new file mode 100644 index 000000000000..3c381043201f --- /dev/null +++ b/lib/libcuse/Makefile @@ -0,0 +1,68 @@ +# +# Copyright (c) 2010 Hans Petter Selasky. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. +# + +PACKAGE=lib${LIB} +LIB= cuse +SHLIB_MAJOR= 1 +SHLIB_MINOR= 0 +SRCS= cuse_lib.c +INCS= cuse.h +MAN= cuse.3 +CFLAGS+= -D_GNU_SOURCE +.if defined(HAVE_DEBUG) +CFLAGS+= -g +CFLAGS+= -DHAVE_DEBUG +.endif +LIBADD= pthread + +MLINKS= +MLINKS+= cuse.3 cuse_alloc_unit_number.3 +MLINKS+= cuse.3 cuse_alloc_unit_number_by_id.3 +MLINKS+= cuse.3 cuse_copy_in.3 +MLINKS+= cuse.3 cuse_copy_out.3 +MLINKS+= cuse.3 cuse_dev_create.3 +MLINKS+= cuse.3 cuse_dev_destroy.3 +MLINKS+= cuse.3 cuse_dev_get_current.3 +MLINKS+= cuse.3 cuse_dev_get_per_file_handle.3 +MLINKS+= cuse.3 cuse_dev_get_priv0.3 +MLINKS+= cuse.3 cuse_dev_get_priv1.3 +MLINKS+= cuse.3 cuse_dev_set_per_file_handle.3 +MLINKS+= cuse.3 cuse_dev_set_priv0.3 +MLINKS+= cuse.3 cuse_dev_set_priv1.3 +MLINKS+= cuse.3 cuse_free_unit_number.3 +MLINKS+= cuse.3 cuse_free_unit_number_by_id.3 +MLINKS+= cuse.3 cuse_got_peer_signal.3 +MLINKS+= cuse.3 cuse_init.3 +MLINKS+= cuse.3 cuse_poll_wakeup.3 +MLINKS+= cuse.3 cuse_set_local.3 +MLINKS+= cuse.3 cuse_get_local.3 +MLINKS+= cuse.3 cuse_uninit.3 +MLINKS+= cuse.3 cuse_vmalloc.3 +MLINKS+= cuse.3 cuse_is_vmalloc_addr.3 +MLINKS+= cuse.3 cuse_vmfree.3 +MLINKS+= cuse.3 cuse_vmoffset.3 +MLINKS+= cuse.3 cuse_wait_and_process.3 + +.include <bsd.lib.mk> diff --git a/lib/libcuse/Makefile.depend b/lib/libcuse/Makefile.depend new file mode 100644 index 000000000000..577dc5747f1e --- /dev/null +++ b/lib/libcuse/Makefile.depend @@ -0,0 +1,16 @@ +# Autogenerated - do NOT edit! + +DIRDEPS = \ + include \ + include/xlocale \ + lib/${CSU_DIR} \ + lib/libc \ + lib/libcompiler_rt \ + lib/libthr \ + + +.include <dirdeps.mk> + +.if ${DEP_RELDIR} == ${_DEP_RELDIR} +# local dependencies - needed for -jN in clean tree +.endif diff --git a/lib/libcuse/cuse.3 b/lib/libcuse/cuse.3 new file mode 100644 index 000000000000..ef8ec56760cb --- /dev/null +++ b/lib/libcuse/cuse.3 @@ -0,0 +1,391 @@ +.\" +.\" Copyright (c) 2010-2022 Hans Petter Selasky +.\" +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.Dd July 18, 2022 +.Dt CUSE 3 +.Os +.Sh NAME +.Nm libcuse +.Nd "Userland character device library" +.Sh LIBRARY +.Lb libcuse +.Sh SYNOPSIS +To load the required kernel module at boot time, place the following line in +.Xr loader.conf 5 : +.Bd -literal -offset indent +cuse_load="YES" +.Ed +.Pp +.In cuse.h +.Sh DESCRIPTION +The +.Nm +library contains functions to create a character device in userspace. +The +.Nm +library is thread safe. +.Sh LIBRARY INITIALISATION / DEINITIALISATION +.Ft "int" +.Fn "cuse_init" "void" +This function initialises +.Nm . +Must be called at the beginning of the program. +This function returns 0 on success or a negative value on failure. +See +.Dv CUSE_ERR_XXX +for known error codes. +If the cuse kernel module is not loaded, +.Dv CUSE_ERR_NOT_LOADED +is returned. +.Pp +.Ft "int" +.Fn "cuse_uninit" "void" +Deinitialise +.Nm . +Can be called at the end of the application. +This function returns 0 on success or a negative value on failure. +See +.Dv CUSE_ERR_XXX +for known error codes. +.Sh UNIT MANAGEMENT +.Ft "int" +.Fn "cuse_alloc_unit_number" "int *" +This function stores a uniq system unit number at the pointed +integer loation. +This function returns 0 on success or a negative value on failure. +See +.Dv CUSE_ERR_XXX +for known error codes. +.Pp +.Ft "int" +.Fn "cuse_alloc_unit_number_by_id" "int *" "int id" +This function stores a unique system unit number at the pointed +integer loation. +The returned unit number is uniq within the given ID. +Valid ID values are defined by the cuse include file. +See the +.Fn CUSE_ID_XXX +macros for more information. +This function returns 0 on success or a negative value on failure. +See +.Dv CUSE_ERR_XXX +for known error codes. +.Pp +.Ft "int" +.Fn "cuse_free_unit_number" "int" +This function frees the given allocated system unit number. +This function returns 0 on success or a negative value on failure. +See +.Dv CUSE_ERR_XXX +for known error codes. +.Pp +.Ft "int" +.Fn "cuse_free_unit_number_by_id" "int unit" "int id" +This function frees the given allocated system unit number belonging +to the given ID. +If both the unit and id argument is -1, all allocated units will be freed. +This function returns 0 on success or a negative value on failure. +See +.Dv CUSE_ERR_XXX +for known error codes. +.Sh LIBRARY USAGE +.Ft "void *" +.Fn "cuse_vmalloc" "unsigned size" +This function allocates +.Ar size +bytes of memory. +Only memory allocated by this function can be memory +mapped by +.Xr mmap 2 . +This function returns a valid data pointer on success or +.Dv NULL +on failure. +The returned pointer is always aligned to the system page size. +The number and size of allocations is limited by the +.Xr mmap 2 +offset having to fit into a 32-bit variable typically for 32-bit +applications. +.Pp +.Ft "int" +.Fn "cuse_is_vmalloc_addr" "void *" +This function returns non-zero if the passed pointer points to a valid +and non-freed allocation, as returned by +.Fn cuse_vmalloc . +Else this function returns zero. +.Pp +.Ft "void" +.Fn "cuse_vmfree" "void *" +This function frees memory allocated by +.Fn cuse_vmalloc . +This function is NULL safe. +.Pp +.Ft "unsigned long" +.Fn "cuse_vmoffset" "void *" +This function returns the mmap offset the client must use to +access the allocated memory. +The passed pointer must be aligned to the system page size. +.Pp +.Ft "struct cuse_dev *" +.Fn "cuse_dev_create" "const struct cuse_methods *mtod" "void *priv0" "void *priv1" "uid_t" "gid_t" "int permission" "const char *fmt" "..." +This function creates a new character device according to the given +parameters. +This function returns a valid cuse_dev structure pointer +on success or +.Dv NULL +on failure. +The device name can only contain a-z, +A-Z, 0-9, dot, / and underscore characters. +.Pp +.Ft "void" +.Fn "cuse_dev_destroy" "struct cuse_dev *" +This functions destroys a previously created character device. +.Pp +.Ft "void *" +.Fn "cuse_dev_get_priv0" "struct cuse_dev *" , +.Ft "void *" +.Fn "cuse_dev_get_priv1" "struct cuse_dev *" , +.Ft "void" +.Fn "cuse_dev_set_priv0" "struct cuse_dev *" "void *" , +.Ft "void" +.Fn "cuse_dev_set_priv1" "struct cuse_dev *" "void *" +These functions are used to set and get the private data of the given +cuse device. +.Pp +.Ft "int" +.Fn "cuse_wait_and_process" "void" +This function will block and do event processing. +If parallel I/O is +required multiple threads must be created looping on this +function. +This function returns 0 on success or a negative value on failure. +See +.Dv CUSE_ERR_XXX +for known error codes. +.Pp +.Ft "void *" +.Fn "cuse_dev_get_per_file_handle" "struct cuse_dev *" , +.Ft "void" +.Fn "cuse_dev_set_per_file_handle" "struct cuse_dev *" "void *" +These functions are used to set and get the per-file-open specific handle +and should only be used inside the cuse file operation callbacks. +.Pp +.Ft "void" +.Fn "cuse_set_local" "int" +This function instructs +.Fn cuse_copy_out +and +.Fn cuse_copy_in +that the +user pointer is local, if the argument passed to it is non-zero. +Else the user pointer is assumed to be at the peer application. +This function should only be used inside the cuse file operation callbacks. +The value is reset to zero when the given file operation returns, and +does not affect any other file operation callbacks. +.Pp +.Ft "int" +.Fn "cuse_get_local" "void" +Returns the current local state. +See +.Fn cuse_set_local . +.Pp +.Ft "int" +.Fn "cuse_copy_out" "const void *src" "void *peer_dst" "int len" , +.Ft "int" +.Fn "cuse_copy_in" "const void *peer_src" "void *dst" "int len" +These functions are used to transfer data between the local +application and the peer application. +These functions must be used +when operating on the data pointers passed to the +.Fn cm_read , +.Fn cm_write , +and +.Fn cm_ioctl +callback functions. +These functions return 0 on success or a negative value on failure. +See +.Dv CUSE_ERR_XXX +for known error codes. +.Pp +.Ft "int" +.Fn "cuse_got_peer_signal" "void" +This function is used to check if a signal has been delivered to the +peer application and should only be used inside the cuse file +operation callbacks. +This function returns 0 if a signal has been +delivered to the caller. +Else it returns a negative value. +See +.Dv CUSE_ERR_XXX +for known error codes. +.Pp +.Ft "struct cuse_dev *" +.Fn "cuse_dev_get_current" "int *pcmd" +This function is used to get the current cuse device pointer and the +currently executing command, by +.Dv CUSE_CMD_XXX +value. +The +.Ar pcmd +argument +is allowed to be +.Dv NULL . +This function should only be used inside the +cuse file operation callbacks. +On success a valid cuse device pointer +is returned. +On failure +.Dv NULL +is returned. +.Pp +.Ft "void" +.Fn "cuse_poll_wakeup" "void" +This function will wake up any file pollers. +.Sh LIBRARY LIMITATIONS +Transfer lengths for +.Fn read , +.Fn write , +.Fn cuse_copy_in , +and +.Fn cuse_copy_out +should not exceed what can fit into a 32-bit signed integer and is +defined by the +.Fn CUSE_LENGTH_MAX +macro. +Transfer lengths for ioctls should not exceed what is defined by the +.Fn CUSE_BUFFER_MAX +macro. +.Sh LIBRARY CALLBACK METHODS +In general fflags are defined by +.Dv CUSE_FFLAG_XXX +and errors are defined by +.Dv CUSE_ERR_XXX . +.Bd -literal -offset indent +enum { + CUSE_ERR_NONE + CUSE_ERR_BUSY + CUSE_ERR_WOULDBLOCK + CUSE_ERR_INVALID + CUSE_ERR_NO_MEMORY + CUSE_ERR_FAULT + CUSE_ERR_SIGNAL + CUSE_ERR_OTHER + CUSE_ERR_NOT_LOADED + CUSE_ERR_NO_DEVICE + + CUSE_POLL_NONE + CUSE_POLL_READ + CUSE_POLL_WRITE + CUSE_POLL_ERROR + + CUSE_FFLAG_NONE + CUSE_FFLAG_READ + CUSE_FFLAG_WRITE + CUSE_FFLAG_NONBLOCK + CUSE_FFLAG_COMPAT32 + + CUSE_CMD_NONE + CUSE_CMD_OPEN + CUSE_CMD_CLOSE + CUSE_CMD_READ + CUSE_CMD_WRITE + CUSE_CMD_IOCTL + CUSE_CMD_POLL + CUSE_CMD_SIGNAL + CUSE_CMD_SYNC + CUSE_CMD_MAX +}; +.Ed +.Pp +.Ft "int" +.Fn "cuse_open_t" "struct cuse_dev *" "int fflags" +This function returns a +.Dv CUSE_ERR_XXX +value. +.Pp +.Ft "int" +.Fn "cuse_close_t" "struct cuse_dev *" "int fflags" +This function returns a +.Dv CUSE_ERR_XXX +value. +.Pp +.Ft "int" +.Fn "cuse_read_t" "struct cuse_dev *" "int fflags" "void *peer_ptr" "int len" +This function returns a +.Dv CUSE_ERR_XXX +value in case of failure or the +actually transferred length in case of success. +.Fn cuse_copy_in +and +.Fn cuse_copy_out +must be used to transfer data to and from the +.Ar peer_ptr . +.Pp +.Ft "int" +.Fn "cuse_write_t" "struct cuse_dev *" "int fflags" "const void *peer_ptr" "int len" +This function returns a +.Dv CUSE_ERR_XXX +value in case of failure or the +actually transferred length in case of success. +.Fn cuse_copy_in +and +.Fn cuse_copy_out +must be used to transfer data to and from the +.Ar peer_ptr . +.Pp +.Ft "int" +.Fn "cuse_ioctl_t" "struct cuse_dev *" "int fflags" "unsigned long cmd" "void *peer_data" +This function returns a +.Dv CUSE_ERR_XXX +value in case of failure or zero +in case of success. +.Fn cuse_copy_in +and +.Fn cuse_copy_out +must be used to +transfer data to and from the +.Ar peer_data . +.Pp +.Ft "int" +.Fn "cuse_poll_t" "struct cuse_dev *" "int fflags" "int events" +This function returns a mask of +.Dv CUSE_POLL_XXX +values in case of failure and success. +The events argument is also a mask of +.Dv CUSE_POLL_XXX +values. +.Bd -literal -offset indent +struct cuse_methods { + cuse_open_t *cm_open; + cuse_close_t *cm_close; + cuse_read_t *cm_read; + cuse_write_t *cm_write; + cuse_ioctl_t *cm_ioctl; + cuse_poll_t *cm_poll; +}; +.Ed +.Sh HISTORY +.Nm +was written by Hans Petter Selasky. diff --git a/lib/libcuse/cuse.h b/lib/libcuse/cuse.h new file mode 100644 index 000000000000..acc19c2ca663 --- /dev/null +++ b/lib/libcuse/cuse.h @@ -0,0 +1,96 @@ +/*- + * Copyright (c) 2014 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _CUSE_H_ +#define _CUSE_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include <fs/cuse/cuse_defs.h> + +struct cuse_dev; + +typedef int (cuse_open_t)(struct cuse_dev *, int fflags); +typedef int (cuse_close_t)(struct cuse_dev *, int fflags); +typedef int (cuse_read_t)(struct cuse_dev *, int fflags, void *user_ptr, int len); +typedef int (cuse_write_t)(struct cuse_dev *, int fflags, const void *user_ptr, int len); +typedef int (cuse_ioctl_t)(struct cuse_dev *, int fflags, unsigned long cmd, void *user_data); +typedef int (cuse_poll_t)(struct cuse_dev *, int fflags, int events); + +struct cuse_methods { + cuse_open_t *cm_open; + cuse_close_t *cm_close; + cuse_read_t *cm_read; + cuse_write_t *cm_write; + cuse_ioctl_t *cm_ioctl; + cuse_poll_t *cm_poll; +}; + +int cuse_init(void); +int cuse_uninit(void); + +void *cuse_vmalloc(unsigned); +int cuse_is_vmalloc_addr(void *); +void cuse_vmfree(void *); +unsigned long cuse_vmoffset(void *ptr); + +int cuse_alloc_unit_number_by_id(int *, int); +int cuse_free_unit_number_by_id(int, int); +int cuse_alloc_unit_number(int *); +int cuse_free_unit_number(int); + +struct cuse_dev *cuse_dev_create(const struct cuse_methods *, void *, void *, uid_t, gid_t, int, const char *,...); +void cuse_dev_destroy(struct cuse_dev *); + +void *cuse_dev_get_priv0(struct cuse_dev *); +void *cuse_dev_get_priv1(struct cuse_dev *); + +void cuse_dev_set_priv0(struct cuse_dev *, void *); +void cuse_dev_set_priv1(struct cuse_dev *, void *); + +void cuse_set_local(int); +int cuse_get_local(void); + +int cuse_wait_and_process(void); + +void cuse_dev_set_per_file_handle(struct cuse_dev *, void *); +void *cuse_dev_get_per_file_handle(struct cuse_dev *); + +int cuse_copy_out(const void *src, void *user_dst, int len); +int cuse_copy_in(const void *user_src, void *dst, int len); +int cuse_got_peer_signal(void); +void cuse_poll_wakeup(void); + +struct cuse_dev *cuse_dev_get_current(int *); + +extern int cuse_debug_level; + +#ifdef __cplusplus +} +#endif + +#endif /* _CUSE_H_ */ diff --git a/lib/libcuse/cuse_lib.c b/lib/libcuse/cuse_lib.c new file mode 100644 index 000000000000..3f040d0eeeda --- /dev/null +++ b/lib/libcuse/cuse_lib.c @@ -0,0 +1,794 @@ +/*- + * Copyright (c) 2010-2022 Hans Petter Selasky. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <stdio.h> +#include <stdint.h> +#include <pthread.h> +#include <signal.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <stdlib.h> +#include <stdarg.h> + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/fcntl.h> +#include <sys/mman.h> +#include <sys/param.h> + +#include <fs/cuse/cuse_ioctl.h> + +#include "cuse.h" + +int cuse_debug_level; + +#ifdef HAVE_DEBUG +static const char *cuse_cmd_str(int cmd); + +#define DPRINTF(...) do { \ + if (cuse_debug_level != 0) \ + printf(__VA_ARGS__); \ +} while (0) +#else +#define DPRINTF(...) do { } while (0) +#endif + +struct cuse_vm_allocation { + uint8_t *ptr; + uint32_t size; +}; + +struct cuse_dev_entered { + TAILQ_ENTRY(cuse_dev_entered) entry; + pthread_t thread; + void *per_file_handle; + struct cuse_dev *cdev; + int cmd; + int is_local; + int got_signal; +}; + +struct cuse_dev { + TAILQ_ENTRY(cuse_dev) entry; + const struct cuse_methods *mtod; + void *priv0; + void *priv1; +}; + +static int f_cuse = -1; + +static pthread_mutex_t m_cuse; +static TAILQ_HEAD(, cuse_dev) h_cuse __guarded_by(m_cuse); +static TAILQ_HEAD(, cuse_dev_entered) h_cuse_entered __guarded_by(m_cuse); +static struct cuse_vm_allocation a_cuse[CUSE_ALLOC_UNIT_MAX] + __guarded_by(m_cuse); + +#define CUSE_LOCK() \ + pthread_mutex_lock(&m_cuse) + +#define CUSE_UNLOCK() \ + pthread_mutex_unlock(&m_cuse) + +int +cuse_init(void) +{ + pthread_mutexattr_t attr; + + f_cuse = open("/dev/cuse", O_RDWR); + if (f_cuse < 0) { + if (feature_present("cuse") == 0) + return (CUSE_ERR_NOT_LOADED); + else + return (CUSE_ERR_INVALID); + } + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + pthread_mutex_init(&m_cuse, &attr); + + TAILQ_INIT(&h_cuse); + TAILQ_INIT(&h_cuse_entered); + + return (0); +} + +int +cuse_uninit(void) +{ + int f; + + if (f_cuse < 0) + return (CUSE_ERR_INVALID); + + f = f_cuse; + f_cuse = -1; + + close(f); + + pthread_mutex_destroy(&m_cuse); + + memset(a_cuse, 0, sizeof(a_cuse)); + + return (0); +} + +unsigned long +cuse_vmoffset(void *_ptr) +{ + uint8_t *ptr_min; + uint8_t *ptr_max; + uint8_t *ptr = _ptr; + unsigned long remainder; + unsigned long n; + + CUSE_LOCK(); + for (n = remainder = 0; n != CUSE_ALLOC_UNIT_MAX; n++) { + if (a_cuse[n].ptr == NULL) + continue; + + ptr_min = a_cuse[n].ptr; + ptr_max = a_cuse[n].ptr + a_cuse[n].size - 1; + + if ((ptr >= ptr_min) && (ptr <= ptr_max)) { + remainder = (ptr - ptr_min); + break; + } + } + CUSE_UNLOCK(); + + return ((n << CUSE_ALLOC_UNIT_SHIFT) + remainder); +} + +void * +cuse_vmalloc(unsigned size) +{ + struct cuse_alloc_info info; + unsigned long pgsize; + unsigned long x; + unsigned long m; + unsigned long n; + void *ptr; + int error; + + /* some sanity checks */ + if (f_cuse < 0 || size < 1 || size > CUSE_ALLOC_BYTES_MAX) + return (NULL); + + memset(&info, 0, sizeof(info)); + + pgsize = getpagesize(); + info.page_count = howmany(size, pgsize); + + /* compute how many units the allocation needs */ + m = howmany(size, 1 << CUSE_ALLOC_UNIT_SHIFT); + if (m == 0 || m > CUSE_ALLOC_UNIT_MAX) + return (NULL); + + CUSE_LOCK(); + for (n = 0; n <= CUSE_ALLOC_UNIT_MAX - m; ) { + if (a_cuse[n].size != 0) { + /* skip to next available unit, depending on allocation size */ + n += howmany(a_cuse[n].size, 1 << CUSE_ALLOC_UNIT_SHIFT); + continue; + } + /* check if there are "m" free units ahead */ + for (x = 1; x != m; x++) { + if (a_cuse[n + x].size != 0) + break; + } + if (x != m) { + /* skip to next available unit, if any */ + n += x + 1; + continue; + } + /* reserve this unit by setting the size to a non-zero value */ + a_cuse[n].size = size; + CUSE_UNLOCK(); + + info.alloc_nr = n; + + error = ioctl(f_cuse, CUSE_IOCTL_ALLOC_MEMORY, &info); + + if (error == 0) { + ptr = mmap(NULL, info.page_count * pgsize, + PROT_READ | PROT_WRITE, + MAP_SHARED, f_cuse, n << CUSE_ALLOC_UNIT_SHIFT); + + if (ptr != MAP_FAILED) { + CUSE_LOCK(); + a_cuse[n].ptr = ptr; + CUSE_UNLOCK(); + + return (ptr); /* success */ + } + + (void) ioctl(f_cuse, CUSE_IOCTL_FREE_MEMORY, &info); + } + + CUSE_LOCK(); + a_cuse[n].size = 0; + n++; + } + CUSE_UNLOCK(); + return (NULL); /* failure */ +} + +int +cuse_is_vmalloc_addr(void *ptr) +{ + int n; + + if (f_cuse < 0 || ptr == NULL) + return (0); /* false */ + + CUSE_LOCK(); + for (n = 0; n != CUSE_ALLOC_UNIT_MAX; n++) { + if (a_cuse[n].ptr == ptr) + break; + } + CUSE_UNLOCK(); + + return (n != CUSE_ALLOC_UNIT_MAX); +} + +void +cuse_vmfree(void *ptr) +{ + struct cuse_vm_allocation temp; + struct cuse_alloc_info info; + int error; + int n; + + if (f_cuse < 0 || ptr == NULL) + return; + + CUSE_LOCK(); + for (n = 0; n != CUSE_ALLOC_UNIT_MAX; n++) { + if (a_cuse[n].ptr != ptr) + continue; + + temp = a_cuse[n]; + + CUSE_UNLOCK(); + + munmap(temp.ptr, temp.size); + + memset(&info, 0, sizeof(info)); + + info.alloc_nr = n; + + error = ioctl(f_cuse, CUSE_IOCTL_FREE_MEMORY, &info); + + if (error != 0) { + /* ignore any errors */ + DPRINTF("Freeing memory failed: %d\n", errno); + } + CUSE_LOCK(); + + a_cuse[n].ptr = NULL; + a_cuse[n].size = 0; + + break; + } + CUSE_UNLOCK(); +} + +int +cuse_alloc_unit_number_by_id(int *pnum, int id) +{ + int error; + + if (f_cuse < 0) + return (CUSE_ERR_INVALID); + + *pnum = (id & CUSE_ID_MASK); + + error = ioctl(f_cuse, CUSE_IOCTL_ALLOC_UNIT_BY_ID, pnum); + if (error) + return (CUSE_ERR_NO_MEMORY); + + return (0); + +} + +int +cuse_free_unit_number_by_id(int num, int id) +{ + int error; + + if (f_cuse < 0) + return (CUSE_ERR_INVALID); + + if (num != -1 || id != -1) + num = (id & CUSE_ID_MASK) | (num & 0xFF); + + error = ioctl(f_cuse, CUSE_IOCTL_FREE_UNIT_BY_ID, &num); + if (error) + return (CUSE_ERR_NO_MEMORY); + + return (0); +} + +int +cuse_alloc_unit_number(int *pnum) +{ + int error; + + if (f_cuse < 0) + return (CUSE_ERR_INVALID); + + error = ioctl(f_cuse, CUSE_IOCTL_ALLOC_UNIT, pnum); + if (error) + return (CUSE_ERR_NO_MEMORY); + + return (0); +} + +int +cuse_free_unit_number(int num) +{ + int error; + + if (f_cuse < 0) + return (CUSE_ERR_INVALID); + + error = ioctl(f_cuse, CUSE_IOCTL_FREE_UNIT, &num); + if (error) + return (CUSE_ERR_NO_MEMORY); + + return (0); +} + +struct cuse_dev * +cuse_dev_create(const struct cuse_methods *mtod, void *priv0, void *priv1, + uid_t _uid, gid_t _gid, int _perms, const char *_fmt,...) +{ + struct cuse_create_dev info; + struct cuse_dev *cdev; + va_list args; + int error; + + if (f_cuse < 0) + return (NULL); + + cdev = malloc(sizeof(*cdev)); + if (cdev == NULL) + return (NULL); + + memset(cdev, 0, sizeof(*cdev)); + + cdev->mtod = mtod; + cdev->priv0 = priv0; + cdev->priv1 = priv1; + + memset(&info, 0, sizeof(info)); + + info.dev = cdev; + info.user_id = _uid; + info.group_id = _gid; + info.permissions = _perms; + + va_start(args, _fmt); + vsnprintf(info.devname, sizeof(info.devname), _fmt, args); + va_end(args); + + error = ioctl(f_cuse, CUSE_IOCTL_CREATE_DEV, &info); + if (error) { + free(cdev); + return (NULL); + } + CUSE_LOCK(); + TAILQ_INSERT_TAIL(&h_cuse, cdev, entry); + CUSE_UNLOCK(); + + return (cdev); +} + + +void +cuse_dev_destroy(struct cuse_dev *cdev) +{ + int error; + + if (f_cuse < 0) + return; + + CUSE_LOCK(); + TAILQ_REMOVE(&h_cuse, cdev, entry); + CUSE_UNLOCK(); + + error = ioctl(f_cuse, CUSE_IOCTL_DESTROY_DEV, &cdev); + if (error) + return; + + free(cdev); +} + +void * +cuse_dev_get_priv0(struct cuse_dev *cdev) +{ + return (cdev->priv0); +} + +void * +cuse_dev_get_priv1(struct cuse_dev *cdev) +{ + return (cdev->priv1); +} + +void +cuse_dev_set_priv0(struct cuse_dev *cdev, void *priv) +{ + cdev->priv0 = priv; +} + +void +cuse_dev_set_priv1(struct cuse_dev *cdev, void *priv) +{ + cdev->priv1 = priv; +} + +int +cuse_wait_and_process(void) +{ + pthread_t curr = pthread_self(); + struct cuse_dev_entered *pe; + struct cuse_dev_entered enter; + struct cuse_command info; + struct cuse_dev *cdev; + int error; + + if (f_cuse < 0) + return (CUSE_ERR_INVALID); + + error = ioctl(f_cuse, CUSE_IOCTL_GET_COMMAND, &info); + if (error) + return (CUSE_ERR_OTHER); + + cdev = info.dev; + + CUSE_LOCK(); + enter.thread = curr; + enter.per_file_handle = (void *)info.per_file_handle; + enter.cmd = info.command; + enter.is_local = 0; + enter.got_signal = 0; + enter.cdev = cdev; + TAILQ_INSERT_TAIL(&h_cuse_entered, &enter, entry); + CUSE_UNLOCK(); + + DPRINTF("cuse: Command = %d = %s, flags = %d, arg = 0x%08x, ptr = 0x%08x\n", + (int)info.command, cuse_cmd_str(info.command), (int)info.fflags, + (int)info.argument, (int)info.data_pointer); + + switch (info.command) { + case CUSE_CMD_OPEN: + if (cdev->mtod->cm_open != NULL) + error = (cdev->mtod->cm_open) (cdev, (int)info.fflags); + else + error = 0; + break; + + case CUSE_CMD_CLOSE: + + /* wait for other threads to stop */ + + while (1) { + + error = 0; + + CUSE_LOCK(); + TAILQ_FOREACH(pe, &h_cuse_entered, entry) { + if (pe->cdev != cdev) + continue; + if (pe->thread == curr) + continue; + if (pe->per_file_handle != + enter.per_file_handle) + continue; + pe->got_signal = 1; + pthread_kill(pe->thread, SIGHUP); + error = CUSE_ERR_BUSY; + } + CUSE_UNLOCK(); + + if (error == 0) + break; + else + usleep(10000); + } + + if (cdev->mtod->cm_close != NULL) + error = (cdev->mtod->cm_close) (cdev, (int)info.fflags); + else + error = 0; + break; + + case CUSE_CMD_READ: + if (cdev->mtod->cm_read != NULL) { + error = (cdev->mtod->cm_read) (cdev, (int)info.fflags, + (void *)info.data_pointer, (int)info.argument); + } else { + error = CUSE_ERR_INVALID; + } + break; + + case CUSE_CMD_WRITE: + if (cdev->mtod->cm_write != NULL) { + error = (cdev->mtod->cm_write) (cdev, (int)info.fflags, + (void *)info.data_pointer, (int)info.argument); + } else { + error = CUSE_ERR_INVALID; + } + break; + + case CUSE_CMD_IOCTL: + if (cdev->mtod->cm_ioctl != NULL) { + error = (cdev->mtod->cm_ioctl) (cdev, (int)info.fflags, + (unsigned int)info.argument, (void *)info.data_pointer); + } else { + error = CUSE_ERR_INVALID; + } + break; + + case CUSE_CMD_POLL: + if (cdev->mtod->cm_poll != NULL) { + error = (cdev->mtod->cm_poll) (cdev, (int)info.fflags, + (int)info.argument); + } else { + error = CUSE_POLL_ERROR; + } + break; + + case CUSE_CMD_SIGNAL: + CUSE_LOCK(); + TAILQ_FOREACH(pe, &h_cuse_entered, entry) { + if (pe->cdev != cdev) + continue; + if (pe->thread == curr) + continue; + if (pe->per_file_handle != + enter.per_file_handle) + continue; + pe->got_signal = 1; + pthread_kill(pe->thread, SIGHUP); + } + CUSE_UNLOCK(); + break; + + default: + error = CUSE_ERR_INVALID; + break; + } + + DPRINTF("cuse: Command error = %d for %s\n", + error, cuse_cmd_str(info.command)); + + CUSE_LOCK(); + TAILQ_REMOVE(&h_cuse_entered, &enter, entry); + CUSE_UNLOCK(); + + /* we ignore any sync command failures */ + ioctl(f_cuse, CUSE_IOCTL_SYNC_COMMAND, &error); + + return (0); +} + +static struct cuse_dev_entered * +cuse_dev_get_entered(void) +{ + struct cuse_dev_entered *pe; + pthread_t curr = pthread_self(); + + CUSE_LOCK(); + TAILQ_FOREACH(pe, &h_cuse_entered, entry) { + if (pe->thread == curr) + break; + } + CUSE_UNLOCK(); + return (pe); +} + +void +cuse_dev_set_per_file_handle(struct cuse_dev *cdev, void *handle) +{ + struct cuse_dev_entered *pe; + + pe = cuse_dev_get_entered(); + if (pe == NULL || pe->cdev != cdev) + return; + + pe->per_file_handle = handle; + ioctl(f_cuse, CUSE_IOCTL_SET_PFH, &handle); +} + +void * +cuse_dev_get_per_file_handle(struct cuse_dev *cdev) +{ + struct cuse_dev_entered *pe; + + pe = cuse_dev_get_entered(); + if (pe == NULL || pe->cdev != cdev) + return (NULL); + + return (pe->per_file_handle); +} + +void +cuse_set_local(int val) +{ + struct cuse_dev_entered *pe; + + pe = cuse_dev_get_entered(); + if (pe == NULL) + return; + + pe->is_local = val; +} + +#ifdef HAVE_DEBUG +static const char * +cuse_cmd_str(int cmd) +{ + static const char *str[CUSE_CMD_MAX] = { + [CUSE_CMD_NONE] = "none", + [CUSE_CMD_OPEN] = "open", + [CUSE_CMD_CLOSE] = "close", + [CUSE_CMD_READ] = "read", + [CUSE_CMD_WRITE] = "write", + [CUSE_CMD_IOCTL] = "ioctl", + [CUSE_CMD_POLL] = "poll", + [CUSE_CMD_SIGNAL] = "signal", + [CUSE_CMD_SYNC] = "sync", + }; + + if ((cmd >= 0) && (cmd < CUSE_CMD_MAX) && + (str[cmd] != NULL)) + return (str[cmd]); + else + return ("unknown"); +} + +#endif + +int +cuse_get_local(void) +{ + struct cuse_dev_entered *pe; + + pe = cuse_dev_get_entered(); + if (pe == NULL) + return (0); + + return (pe->is_local); +} + +int +cuse_copy_out(const void *src, void *user_dst, int len) +{ + struct cuse_data_chunk info; + struct cuse_dev_entered *pe; + int error; + + if ((f_cuse < 0) || (len < 0)) + return (CUSE_ERR_INVALID); + + pe = cuse_dev_get_entered(); + if (pe == NULL) + return (CUSE_ERR_INVALID); + + DPRINTF("cuse: copy_out(%p,%p,%d), cmd = %d = %s\n", + src, user_dst, len, pe->cmd, cuse_cmd_str(pe->cmd)); + + if (pe->is_local) { + memcpy(user_dst, src, len); + } else { + info.local_ptr = (uintptr_t)src; + info.peer_ptr = (uintptr_t)user_dst; + info.length = len; + + error = ioctl(f_cuse, CUSE_IOCTL_WRITE_DATA, &info); + if (error) { + DPRINTF("cuse: copy_out() error = %d\n", errno); + return (CUSE_ERR_FAULT); + } + } + return (0); +} + +int +cuse_copy_in(const void *user_src, void *dst, int len) +{ + struct cuse_data_chunk info; + struct cuse_dev_entered *pe; + int error; + + if ((f_cuse < 0) || (len < 0)) + return (CUSE_ERR_INVALID); + + pe = cuse_dev_get_entered(); + if (pe == NULL) + return (CUSE_ERR_INVALID); + + DPRINTF("cuse: copy_in(%p,%p,%d), cmd = %d = %s\n", + user_src, dst, len, pe->cmd, cuse_cmd_str(pe->cmd)); + + if (pe->is_local) { + memcpy(dst, user_src, len); + } else { + info.local_ptr = (uintptr_t)dst; + info.peer_ptr = (uintptr_t)user_src; + info.length = len; + + error = ioctl(f_cuse, CUSE_IOCTL_READ_DATA, &info); + if (error) { + DPRINTF("cuse: copy_in() error = %d\n", errno); + return (CUSE_ERR_FAULT); + } + } + return (0); +} + +struct cuse_dev * +cuse_dev_get_current(int *pcmd) +{ + struct cuse_dev_entered *pe; + + pe = cuse_dev_get_entered(); + if (pe == NULL) { + if (pcmd != NULL) + *pcmd = 0; + return (NULL); + } + if (pcmd != NULL) + *pcmd = pe->cmd; + return (pe->cdev); +} + +int +cuse_got_peer_signal(void) +{ + struct cuse_dev_entered *pe; + + pe = cuse_dev_get_entered(); + if (pe == NULL) + return (CUSE_ERR_INVALID); + + if (pe->got_signal) + return (0); + + return (CUSE_ERR_OTHER); +} + +void +cuse_poll_wakeup(void) +{ + int error = 0; + + if (f_cuse < 0) + return; + + ioctl(f_cuse, CUSE_IOCTL_SELWAKEUP, &error); +} |