aboutsummaryrefslogtreecommitdiff
path: root/lib/libcuse/cuse_lib.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/libcuse/cuse_lib.c')
-rw-r--r--lib/libcuse/cuse_lib.c794
1 files changed, 794 insertions, 0 deletions
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);
+}