diff --git a/etc/mtree/BSD.include.dist b/etc/mtree/BSD.include.dist index 7436eace4b90..bdd628cf0532 100644 --- a/etc/mtree/BSD.include.dist +++ b/etc/mtree/BSD.include.dist @@ -162,6 +162,8 @@ .. .. fs + cuse + .. devfs .. fdescfs diff --git a/include/Makefile b/include/Makefile index 737575fb1aef..4693c2f87438 100644 --- a/include/Makefile +++ b/include/Makefile @@ -45,6 +45,7 @@ LSUBDIRS= cam/ata cam/scsi \ dev/ic dev/iicbus ${_dev_ieee488} dev/io dev/lmc dev/mfi dev/nvme \ dev/ofw dev/pbio dev/pci ${_dev_powermac_nvram} dev/ppbus dev/smbus \ dev/speaker dev/usb dev/utopia dev/vkbd dev/wi \ + fs/cuse \ fs/devfs fs/fdescfs fs/msdosfs fs/nandfs fs/nfs fs/nullfs \ fs/procfs fs/smbfs fs/udf fs/unionfs \ geom/cache geom/concat geom/eli geom/gate geom/journal geom/label \ diff --git a/lib/libcuse/Makefile b/lib/libcuse/Makefile new file mode 100644 index 000000000000..db28c22293a9 --- /dev/null +++ b/lib/libcuse/Makefile @@ -0,0 +1,64 @@ +# $FreeBSD$ +# +# 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. +# + +LIB= cuse +SHLIB_MAJOR= 1 +SHLIB_MINOR= 0 +SRCS= cuse_lib.c +INCS= cuse.h +MAN= cuse.3 +PTHREAD_LIBS?= -lpthread +CFLAGS+= -D_GNU_SOURCE +.if defined(HAVE_DEBUG) +CFLAGS+= -g +CFLAGS+= -DHAVE_DEBUG +.endif +LDADD+= ${PTHREAD_LIBS} + +MLINKS= +MLINKS+= cuse.3 cuse_alloc_unit_number.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_free_unit_number.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_wait_and_process.3 + +.include diff --git a/lib/libcuse/cuse.3 b/lib/libcuse/cuse.3 new file mode 100644 index 000000000000..635af2c15fd3 --- /dev/null +++ b/lib/libcuse/cuse.3 @@ -0,0 +1,393 @@ +.\" $FreeBSD$ +.\" +.\" Copyright (c) 2010-2013 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 May 23, 2014 +.Dt CUSE 3 +.Os +.Sh NAME +.Nm libcuse +. +.Nd "Userland character device library" +. +. +.Sh LIBRARY +. +. +Userland character device library (libcuse -lcuse) +. +. +.Sh SYNOPSIS +. +.Pp +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 +. +.Pp +. +.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 CUSE_ERR_XXX for known error codes. +If the cuse kernel module is not loaded, 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 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 CUSE_ERR_XXX for known error codes. +. +.Pp +. +.Ft "int" +.Fn "cuse_alloc_unit_number_by_id" "int *" "int id" +This function stores a uniq 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 CUSE_ID_XXX() macros for more information. +This function returns 0 on success or a negative value on failure. +See 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 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 CUSE_ERR_XXX for known error codes. +. +. +.Sh LIBRARY USAGE +. +. +.Ft "void *" +.Fn "cuse_vmalloc" "int size" +This function allocates +.Ar size +bytes of memory. Only memory allocated by this function can be memory +mapped by mmap(). This function returns a valid data pointer on success or +NULL on failure. +. +.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 "cuse_vmalloc()". +Else this function returns zero. +. +.Pp +. +.Ft "void" +.Fn "cuse_vmfree" "void *" +This function frees memory allocated by cuse_vmalloc(). Note that the +cuse library will internally not free the memory until the +cuse_uninit() function is called and that the number of uniq +allocations is limited. +. +. +.Pp +. +.Ft "unsigned long" +.Fn "cuse_vmoffset" "void *" +This function returns the mmap offset that the client must use to +access the allocated memory. +. +.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 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 parallell 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 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 cuse_copy_out() and 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" +Return current local state. See "cuse_set_local" function. +. +.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 cm_read(), +cm_write() and cm_ioctl() callback functions. +These functions return 0 on success or a negative value on failure. +See 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 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 CUSE_CMD_XXX value. The pcmd argument +is allowed to be NULL. This function should only be used inside the +cuse file operation callbacks. On success a valid cuse device pointer +is returned. On failure NULL is returned. +. +.Pp +. +.Ft "void" +.Fn "cuse_poll_wakeup" "void" +This function will wake up any file pollers. +. +.Pp +. +.Sh LIBRARY LIMITATIONS +. +. +Transfer lengths for read, write, cuse_copy_in and cuse_copy_out +should not exceed what can fit into a 32-bit signed integer and is +defined by the CUSE_LENGTH_MAX macro. +. +Transfer lengths for ioctls should not exceed what is defined by the +CUSE_BUFFER_MAX macro. +. +. +.Sh LIBRARY CALLBACK METHODS +. +In general fflags are defined by CUSE_FFLAG_XXX and errors are defined by 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_POLL_NONE + CUSE_POLL_READ + CUSE_POLL_WRITE + CUSE_POLL_ERROR + + CUSE_FFLAG_NONE + CUSE_FFLAG_READ + CUSE_FFLAG_WRITE + CUSE_FFLAG_NONBLOCK + + 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 functions returns a CUSE_ERR_XXX value. +. +.Pp +. +.Ft "int" +.Fn "cuse_close_t" "struct cuse_dev *" "int fflags" +This functions returns a CUSE_ERR_XXX value. +. +.Pp +. +.Ft "int" +.Fn "cuse_read_t" "struct cuse_dev *" "int fflags" "void *peer_ptr" "int len" +This functions returns a CUSE_ERR_XXX value in case of failure or the +actually transferred length in case of success. cuse_copy_in() and +cuse_copy_out() must be used to transfer data to and from the +peer_ptr. +. +.Pp +. +.Ft "int" +.Fn "cuse_write_t" "struct cuse_dev *" "int fflags" "const void *peer_ptr" "int len" +This functions returns a CUSE_ERR_XXX value in case of failure or the +actually transferred length in case of success. cuse_copy_in() and +cuse_copy_out() must be used to transfer data to and from the +peer_ptr. +. +.Pp +. +.Ft "int" +.Fn "cuse_ioctl_t" "struct cuse_dev *" "int fflags" "unsigned long cmd" "void *peer_data" +This functions returns a CUSE_ERR_XXX value in case of failure or zero +in case of success. cuse_copy_in() and cuse_copy_out() must be used to +transfer data to and from the peer_data. +. +.Pp +. +.Ft "int" +.Fn "cuse_poll_t" "struct cuse_dev *" "int fflags" "int events" +This functions returns a mask of CUSE_POLL_XXX values in case of +failure and success. The events argument is also a mask of +CUSE_POLL_XXX values. +. +.Pp +. +.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 SEE ALSO +. +.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..d502c5bd5415 --- /dev/null +++ b/lib/libcuse/cuse.h @@ -0,0 +1,97 @@ +/* $FreeBSD$ */ +/*- + * 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 + +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(int); +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..f518e11d78f3 --- /dev/null +++ b/lib/libcuse/cuse_lib.c @@ -0,0 +1,800 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2010-2012 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#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 TAILQ_HEAD(, cuse_dev) h_cuse; +static TAILQ_HEAD(, cuse_dev_entered) h_cuse_entered; +static int f_cuse = -1; +static pthread_mutex_t m_cuse; +static struct cuse_vm_allocation a_cuse[CUSE_ALLOC_UNIT_MAX]; + +static void +cuse_lock(void) +{ + pthread_mutex_lock(&m_cuse); +} + +static void +cuse_unlock(void) +{ + 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; + int n; + + cuse_lock(); + for (n = 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)) { + + cuse_unlock(); + + remainder = (ptr - ptr_min); + + remainder -= remainder % PAGE_SIZE; + + return ((n * PAGE_SIZE * CUSE_ALLOC_PAGES_MAX) + remainder); + } + } + cuse_unlock(); + + return (0x80000000UL); /* failure */ +} + +void * +cuse_vmalloc(int size) +{ + struct cuse_alloc_info info; + void *ptr; + int error; + int n; + + if (f_cuse < 0) + return (NULL); + + memset(&info, 0, sizeof(info)); + + if (size < 1) + return (NULL); + + info.page_count = (size + PAGE_SIZE - 1) / PAGE_SIZE; + + cuse_lock(); + for (n = 0; n != CUSE_ALLOC_UNIT_MAX; n++) { + + if (a_cuse[n].ptr != NULL) + continue; + + a_cuse[n].ptr = ((uint8_t *)1); /* reserve */ + a_cuse[n].size = 0; + + cuse_unlock(); + + info.alloc_nr = n; + + error = ioctl(f_cuse, CUSE_IOCTL_ALLOC_MEMORY, &info); + + if (error) { + + cuse_lock(); + + a_cuse[n].ptr = NULL; + + if (errno == EBUSY) + continue; + else + break; + } + ptr = mmap(NULL, info.page_count * PAGE_SIZE, + PROT_READ | PROT_WRITE, + MAP_SHARED, f_cuse, CUSE_ALLOC_PAGES_MAX * + PAGE_SIZE * n); + + if (ptr == MAP_FAILED) { + + error = ioctl(f_cuse, CUSE_IOCTL_FREE_MEMORY, &info); + + if (error) { + /* ignore */ + } + cuse_lock(); + + a_cuse[n].ptr = NULL; + + break; + } + cuse_lock(); + a_cuse[n].ptr = ptr; + a_cuse[n].size = size; + cuse_unlock(); + + return (ptr); /* success */ + } + 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_alloc_info info; + int error; + int n; + + if (f_cuse < 0) + return; + + memset(&info, 0, sizeof(info)); + + cuse_lock(); + for (n = 0; n != CUSE_ALLOC_UNIT_MAX; n++) { + if (a_cuse[n].ptr != ptr) + continue; + + cuse_unlock(); + + info.alloc_nr = n; + + munmap(ptr, a_cuse[n].size); + + error = ioctl(f_cuse, CUSE_IOCTL_FREE_MEMORY, &info); + + if (error) { + /* ignore */ + } + 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 = (unsigned long)src; + info.peer_ptr = (unsigned long)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 = (unsigned long)dst; + info.peer_ptr = (unsigned long)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); +} diff --git a/sys/conf/files b/sys/conf/files index c7d850c49583..79452a9056f2 100644 --- a/sys/conf/files +++ b/sys/conf/files @@ -2595,6 +2595,7 @@ fs/devfs/devfs_vnops.c standard fs/fdescfs/fdesc_vfsops.c optional fdescfs fs/fdescfs/fdesc_vnops.c optional fdescfs fs/fifofs/fifo_vnops.c standard +fs/cuse/cuse.c optional cuse fs/fuse/fuse_device.c optional fuse fs/fuse/fuse_file.c optional fuse fs/fuse/fuse_internal.c optional fuse diff --git a/sys/fs/cuse/cuse.c b/sys/fs/cuse/cuse.c new file mode 100644 index 000000000000..4a23963b6cf6 --- /dev/null +++ b/sys/fs/cuse/cuse.c @@ -0,0 +1,1866 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2010-2013 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 "opt_compat.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include +#include + +MODULE_VERSION(cuse, 1); + +#define NBUSY ((uint8_t *)1) + +#ifdef FEATURE +FEATURE(cuse, "Userspace character devices"); +#endif + +struct cuse_command; +struct cuse_server; +struct cuse_client; + +struct cuse_client_command { + TAILQ_ENTRY(cuse_client_command) entry; + struct cuse_command sub; + struct sx sx; + struct cv cv; + struct thread *entered; + struct cuse_client *client; + struct proc *proc_curr; + int proc_refs; + int got_signal; + int error; + int command; +}; + +struct cuse_memory { + struct cuse_server *owner; + uint8_t *virtaddr; + uint32_t page_count; + uint32_t is_allocated; +}; + +struct cuse_server_dev { + TAILQ_ENTRY(cuse_server_dev) entry; + struct cuse_server *server; + struct cdev *kern_dev; + struct cuse_dev *user_dev; +}; + +struct cuse_server { + TAILQ_ENTRY(cuse_server) entry; + TAILQ_HEAD(, cuse_client_command) head; + TAILQ_HEAD(, cuse_server_dev) hdev; + TAILQ_HEAD(, cuse_client) hcli; + struct cv cv; + struct selinfo selinfo; + int is_closing; + int refs; +}; + +struct cuse_client { + TAILQ_ENTRY(cuse_client) entry; + TAILQ_ENTRY(cuse_client) entry_ref; + struct cuse_client_command cmds[CUSE_CMD_MAX]; + struct cuse_server *server; + struct cuse_server_dev *server_dev; + + uint8_t ioctl_buffer[CUSE_BUFFER_MAX] __aligned(4); + + int fflags; /* file flags */ + int cflags; /* client flags */ +#define CUSE_CLI_IS_CLOSING 0x01 +#define CUSE_CLI_KNOTE_NEED_READ 0x02 +#define CUSE_CLI_KNOTE_NEED_WRITE 0x04 +#define CUSE_CLI_KNOTE_HAS_READ 0x08 +#define CUSE_CLI_KNOTE_HAS_WRITE 0x10 +}; + +#define CUSE_CLIENT_CLOSING(pcc) \ + ((pcc)->cflags & CUSE_CLI_IS_CLOSING) + +static MALLOC_DEFINE(M_CUSE, "cuse", "CUSE memory"); + +static TAILQ_HEAD(, cuse_server) cuse_server_head; +static struct mtx cuse_mtx; +static struct cdev *cuse_dev; +static struct cuse_server *cuse_alloc_unit[CUSE_DEVICES_MAX]; +static int cuse_alloc_unit_id[CUSE_DEVICES_MAX]; +static struct cuse_memory cuse_mem[CUSE_ALLOC_UNIT_MAX]; + +static void cuse_client_kqfilter_read_detach(struct knote *kn); +static void cuse_client_kqfilter_write_detach(struct knote *kn); +static int cuse_client_kqfilter_read_event(struct knote *kn, long hint); +static int cuse_client_kqfilter_write_event(struct knote *kn, long hint); + +static struct filterops cuse_client_kqfilter_read_ops = { + .f_isfd = 1, + .f_detach = cuse_client_kqfilter_read_detach, + .f_event = cuse_client_kqfilter_read_event, +}; + +static struct filterops cuse_client_kqfilter_write_ops = { + .f_isfd = 1, + .f_detach = cuse_client_kqfilter_write_detach, + .f_event = cuse_client_kqfilter_write_event, +}; + +static d_open_t cuse_client_open; +static d_close_t cuse_client_close; +static d_ioctl_t cuse_client_ioctl; +static d_read_t cuse_client_read; +static d_write_t cuse_client_write; +static d_poll_t cuse_client_poll; +static d_mmap_t cuse_client_mmap; +static d_kqfilter_t cuse_client_kqfilter; + +static struct cdevsw cuse_client_devsw = { + .d_version = D_VERSION, + .d_open = cuse_client_open, + .d_close = cuse_client_close, + .d_ioctl = cuse_client_ioctl, + .d_name = "cuse_client", + .d_flags = D_TRACKCLOSE, + .d_read = cuse_client_read, + .d_write = cuse_client_write, + .d_poll = cuse_client_poll, + .d_mmap = cuse_client_mmap, + .d_kqfilter = cuse_client_kqfilter, +}; + +static d_open_t cuse_server_open; +static d_close_t cuse_server_close; +static d_ioctl_t cuse_server_ioctl; +static d_read_t cuse_server_read; +static d_write_t cuse_server_write; +static d_poll_t cuse_server_poll; +static d_mmap_t cuse_server_mmap; + +static struct cdevsw cuse_server_devsw = { + .d_version = D_VERSION, + .d_open = cuse_server_open, + .d_close = cuse_server_close, + .d_ioctl = cuse_server_ioctl, + .d_name = "cuse_server", + .d_flags = D_TRACKCLOSE, + .d_read = cuse_server_read, + .d_write = cuse_server_write, + .d_poll = cuse_server_poll, + .d_mmap = cuse_server_mmap, +}; + +static void cuse_client_is_closing(struct cuse_client *); +static int cuse_free_unit_by_id_locked(struct cuse_server *, int); + +static void +cuse_lock(void) +{ + mtx_lock(&cuse_mtx); +} + +static void +cuse_unlock(void) +{ + mtx_unlock(&cuse_mtx); +} + +static void +cuse_cmd_lock(struct cuse_client_command *pccmd) +{ + sx_xlock(&pccmd->sx); +} + +static void +cuse_cmd_unlock(struct cuse_client_command *pccmd) +{ + sx_xunlock(&pccmd->sx); +} + +static void +cuse_kern_init(void *arg) +{ + TAILQ_INIT(&cuse_server_head); + + mtx_init(&cuse_mtx, "cuse-mtx", NULL, MTX_DEF); + + cuse_dev = make_dev(&cuse_server_devsw, 0, + UID_ROOT, GID_OPERATOR, 0600, "cuse"); + + printf("Cuse v%d.%d.%d @ /dev/cuse\n", + (CUSE_VERSION >> 16) & 0xFF, (CUSE_VERSION >> 8) & 0xFF, + (CUSE_VERSION >> 0) & 0xFF); +} + +SYSINIT(cuse_kern_init, SI_SUB_DEVFS, SI_ORDER_ANY, cuse_kern_init, 0); + +static void +cuse_kern_uninit(void *arg) +{ + void *ptr; + + while (1) { + + printf("Cuse: Please exit all /dev/cuse instances " + "and processes which have used this device.\n"); + + pause("DRAIN", 2 * hz); + + cuse_lock(); + ptr = TAILQ_FIRST(&cuse_server_head); + cuse_unlock(); + + if (ptr == NULL) + break; + } + + if (cuse_dev != NULL) + destroy_dev(cuse_dev); + + mtx_destroy(&cuse_mtx); +} + +SYSUNINIT(cuse_kern_uninit, SI_SUB_DEVFS, SI_ORDER_ANY, cuse_kern_uninit, 0); + +static int +cuse_server_get(struct cuse_server **ppcs) +{ + struct cuse_server *pcs; + int error; + + error = devfs_get_cdevpriv((void **)&pcs); + if (error != 0) { + *ppcs = NULL; + return (error); + } + /* check if closing */ + cuse_lock(); + if (pcs->is_closing) { + cuse_unlock(); + *ppcs = NULL; + return (EINVAL); + } + cuse_unlock(); + *ppcs = pcs; + return (0); +} + +static void +cuse_server_is_closing(struct cuse_server *pcs) +{ + struct cuse_client *pcc; + + if (pcs->is_closing) + return; + + pcs->is_closing = 1; + + TAILQ_FOREACH(pcc, &pcs->hcli, entry) { + cuse_client_is_closing(pcc); + } +} + +static struct cuse_client_command * +cuse_server_find_command(struct cuse_server *pcs, struct thread *td) +{ + struct cuse_client *pcc; + int n; + + if (pcs->is_closing) + goto done; + + TAILQ_FOREACH(pcc, &pcs->hcli, entry) { + if (CUSE_CLIENT_CLOSING(pcc)) + continue; + for (n = 0; n != CUSE_CMD_MAX; n++) { + if (pcc->cmds[n].entered == td) + return (&pcc->cmds[n]); + } + } +done: + return (NULL); +} + +static void +cuse_str_filter(char *ptr) +{ + int c; + + while (((c = *ptr) != 0)) { + + if ((c >= 'a') && (c <= 'z')) { + ptr++; + continue; + } + if ((c >= 'A') && (c <= 'Z')) { + ptr++; + continue; + } + if ((c >= '0') && (c <= '9')) { + ptr++; + continue; + } + if ((c == '.') || (c == '_') || (c == '/')) { + ptr++; + continue; + } + *ptr = '_'; + + ptr++; + } +} + +static int +cuse_convert_error(int error) +{ + ; /* indent fix */ + switch (error) { + case CUSE_ERR_NONE: + return (0); + case CUSE_ERR_BUSY: + return (EBUSY); + case CUSE_ERR_WOULDBLOCK: + return (EWOULDBLOCK); + case CUSE_ERR_INVALID: + return (EINVAL); + case CUSE_ERR_NO_MEMORY: + return (ENOMEM); + case CUSE_ERR_FAULT: + return (EFAULT); + case CUSE_ERR_SIGNAL: + return (EINTR); + default: + return (ENXIO); + } +} + +static void +cuse_server_free_memory(struct cuse_server *pcs) +{ + struct cuse_memory *mem; + uint32_t n; + + for (n = 0; n != CUSE_ALLOC_UNIT_MAX; n++) { + mem = &cuse_mem[n]; + + /* this memory is never freed */ + if (mem->owner == pcs) { + mem->owner = NULL; + mem->is_allocated = 0; + } + } +} + +static int +cuse_server_alloc_memory(struct cuse_server *pcs, + struct cuse_memory *mem, uint32_t page_count) +{ + void *ptr; + int error; + + cuse_lock(); + + if (mem->virtaddr == NBUSY) { + cuse_unlock(); + return (EBUSY); + } + if (mem->virtaddr != NULL) { + if (mem->is_allocated != 0) { + cuse_unlock(); + return (EBUSY); + } + if (mem->page_count == page_count) { + mem->is_allocated = 1; + mem->owner = pcs; + cuse_unlock(); + return (0); + } + cuse_unlock(); + return (EBUSY); + } + memset(mem, 0, sizeof(*mem)); + + mem->virtaddr = NBUSY; + + cuse_unlock(); + + ptr = malloc(page_count * PAGE_SIZE, M_CUSE, M_WAITOK | M_ZERO); + if (ptr == NULL) + error = ENOMEM; + else + error = 0; + + cuse_lock(); + + if (error) { + mem->virtaddr = NULL; + cuse_unlock(); + return (error); + } + mem->virtaddr = ptr; + mem->page_count = page_count; + mem->is_allocated = 1; + mem->owner = pcs; + cuse_unlock(); + + return (0); +} + +static int +cuse_client_get(struct cuse_client **ppcc) +{ + struct cuse_client *pcc; + int error; + + /* try to get private data */ + error = devfs_get_cdevpriv((void **)&pcc); + if (error != 0) { + *ppcc = NULL; + return (error); + } + /* check if closing */ + cuse_lock(); + if (CUSE_CLIENT_CLOSING(pcc) || pcc->server->is_closing) { + cuse_unlock(); + *ppcc = NULL; + return (EINVAL); + } + cuse_unlock(); + *ppcc = pcc; + return (0); +} + +static void +cuse_client_is_closing(struct cuse_client *pcc) +{ + struct cuse_client_command *pccmd; + uint32_t n; + + if (CUSE_CLIENT_CLOSING(pcc)) + return; + + pcc->cflags |= CUSE_CLI_IS_CLOSING; + pcc->server_dev = NULL; + + for (n = 0; n != CUSE_CMD_MAX; n++) { + + pccmd = &pcc->cmds[n]; + + if (pccmd->entry.tqe_prev != NULL) { + TAILQ_REMOVE(&pcc->server->head, pccmd, entry); + pccmd->entry.tqe_prev = NULL; + } + cv_broadcast(&pccmd->cv); + } +} + +static void +cuse_client_send_command_locked(struct cuse_client_command *pccmd, + unsigned long data_ptr, unsigned long arg, int fflags, int ioflag) +{ + unsigned long cuse_fflags = 0; + struct cuse_server *pcs; + + if (fflags & FREAD) + cuse_fflags |= CUSE_FFLAG_READ; + + if (fflags & FWRITE) + cuse_fflags |= CUSE_FFLAG_WRITE; + + if (ioflag & IO_NDELAY) + cuse_fflags |= CUSE_FFLAG_NONBLOCK; + + pccmd->sub.fflags = cuse_fflags; + pccmd->sub.data_pointer = data_ptr; + pccmd->sub.argument = arg; + + pcs = pccmd->client->server; + + if ((pccmd->entry.tqe_prev == NULL) && + (CUSE_CLIENT_CLOSING(pccmd->client) == 0) && + (pcs->is_closing == 0)) { + TAILQ_INSERT_TAIL(&pcs->head, pccmd, entry); + cv_signal(&pcs->cv); + } +} + +static void +cuse_client_got_signal(struct cuse_client_command *pccmd) +{ + struct cuse_server *pcs; + + pccmd->got_signal = 1; + + pccmd = &pccmd->client->cmds[CUSE_CMD_SIGNAL]; + + pcs = pccmd->client->server; + + if ((pccmd->entry.tqe_prev == NULL) && + (CUSE_CLIENT_CLOSING(pccmd->client) == 0) && + (pcs->is_closing == 0)) { + TAILQ_INSERT_TAIL(&pcs->head, pccmd, entry); + cv_signal(&pcs->cv); + } +} + +static int +cuse_client_receive_command_locked(struct cuse_client_command *pccmd, + uint8_t *arg_ptr, uint32_t arg_len) +{ + int error; + + error = 0; + + pccmd->proc_curr = curthread->td_proc; + + if (CUSE_CLIENT_CLOSING(pccmd->client) || + pccmd->client->server->is_closing) { + error = CUSE_ERR_OTHER; + goto done; + } + while (pccmd->command == CUSE_CMD_NONE) { + if (error != 0) { + cv_wait(&pccmd->cv, &cuse_mtx); + } else { + error = cv_wait_sig(&pccmd->cv, &cuse_mtx); + + if (error != 0) + cuse_client_got_signal(pccmd); + } + if (CUSE_CLIENT_CLOSING(pccmd->client) || + pccmd->client->server->is_closing) { + error = CUSE_ERR_OTHER; + goto done; + } + } + + error = pccmd->error; + pccmd->command = CUSE_CMD_NONE; + cv_signal(&pccmd->cv); + +done: + + /* wait until all process references are gone */ + + pccmd->proc_curr = NULL; + + while (pccmd->proc_refs != 0) + cv_wait(&pccmd->cv, &cuse_mtx); + + return (error); +} + +/*------------------------------------------------------------------------* + * CUSE SERVER PART + *------------------------------------------------------------------------*/ + +static void +cuse_server_free_dev(struct cuse_server_dev *pcsd) +{ + struct cuse_server *pcs; + struct cuse_client *pcc; + + /* get server pointer */ + pcs = pcsd->server; + + /* prevent creation of more devices */ + cuse_lock(); + if (pcsd->kern_dev != NULL) + pcsd->kern_dev->si_drv1 = NULL; + + TAILQ_FOREACH(pcc, &pcs->hcli, entry) { + if (pcc->server_dev == pcsd) + cuse_client_is_closing(pcc); + } + cuse_unlock(); + + /* destroy device, if any */ + if (pcsd->kern_dev != NULL) { + /* destroy device synchronously */ + destroy_dev(pcsd->kern_dev); + } + free(pcsd, M_CUSE); +} + +static void +cuse_server_free(void *arg) +{ + struct cuse_server *pcs = arg; + struct cuse_server_dev *pcsd; + + cuse_lock(); + pcs->refs--; + if (pcs->refs != 0) { + cuse_unlock(); + return; + } + cuse_server_is_closing(pcs); + + TAILQ_REMOVE(&cuse_server_head, pcs, entry); + + cuse_free_unit_by_id_locked(pcs, -1); + + while ((pcsd = TAILQ_FIRST(&pcs->hdev)) != NULL) { + TAILQ_REMOVE(&pcs->hdev, pcsd, entry); + cuse_unlock(); + cuse_server_free_dev(pcsd); + cuse_lock(); + } + + cuse_server_free_memory(pcs); + + knlist_clear(&pcs->selinfo.si_note, 1); + knlist_destroy(&pcs->selinfo.si_note); + + cuse_unlock(); + + seldrain(&pcs->selinfo); + + cv_destroy(&pcs->cv); + + free(pcs, M_CUSE); +} + +static int +cuse_server_open(struct cdev *dev, int fflags, int devtype, struct thread *td) +{ + struct cuse_server *pcs; + + pcs = malloc(sizeof(*pcs), M_CUSE, M_WAITOK | M_ZERO); + if (pcs == NULL) + return (ENOMEM); + + if (devfs_set_cdevpriv(pcs, &cuse_server_free)) { + printf("Cuse: Cannot set cdevpriv.\n"); + free(pcs, M_CUSE); + return (ENOMEM); + } + TAILQ_INIT(&pcs->head); + TAILQ_INIT(&pcs->hdev); + TAILQ_INIT(&pcs->hcli); + + cv_init(&pcs->cv, "cuse-server-cv"); + + knlist_init_mtx(&pcs->selinfo.si_note, &cuse_mtx); + + cuse_lock(); + pcs->refs++; + TAILQ_INSERT_TAIL(&cuse_server_head, pcs, entry); + cuse_unlock(); + + return (0); +} + +static int +cuse_server_close(struct cdev *dev, int fflag, int devtype, struct thread *td) +{ + struct cuse_server *pcs; + int error; + + error = cuse_server_get(&pcs); + if (error != 0) + goto done; + + cuse_lock(); + cuse_server_is_closing(pcs); + knlist_clear(&pcs->selinfo.si_note, 1); + cuse_unlock(); + +done: + return (0); +} + +static int +cuse_server_read(struct cdev *dev, struct uio *uio, int ioflag) +{ + return (ENXIO); +} + +static int +cuse_server_write(struct cdev *dev, struct uio *uio, int ioflag) +{ + return (ENXIO); +} + +static int +cuse_server_ioctl_copy_locked(struct cuse_client_command *pccmd, + struct cuse_data_chunk *pchk, int isread) +{ + struct proc *p_proc; + uint32_t offset; + int error; + + offset = pchk->peer_ptr - CUSE_BUF_MIN_PTR; + + if (pchk->length > CUSE_BUFFER_MAX) + return (EFAULT); + + if (offset >= CUSE_BUFFER_MAX) + return (EFAULT); + + if ((offset + pchk->length) > CUSE_BUFFER_MAX) + return (EFAULT); + + p_proc = pccmd->proc_curr; + if (p_proc == NULL) + return (ENXIO); + + if (pccmd->proc_refs < 0) + return (ENOMEM); + + pccmd->proc_refs++; + + cuse_unlock(); + + if (isread == 0) { + error = copyin( + (void *)pchk->local_ptr, + pccmd->client->ioctl_buffer + offset, + pchk->length); + } else { + error = copyout( + pccmd->client->ioctl_buffer + offset, + (void *)pchk->local_ptr, + pchk->length); + } + + cuse_lock(); + + pccmd->proc_refs--; + + if (pccmd->proc_curr == NULL) + cv_signal(&pccmd->cv); + + return (error); +} + +static int +cuse_proc2proc_copy(struct proc *proc_s, vm_offset_t data_s, + struct proc *proc_d, vm_offset_t data_d, size_t len) +{ + struct thread *td; + struct proc *proc_cur; + int error; + + td = curthread; + proc_cur = td->td_proc; + + if (proc_cur == proc_d) { + struct iovec iov = { + .iov_base = (caddr_t)data_d, + .iov_len = len, + }; + struct uio uio = { + .uio_iov = &iov, + .uio_iovcnt = 1, + .uio_offset = (off_t)data_s, + .uio_resid = len, + .uio_segflg = UIO_USERSPACE, + .uio_rw = UIO_READ, + .uio_td = td, + }; + + PROC_LOCK(proc_s); + _PHOLD(proc_s); + PROC_UNLOCK(proc_s); + + error = proc_rwmem(proc_s, &uio); + + PROC_LOCK(proc_s); + _PRELE(proc_s); + PROC_UNLOCK(proc_s); + + } else if (proc_cur == proc_s) { + struct iovec iov = { + .iov_base = (caddr_t)data_s, + .iov_len = len, + }; + struct uio uio = { + .uio_iov = &iov, + .uio_iovcnt = 1, + .uio_offset = (off_t)data_d, + .uio_resid = len, + .uio_segflg = UIO_USERSPACE, + .uio_rw = UIO_WRITE, + .uio_td = td, + }; + + PROC_LOCK(proc_d); + _PHOLD(proc_d); + PROC_UNLOCK(proc_d); + + error = proc_rwmem(proc_d, &uio); + + PROC_LOCK(proc_d); + _PRELE(proc_d); + PROC_UNLOCK(proc_d); + } else { + error = EINVAL; + } + return (error); +} + +static int +cuse_server_data_copy_locked(struct cuse_client_command *pccmd, + struct cuse_data_chunk *pchk, int isread) +{ + struct proc *p_proc; + int error; + + p_proc = pccmd->proc_curr; + if (p_proc == NULL) + return (ENXIO); + + if (pccmd->proc_refs < 0) + return (ENOMEM); + + pccmd->proc_refs++; + + cuse_unlock(); + + if (isread == 0) { + error = cuse_proc2proc_copy( + curthread->td_proc, pchk->local_ptr, + p_proc, pchk->peer_ptr, + pchk->length); + } else { + error = cuse_proc2proc_copy( + p_proc, pchk->peer_ptr, + curthread->td_proc, pchk->local_ptr, + pchk->length); + } + + cuse_lock(); + + pccmd->proc_refs--; + + if (pccmd->proc_curr == NULL) + cv_signal(&pccmd->cv); + + return (error); +} + +static int +cuse_alloc_unit_by_id_locked(struct cuse_server *pcs, int id) +{ + int n; + int x = 0; + int match; + + do { + for (match = n = 0; n != CUSE_DEVICES_MAX; n++) { + if (cuse_alloc_unit[n] != NULL) { + if ((cuse_alloc_unit_id[n] ^ id) & CUSE_ID_MASK) + continue; + if ((cuse_alloc_unit_id[n] & ~CUSE_ID_MASK) == x) { + x++; + match = 1; + } + } + } + } while (match); + + if (x < 256) { + for (n = 0; n != CUSE_DEVICES_MAX; n++) { + if (cuse_alloc_unit[n] == NULL) { + cuse_alloc_unit[n] = pcs; + cuse_alloc_unit_id[n] = id | x; + return (x); + } + } + } + return (-1); +} + +static void +cuse_server_wakeup_locked(struct cuse_server *pcs) +{ + selwakeup(&pcs->selinfo); + KNOTE_LOCKED(&pcs->selinfo.si_note, 0); +} + +static int +cuse_free_unit_by_id_locked(struct cuse_server *pcs, int id) +{ + int n; + int found = 0; + + for (n = 0; n != CUSE_DEVICES_MAX; n++) { + if (cuse_alloc_unit[n] == pcs) { + if (cuse_alloc_unit_id[n] == id || id == -1) { + cuse_alloc_unit[n] = NULL; + cuse_alloc_unit_id[n] = 0; + found = 1; + } + } + } + + return (found ? 0 : EINVAL); +} + +static int +cuse_server_ioctl(struct cdev *dev, unsigned long cmd, + caddr_t data, int fflag, struct thread *td) +{ + struct cuse_server *pcs; + int error; + + error = cuse_server_get(&pcs); + if (error != 0) + return (error); + + switch (cmd) { + struct cuse_client_command *pccmd; + struct cuse_client *pcc; + struct cuse_command *pcmd; + struct cuse_alloc_info *pai; + struct cuse_create_dev *pcd; + struct cuse_server_dev *pcsd; + struct cuse_data_chunk *pchk; + int n; + + case CUSE_IOCTL_GET_COMMAND: + pcmd = (void *)data; + + cuse_lock(); + + while ((pccmd = TAILQ_FIRST(&pcs->head)) == NULL) { + error = cv_wait_sig(&pcs->cv, &cuse_mtx); + + if (pcs->is_closing) + error = ENXIO; + + if (error) { + cuse_unlock(); + return (error); + } + } + + TAILQ_REMOVE(&pcs->head, pccmd, entry); + pccmd->entry.tqe_prev = NULL; + + pccmd->entered = curthread; + + *pcmd = pccmd->sub; + + cuse_unlock(); + + break; + + case CUSE_IOCTL_SYNC_COMMAND: + + cuse_lock(); + while ((pccmd = cuse_server_find_command(pcs, curthread)) != NULL) { + + /* send sync command */ + pccmd->entered = NULL; + pccmd->error = *(int *)data; + pccmd->command = CUSE_CMD_SYNC; + + /* signal peer, if any */ + cv_signal(&pccmd->cv); + } + cuse_unlock(); + + break; + + case CUSE_IOCTL_ALLOC_UNIT: + + cuse_lock(); + n = cuse_alloc_unit_by_id_locked(pcs, + CUSE_ID_DEFAULT(0)); + cuse_unlock(); + + if (n < 0) + error = ENOMEM; + else + *(int *)data = n; + break; + + case CUSE_IOCTL_ALLOC_UNIT_BY_ID: + + n = *(int *)data; + + n = (n & CUSE_ID_MASK); + + cuse_lock(); + n = cuse_alloc_unit_by_id_locked(pcs, n); + cuse_unlock(); + + if (n < 0) + error = ENOMEM; + else + *(int *)data = n; + break; + + case CUSE_IOCTL_FREE_UNIT: + + n = *(int *)data; + + n = CUSE_ID_DEFAULT(n); + + cuse_lock(); + error = cuse_free_unit_by_id_locked(pcs, n); + cuse_unlock(); + break; + + case CUSE_IOCTL_FREE_UNIT_BY_ID: + + n = *(int *)data; + + cuse_lock(); + error = cuse_free_unit_by_id_locked(pcs, n); + cuse_unlock(); + break; + + case CUSE_IOCTL_ALLOC_MEMORY: + + pai = (void *)data; + + if (pai->alloc_nr >= CUSE_ALLOC_UNIT_MAX) { + error = ENOMEM; + break; + } + if (pai->page_count > CUSE_ALLOC_PAGES_MAX) { + error = ENOMEM; + break; + } + error = cuse_server_alloc_memory(pcs, + &cuse_mem[pai->alloc_nr], pai->page_count); + break; + + case CUSE_IOCTL_FREE_MEMORY: + pai = (void *)data; + + if (pai->alloc_nr >= CUSE_ALLOC_UNIT_MAX) { + error = ENOMEM; + break; + } + /* we trust the character device driver in this case */ + + cuse_lock(); + if (cuse_mem[pai->alloc_nr].owner == pcs) { + cuse_mem[pai->alloc_nr].is_allocated = 0; + cuse_mem[pai->alloc_nr].owner = NULL; + } else { + error = EINVAL; + } + cuse_unlock(); + break; + + case CUSE_IOCTL_GET_SIG: + + cuse_lock(); + pccmd = cuse_server_find_command(pcs, curthread); + + if (pccmd != NULL) { + n = pccmd->got_signal; + pccmd->got_signal = 0; + } else { + n = 0; + } + cuse_unlock(); + + *(int *)data = n; + + break; + + case CUSE_IOCTL_SET_PFH: + + cuse_lock(); + pccmd = cuse_server_find_command(pcs, curthread); + + if (pccmd != NULL) { + pcc = pccmd->client; + for (n = 0; n != CUSE_CMD_MAX; n++) { + pcc->cmds[n].sub.per_file_handle = *(unsigned long *)data; + } + } else { + error = ENXIO; + } + cuse_unlock(); + break; + + case CUSE_IOCTL_CREATE_DEV: + + error = priv_check(curthread, PRIV_DRIVER); + if (error) + break; + + pcd = (void *)data; + + /* filter input */ + + pcd->devname[sizeof(pcd->devname) - 1] = 0; + + if (pcd->devname[0] == 0) { + error = EINVAL; + break; + } + cuse_str_filter(pcd->devname); + + pcd->permissions &= 0777; + + /* try to allocate a character device */ + + pcsd = malloc(sizeof(*pcsd), M_CUSE, M_WAITOK | M_ZERO); + + if (pcsd == NULL) { + error = ENOMEM; + break; + } + pcsd->server = pcs; + + pcsd->user_dev = pcd->dev; + + pcsd->kern_dev = make_dev_credf(MAKEDEV_CHECKNAME, + &cuse_client_devsw, 0, NULL, pcd->user_id, pcd->group_id, + pcd->permissions, "%s", pcd->devname); + + if (pcsd->kern_dev == NULL) { + free(pcsd, M_CUSE); + error = ENOMEM; + break; + } + pcsd->kern_dev->si_drv1 = pcsd; + + cuse_lock(); + TAILQ_INSERT_TAIL(&pcs->hdev, pcsd, entry); + cuse_unlock(); + + break; + + case CUSE_IOCTL_DESTROY_DEV: + + error = priv_check(curthread, PRIV_DRIVER); + if (error) + break; + + cuse_lock(); + + error = EINVAL; + + pcsd = TAILQ_FIRST(&pcs->hdev); + while (pcsd != NULL) { + if (pcsd->user_dev == *(struct cuse_dev **)data) { + TAILQ_REMOVE(&pcs->hdev, pcsd, entry); + cuse_unlock(); + cuse_server_free_dev(pcsd); + cuse_lock(); + error = 0; + pcsd = TAILQ_FIRST(&pcs->hdev); + } else { + pcsd = TAILQ_NEXT(pcsd, entry); + } + } + + cuse_unlock(); + break; + + case CUSE_IOCTL_WRITE_DATA: + case CUSE_IOCTL_READ_DATA: + + cuse_lock(); + pchk = (struct cuse_data_chunk *)data; + + pccmd = cuse_server_find_command(pcs, curthread); + + if (pccmd == NULL) { + error = ENXIO; /* invalid request */ + } else if (pchk->peer_ptr < CUSE_BUF_MIN_PTR) { + error = EFAULT; /* NULL pointer */ + } else if (pchk->peer_ptr < CUSE_BUF_MAX_PTR) { + error = cuse_server_ioctl_copy_locked(pccmd, + pchk, cmd == CUSE_IOCTL_READ_DATA); + } else { + error = cuse_server_data_copy_locked(pccmd, + pchk, cmd == CUSE_IOCTL_READ_DATA); + } + cuse_unlock(); + break; + + case CUSE_IOCTL_SELWAKEUP: + cuse_lock(); + /* + * We don't know which direction caused the event. + * Wakeup both! + */ + TAILQ_FOREACH(pcc, &pcs->hcli, entry) { + pcc->cflags |= (CUSE_CLI_KNOTE_NEED_READ | + CUSE_CLI_KNOTE_NEED_WRITE); + } + cuse_server_wakeup_locked(pcs); + cuse_unlock(); + break; + + default: + error = ENXIO; + break; + } + return (error); +} + +static int +cuse_server_poll(struct cdev *dev, int events, struct thread *td) +{ + return (events & (POLLHUP | POLLPRI | POLLIN | + POLLRDNORM | POLLOUT | POLLWRNORM)); +} + +static int +cuse_server_mmap(struct cdev *dev, vm_ooffset_t offset, vm_paddr_t *paddr, int nprot, vm_memattr_t *memattr) +{ + uint32_t page_nr = offset / PAGE_SIZE; + uint32_t alloc_nr = page_nr / CUSE_ALLOC_PAGES_MAX; + struct cuse_memory *mem; + struct cuse_server *pcs; + uint8_t *ptr; + int error; + + if (alloc_nr >= CUSE_ALLOC_UNIT_MAX) + return (ENOMEM); + + error = cuse_server_get(&pcs); + if (error != 0) + pcs = NULL; + + cuse_lock(); + mem = &cuse_mem[alloc_nr]; + + /* try to enforce slight ownership */ + if ((pcs != NULL) && (mem->owner != pcs)) { + cuse_unlock(); + return (EINVAL); + } + if (mem->virtaddr == NULL) { + cuse_unlock(); + return (ENOMEM); + } + if (mem->virtaddr == NBUSY) { + cuse_unlock(); + return (ENOMEM); + } + page_nr %= CUSE_ALLOC_PAGES_MAX; + + if (page_nr >= mem->page_count) { + cuse_unlock(); + return (ENXIO); + } + ptr = mem->virtaddr + (page_nr * PAGE_SIZE); + cuse_unlock(); + + *paddr = vtophys(ptr); + + return (0); +} + +/*------------------------------------------------------------------------* + * CUSE CLIENT PART + *------------------------------------------------------------------------*/ +static void +cuse_client_free(void *arg) +{ + struct cuse_client *pcc = arg; + struct cuse_client_command *pccmd; + struct cuse_server *pcs; + int n; + + cuse_lock(); + cuse_client_is_closing(pcc); + TAILQ_REMOVE(&pcc->server->hcli, pcc, entry); + cuse_unlock(); + + for (n = 0; n != CUSE_CMD_MAX; n++) { + + pccmd = &pcc->cmds[n]; + + sx_destroy(&pccmd->sx); + cv_destroy(&pccmd->cv); + } + + pcs = pcc->server; + + free(pcc, M_CUSE); + + /* drop reference on server */ + cuse_server_free(pcs); +} + +static int +cuse_client_open(struct cdev *dev, int fflags, int devtype, struct thread *td) +{ + struct cuse_client_command *pccmd; + struct cuse_server_dev *pcsd; + struct cuse_client *pcc; + struct cuse_server *pcs; + struct cuse_dev *pcd; + int error; + int n; + + cuse_lock(); + pcsd = dev->si_drv1; + if (pcsd != NULL) { + pcs = pcsd->server; + pcd = pcsd->user_dev; + pcs->refs++; + if (pcs->refs < 0) { + /* overflow */ + pcs->refs--; + pcsd = NULL; + } + } else { + pcs = NULL; + pcd = NULL; + } + cuse_unlock(); + + if (pcsd == NULL) + return (EINVAL); + + pcc = malloc(sizeof(*pcc), M_CUSE, M_WAITOK | M_ZERO); + if (pcc == NULL) { + /* drop reference on server */ + cuse_server_free(pcs); + return (ENOMEM); + } + if (devfs_set_cdevpriv(pcc, &cuse_client_free)) { + printf("Cuse: Cannot set cdevpriv.\n"); + /* drop reference on server */ + cuse_server_free(pcs); + free(pcc, M_CUSE); + return (ENOMEM); + } + pcc->fflags = fflags; + pcc->server_dev = pcsd; + pcc->server = pcs; + + for (n = 0; n != CUSE_CMD_MAX; n++) { + + pccmd = &pcc->cmds[n]; + + pccmd->sub.dev = pcd; + pccmd->sub.command = n; + pccmd->client = pcc; + + sx_init(&pccmd->sx, "cuse-client-sx"); + cv_init(&pccmd->cv, "cuse-client-cv"); + } + + cuse_lock(); + + /* cuse_client_free() assumes that the client is listed somewhere! */ + /* always enqueue */ + + TAILQ_INSERT_TAIL(&pcs->hcli, pcc, entry); + + /* check if server is closing */ + if ((pcs->is_closing != 0) || (dev->si_drv1 == NULL)) { + error = EINVAL; + } else { + error = 0; + } + cuse_unlock(); + + if (error) { + devfs_clear_cdevpriv(); /* XXX bugfix */ + return (error); + } + pccmd = &pcc->cmds[CUSE_CMD_OPEN]; + + cuse_cmd_lock(pccmd); + + cuse_lock(); + cuse_client_send_command_locked(pccmd, 0, 0, pcc->fflags, 0); + + error = cuse_client_receive_command_locked(pccmd, 0, 0); + cuse_unlock(); + + if (error < 0) { + error = cuse_convert_error(error); + } else { + error = 0; + } + + cuse_cmd_unlock(pccmd); + + if (error) + devfs_clear_cdevpriv(); /* XXX bugfix */ + + return (error); +} + +static int +cuse_client_close(struct cdev *dev, int fflag, int devtype, struct thread *td) +{ + struct cuse_client_command *pccmd; + struct cuse_client *pcc; + int error; + + error = cuse_client_get(&pcc); + if (error != 0) + return (0); + + pccmd = &pcc->cmds[CUSE_CMD_CLOSE]; + + cuse_cmd_lock(pccmd); + + cuse_lock(); + cuse_client_send_command_locked(pccmd, 0, 0, pcc->fflags, 0); + + error = cuse_client_receive_command_locked(pccmd, 0, 0); + cuse_unlock(); + + cuse_cmd_unlock(pccmd); + + cuse_lock(); + cuse_client_is_closing(pcc); + cuse_unlock(); + + return (0); +} + +static void +cuse_client_kqfilter_poll(struct cdev *dev, struct cuse_client *pcc) +{ + int temp; + + cuse_lock(); + temp = (pcc->cflags & (CUSE_CLI_KNOTE_HAS_READ | + CUSE_CLI_KNOTE_HAS_WRITE)); + pcc->cflags &= ~(CUSE_CLI_KNOTE_NEED_READ | + CUSE_CLI_KNOTE_NEED_WRITE); + cuse_unlock(); + + if (temp != 0) { + /* get the latest polling state from the server */ + temp = cuse_client_poll(dev, POLLIN | POLLOUT, NULL); + + cuse_lock(); + if (temp & (POLLIN | POLLOUT)) { + if (temp & POLLIN) + pcc->cflags |= CUSE_CLI_KNOTE_NEED_READ; + if (temp & POLLOUT) + pcc->cflags |= CUSE_CLI_KNOTE_NEED_WRITE; + + /* make sure the "knote" gets woken up */ + cuse_server_wakeup_locked(pcc->server); + } + cuse_unlock(); + } +} + +static int +cuse_client_read(struct cdev *dev, struct uio *uio, int ioflag) +{ + struct cuse_client_command *pccmd; + struct cuse_client *pcc; + int error; + int len; + + error = cuse_client_get(&pcc); + if (error != 0) + return (error); + + pccmd = &pcc->cmds[CUSE_CMD_READ]; + + if (uio->uio_segflg != UIO_USERSPACE) { + return (EINVAL); + } + uio->uio_segflg = UIO_NOCOPY; + + cuse_cmd_lock(pccmd); + + while (uio->uio_resid != 0) { + + if (uio->uio_iov->iov_len > CUSE_LENGTH_MAX) { + error = ENOMEM; + break; + } + + len = uio->uio_iov->iov_len; + + cuse_lock(); + cuse_client_send_command_locked(pccmd, + (unsigned long)uio->uio_iov->iov_base, + (unsigned long)(unsigned int)len, pcc->fflags, ioflag); + + error = cuse_client_receive_command_locked(pccmd, 0, 0); + cuse_unlock(); + + if (error < 0) { + error = cuse_convert_error(error); + break; + } else if (error == len) { + error = uiomove(NULL, error, uio); + if (error) + break; + } else { + error = uiomove(NULL, error, uio); + break; + } + } + cuse_cmd_unlock(pccmd); + + uio->uio_segflg = UIO_USERSPACE;/* restore segment flag */ + + if (error == EWOULDBLOCK) + cuse_client_kqfilter_poll(dev, pcc); + + return (error); +} + +static int +cuse_client_write(struct cdev *dev, struct uio *uio, int ioflag) +{ + struct cuse_client_command *pccmd; + struct cuse_client *pcc; + int error; + int len; + + error = cuse_client_get(&pcc); + if (error != 0) + return (error); + + pccmd = &pcc->cmds[CUSE_CMD_WRITE]; + + if (uio->uio_segflg != UIO_USERSPACE) { + return (EINVAL); + } + uio->uio_segflg = UIO_NOCOPY; + + cuse_cmd_lock(pccmd); + + while (uio->uio_resid != 0) { + + if (uio->uio_iov->iov_len > CUSE_LENGTH_MAX) { + error = ENOMEM; + break; + } + + len = uio->uio_iov->iov_len; + + cuse_lock(); + cuse_client_send_command_locked(pccmd, + (unsigned long)uio->uio_iov->iov_base, + (unsigned long)(unsigned int)len, pcc->fflags, ioflag); + + error = cuse_client_receive_command_locked(pccmd, 0, 0); + cuse_unlock(); + + if (error < 0) { + error = cuse_convert_error(error); + break; + } else if (error == len) { + error = uiomove(NULL, error, uio); + if (error) + break; + } else { + error = uiomove(NULL, error, uio); + break; + } + } + cuse_cmd_unlock(pccmd); + + uio->uio_segflg = UIO_USERSPACE;/* restore segment flag */ + + if (error == EWOULDBLOCK) + cuse_client_kqfilter_poll(dev, pcc); + + return (error); +} + +int +cuse_client_ioctl(struct cdev *dev, unsigned long cmd, + caddr_t data, int fflag, struct thread *td) +{ + struct cuse_client_command *pccmd; + struct cuse_client *pcc; + int error; + int len; + + error = cuse_client_get(&pcc); + if (error != 0) + return (error); + + len = IOCPARM_LEN(cmd); + if (len > CUSE_BUFFER_MAX) + return (ENOMEM); + + pccmd = &pcc->cmds[CUSE_CMD_IOCTL]; + + cuse_cmd_lock(pccmd); + + if (cmd & IOC_IN) + memcpy(pcc->ioctl_buffer, data, len); + + /* + * When the ioctl-length is zero drivers can pass information + * through the data pointer of the ioctl. Make sure this information + * is forwarded to the driver. + */ + + cuse_lock(); + cuse_client_send_command_locked(pccmd, + (len == 0) ? *(long *)data : CUSE_BUF_MIN_PTR, + (unsigned long)cmd, pcc->fflags, + (fflag & O_NONBLOCK) ? IO_NDELAY : 0); + + error = cuse_client_receive_command_locked(pccmd, data, len); + cuse_unlock(); + + if (error < 0) { + error = cuse_convert_error(error); + } else { + error = 0; + } + + if (cmd & IOC_OUT) + memcpy(data, pcc->ioctl_buffer, len); + + cuse_cmd_unlock(pccmd); + + if (error == EWOULDBLOCK) + cuse_client_kqfilter_poll(dev, pcc); + + return (error); +} + +static int +cuse_client_poll(struct cdev *dev, int events, struct thread *td) +{ + struct cuse_client_command *pccmd; + struct cuse_client *pcc; + unsigned long temp; + int error; + int revents; + + error = cuse_client_get(&pcc); + if (error != 0) + return (POLLNVAL); + + temp = 0; + + if (events & (POLLPRI | POLLIN | POLLRDNORM)) + temp |= CUSE_POLL_READ; + + if (events & (POLLOUT | POLLWRNORM)) + temp |= CUSE_POLL_WRITE; + + if (events & POLLHUP) + temp |= CUSE_POLL_ERROR; + + pccmd = &pcc->cmds[CUSE_CMD_POLL]; + + cuse_cmd_lock(pccmd); + + /* Need to selrecord() first to not loose any events. */ + if (temp != 0 && td != NULL) + selrecord(td, &pcc->server->selinfo); + + cuse_lock(); + cuse_client_send_command_locked(pccmd, + 0, temp, pcc->fflags, IO_NDELAY); + + error = cuse_client_receive_command_locked(pccmd, 0, 0); + cuse_unlock(); + + if (error < 0) { + revents = POLLNVAL; + } else { + revents = 0; + if (error & CUSE_POLL_READ) + revents |= (events & (POLLPRI | POLLIN | POLLRDNORM)); + if (error & CUSE_POLL_WRITE) + revents |= (events & (POLLOUT | POLLWRNORM)); + if (error & CUSE_POLL_ERROR) + revents |= (events & POLLHUP); + } + + cuse_cmd_unlock(pccmd); + + return (revents); +} + +static int +cuse_client_mmap(struct cdev *dev, vm_ooffset_t offset, vm_paddr_t *paddr, int nprot, vm_memattr_t *memattr) +{ + uint32_t page_nr = offset / PAGE_SIZE; + uint32_t alloc_nr = page_nr / CUSE_ALLOC_PAGES_MAX; + struct cuse_memory *mem; + struct cuse_server *pcs; + struct cuse_client *pcc; + uint8_t *ptr; + int error; + + if (alloc_nr >= CUSE_ALLOC_UNIT_MAX) + return (ENOMEM); + + error = cuse_client_get(&pcc); + if (error != 0) + pcs = NULL; + else + pcs = pcc->server; + + cuse_lock(); + mem = &cuse_mem[alloc_nr]; + + /* try to enforce slight ownership */ + if ((pcs != NULL) && (mem->owner != pcs)) { + cuse_unlock(); + return (EINVAL); + } + if (mem->virtaddr == NULL) { + cuse_unlock(); + return (ENOMEM); + } + if (mem->virtaddr == NBUSY) { + cuse_unlock(); + return (ENOMEM); + } + page_nr %= CUSE_ALLOC_PAGES_MAX; + + if (page_nr >= mem->page_count) { + cuse_unlock(); + return (ENXIO); + } + ptr = mem->virtaddr + (page_nr * PAGE_SIZE); + cuse_unlock(); + + *paddr = vtophys(ptr); + + return (0); +} + +static void +cuse_client_kqfilter_read_detach(struct knote *kn) +{ + struct cuse_client *pcc; + + cuse_lock(); + pcc = kn->kn_hook; + knlist_remove(&pcc->server->selinfo.si_note, kn, 1); + cuse_unlock(); +} + +static void +cuse_client_kqfilter_write_detach(struct knote *kn) +{ + struct cuse_client *pcc; + + cuse_lock(); + pcc = kn->kn_hook; + knlist_remove(&pcc->server->selinfo.si_note, kn, 1); + cuse_unlock(); +} + +static int +cuse_client_kqfilter_read_event(struct knote *kn, long hint) +{ + struct cuse_client *pcc; + + mtx_assert(&cuse_mtx, MA_OWNED); + + pcc = kn->kn_hook; + return ((pcc->cflags & CUSE_CLI_KNOTE_NEED_READ) ? 1 : 0); +} + +static int +cuse_client_kqfilter_write_event(struct knote *kn, long hint) +{ + struct cuse_client *pcc; + + mtx_assert(&cuse_mtx, MA_OWNED); + + pcc = kn->kn_hook; + return ((pcc->cflags & CUSE_CLI_KNOTE_NEED_WRITE) ? 1 : 0); +} + +static int +cuse_client_kqfilter(struct cdev *dev, struct knote *kn) +{ + struct cuse_client *pcc; + struct cuse_server *pcs; + int error; + + error = cuse_client_get(&pcc); + if (error != 0) + return (error); + + cuse_lock(); + pcs = pcc->server; + switch (kn->kn_filter) { + case EVFILT_READ: + pcc->cflags |= CUSE_CLI_KNOTE_HAS_READ; + kn->kn_hook = pcc; + kn->kn_fop = &cuse_client_kqfilter_read_ops; + knlist_add(&pcs->selinfo.si_note, kn, 1); + break; + case EVFILT_WRITE: + pcc->cflags |= CUSE_CLI_KNOTE_HAS_WRITE; + kn->kn_hook = pcc; + kn->kn_fop = &cuse_client_kqfilter_write_ops; + knlist_add(&pcs->selinfo.si_note, kn, 1); + break; + default: + error = EINVAL; + break; + } + cuse_unlock(); + + if (error == 0) + cuse_client_kqfilter_poll(dev, pcc); + return (error); +} diff --git a/sys/fs/cuse/cuse_defs.h b/sys/fs/cuse/cuse_defs.h new file mode 100644 index 000000000000..0134bfee8fdc --- /dev/null +++ b/sys/fs/cuse/cuse_defs.h @@ -0,0 +1,86 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2010-2012 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_DEFS_H_ +#define _CUSE_DEFS_H_ + +#define CUSE_VERSION 0x000122 + +#define CUSE_ERR_NONE 0 +#define CUSE_ERR_BUSY -1 +#define CUSE_ERR_WOULDBLOCK -2 +#define CUSE_ERR_INVALID -3 +#define CUSE_ERR_NO_MEMORY -4 +#define CUSE_ERR_FAULT -5 +#define CUSE_ERR_SIGNAL -6 +#define CUSE_ERR_OTHER -7 +#define CUSE_ERR_NOT_LOADED -8 + +#define CUSE_POLL_NONE 0 +#define CUSE_POLL_READ 1 +#define CUSE_POLL_WRITE 2 +#define CUSE_POLL_ERROR 4 + +#define CUSE_FFLAG_NONE 0 +#define CUSE_FFLAG_READ 1 +#define CUSE_FFLAG_WRITE 2 +#define CUSE_FFLAG_NONBLOCK 4 + +#define CUSE_DBG_NONE 0 +#define CUSE_DBG_FULL 1 + +/* maximum data transfer length */ +#define CUSE_LENGTH_MAX 0x7FFFFFFFU + +enum { + 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, +}; + +#define CUSE_MAKE_ID(a,b,c,u) ((((a) & 0x7F) << 24)| \ + (((b) & 0xFF) << 16)|(((c) & 0xFF) << 8)|((u) & 0xFF)) + +#define CUSE_ID_MASK 0x7FFFFF00U + +/* + * The following ID's are defined: + * =============================== + */ +#define CUSE_ID_DEFAULT(what) CUSE_MAKE_ID(0,0,what,0) +#define CUSE_ID_WEBCAMD(what) CUSE_MAKE_ID('W','C',what,0) /* Used by Webcamd. */ +#define CUSE_ID_SUNDTEK(what) CUSE_MAKE_ID('S','K',what,0) /* Used by Sundtek. */ +#define CUSE_ID_CX88(what) CUSE_MAKE_ID('C','X',what,0) /* Used by cx88 driver. */ +#define CUSE_ID_UHIDD(what) CUSE_MAKE_ID('U','D',what,0) /* Used by uhidd. */ + +#endif /* _CUSE_DEFS_H_ */ diff --git a/sys/fs/cuse/cuse_ioctl.h b/sys/fs/cuse/cuse_ioctl.h new file mode 100644 index 000000000000..8e1867cd009d --- /dev/null +++ b/sys/fs/cuse/cuse_ioctl.h @@ -0,0 +1,88 @@ +/* $FreeBSD$ */ +/*- + * 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_IOCTL_H_ +#define _CUSE_IOCTL_H_ + +#include +#include + +#define CUSE_BUFFER_MAX PAGE_SIZE +#define CUSE_DEVICES_MAX 64 /* units */ +#define CUSE_BUF_MIN_PTR 0x10000UL +#define CUSE_BUF_MAX_PTR 0x20000UL +#define CUSE_ALLOC_UNIT_MAX 128 /* units */ +#define CUSE_ALLOC_PAGES_MAX (((16UL * 1024UL * 1024UL) + PAGE_SIZE - 1) / PAGE_SIZE) + +struct cuse_dev; + +struct cuse_data_chunk { + unsigned long local_ptr; + unsigned long peer_ptr; + unsigned long length; +}; + +struct cuse_alloc_info { + unsigned long page_count; + unsigned long alloc_nr; +}; + +struct cuse_command { + struct cuse_dev *dev; + unsigned long fflags; + unsigned long per_file_handle; + unsigned long data_pointer; + unsigned long argument; + unsigned long command; /* see CUSE_CMD_XXX */ +}; + +struct cuse_create_dev { + struct cuse_dev *dev; + uid_t user_id; + gid_t group_id; + int permissions; + char devname[80]; /* /dev/xxxxx */ +}; + +/* Definition of internal IOCTLs for /dev/cuse */ + +#define CUSE_IOCTL_GET_COMMAND _IOR('C', 0, struct cuse_command) +#define CUSE_IOCTL_WRITE_DATA _IOW('C', 1, struct cuse_data_chunk) +#define CUSE_IOCTL_READ_DATA _IOW('C', 2, struct cuse_data_chunk) +#define CUSE_IOCTL_SYNC_COMMAND _IOW('C', 3, int) +#define CUSE_IOCTL_GET_SIG _IOR('C', 4, int) +#define CUSE_IOCTL_ALLOC_MEMORY _IOW('C', 5, struct cuse_alloc_info) +#define CUSE_IOCTL_FREE_MEMORY _IOW('C', 6, struct cuse_alloc_info) +#define CUSE_IOCTL_SET_PFH _IOW('C', 7, unsigned long) +#define CUSE_IOCTL_CREATE_DEV _IOW('C', 8, struct cuse_create_dev) +#define CUSE_IOCTL_DESTROY_DEV _IOW('C', 9, struct cuse_dev *) +#define CUSE_IOCTL_ALLOC_UNIT _IOR('C',10, int) +#define CUSE_IOCTL_FREE_UNIT _IOW('C',11, int) +#define CUSE_IOCTL_SELWAKEUP _IOW('C',12, int) +#define CUSE_IOCTL_ALLOC_UNIT_BY_ID _IOWR('C',13, int) +#define CUSE_IOCTL_FREE_UNIT_BY_ID _IOWR('C',14, int) + +#endif /* _CUSE_IOCTL_H_ */ diff --git a/sys/modules/cuse/Makefile b/sys/modules/cuse/Makefile new file mode 100644 index 000000000000..049eb7e0d858 --- /dev/null +++ b/sys/modules/cuse/Makefile @@ -0,0 +1,32 @@ +# $FreeBSD$ +# +# 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. +# + +.PATH: ${.CURDIR}/../../fs/cuse + +KMOD= cuse +SRCS= cuse.c device_if.h bus_if.h vnode_if.h opt_compat.h + +.include