WIP: add guse lowlevel
authorNikita Shubin <n.shubin@yadro.com>
Sat, 22 Feb 2025 05:12:11 +0000 (08:12 +0300)
committerNikita Shubin <n.shubin@yadro.com>
Thu, 13 Mar 2025 08:07:16 +0000 (11:07 +0300)
Signed-off-by: Nikita Shubin <n.shubin@yadro.com>
include/fuse_kernel.h
include/guse_lowlevel.h [new file with mode: 0644]
include/meson.build
lib/fuse_i.h
lib/fuse_lowlevel.c
lib/fuse_versionscript
lib/guse_lowlevel.c [new file with mode: 0644]
lib/meson.build

index d08b99d60f6fd6d0d072d01ad6bcc1b48da0a242..22f067210f23bc5b94e954532dea5500325b94c8 100644 (file)
@@ -637,6 +637,9 @@ enum fuse_opcode {
        /* CUSE specific operations */
        CUSE_INIT               = 4096,
 
+       /* GUSE specific operations */
+       GUSE_INIT               = 4097,
+
        /* Reserved opcodes: helpful to detect structure endian-ness */
        CUSE_INIT_BSWAP_RESERVED        = 1048576,      /* CUSE_INIT << 8 */
        FUSE_INIT_BSWAP_RESERVED        = 436207616,    /* FUSE_INIT << 24 */
diff --git a/include/guse_lowlevel.h b/include/guse_lowlevel.h
new file mode 100644 (file)
index 0000000..4dbc303
--- /dev/null
@@ -0,0 +1,84 @@
+/*
+  GUSE: GPIO character device in Userspace
+  Copyright (C) 2025 Nikita Shubin <n.shubin@yadro.com>
+
+  This program can be distributed under the terms of the GNU LGPLv2.
+  See the file COPYING.LIB.
+
+  Read example/cusexmp.c for usages.
+*/
+
+#ifndef GUSE_LOWLEVEL_H_
+#define GUSE_LOWLEVEL_H_
+
+#ifndef FUSE_USE_VERSION
+#define FUSE_USE_VERSION 29
+#endif
+
+#include "fuse_lowlevel.h"
+
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/uio.h>
+
+#define GUSE_DEVICE_INODE_FLAG      BIT_ULL(63)
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct fuse_session;
+
+struct guse_info {
+       unsigned        dev_major;
+       unsigned        dev_minor;
+       unsigned        dev_info_argc;
+       const char      **dev_info_argv;
+       unsigned        flags;
+};
+
+/*
+ * Most ops behave almost identically to the matching fuse_lowlevel
+ * ops except that they don't take @ino.
+ *
+ * init_done   : called after initialization is complete
+ * read                : always direct IO, simultaneous operations allowed
+ * ioctl       : might be in unrestricted mode depending on ci->flags
+ */
+struct guse_cdev_lowlevel_ops {
+       void (*init) (void *userdata, struct fuse_conn_info *conn);
+       void (*init_done) (void *userdata);
+       void (*destroy) (void *userdata);
+       void (*open) (fuse_req_t req, fuse_ino_t ino,
+                     struct fuse_file_info *fi);
+       void (*read) (fuse_req_t req, fuse_ino_t ino, size_t size, off_t off,
+                     struct fuse_file_info *fi);
+       void (*release) (fuse_req_t req, fuse_ino_t ino,
+                        struct fuse_file_info *fi);
+       void (*ioctl) (fuse_req_t req, fuse_ino_t ino, unsigned int cmd,
+                      void *arg, struct fuse_file_info *fi, unsigned flags,
+                      const void *in_buf, size_t in_bufsz, size_t out_bufsz);
+       void (*poll) (fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi,
+                     struct fuse_pollhandle *ph);
+};
+
+struct fuse_session *guse_lowlevel_new(struct fuse_args *args,
+                                      const struct guse_info *ci,
+                                      const struct guse_cdev_lowlevel_ops *clop,
+                                      void *userdata);
+
+struct fuse_session *guse_lowlevel_setup(int argc, char *argv[],
+                                        const struct guse_info *ci,
+                                        const struct guse_cdev_lowlevel_ops *clop,
+                                        int *multithreaded, void *userdata);
+
+void guse_lowlevel_teardown(struct fuse_session *se);
+
+int guse_lowlevel_main(int argc, char *argv[], const struct guse_info *ci,
+                      const struct guse_cdev_lowlevel_ops *clop, void *userdata);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* GUSE_LOWLEVEL_H_ */
index bf671977a5a6a9142bd67aceabd8a919e3d968d0..e40c43856fdda6870bff04d59290686ae850014c 100644 (file)
@@ -1,4 +1,5 @@
 libfuse_headers = [ 'fuse.h', 'fuse_common.h', 'fuse_lowlevel.h',
-                   'fuse_opt.h', 'cuse_lowlevel.h', 'fuse_log.h' ]
+                   'fuse_opt.h', 'cuse_lowlevel.h', 'fuse_log.h',
+              'guse_lowlevel.h' ]
 
 install_headers(libfuse_headers, subdir: 'fuse3')
index ea04c34f4e426e352a118439d63024bbcc0cc24a..74c768d2ca6630ed19942473d6d06effc9ccceff 100644 (file)
@@ -61,6 +61,7 @@ struct fuse_session {
        struct fuse_lowlevel_ops op;
        int got_init;
        struct cuse_data *cuse_data;
+       struct guse_data *guse_data;
        void *userdata;
        uid_t owner;
        struct fuse_conn_info conn;
@@ -186,6 +187,8 @@ void fuse_free_req(fuse_req_t req);
 
 void cuse_lowlevel_init(fuse_req_t req, fuse_ino_t nodeide, const void *inarg);
 
+void guse_lowlevel_init(fuse_req_t req, fuse_ino_t nodeide, const void *inarg);
+
 int fuse_start_thread(pthread_t *thread_id, void *(*func)(void *), void *arg);
 
 void fuse_buf_free(struct fuse_buf *buf);
index e48efa527417ff2348b7b45597d0e64be716fb8f..613f57fdbb90822b1ebf8500a81307c7e07a0b20 100644 (file)
@@ -2710,6 +2710,7 @@ static struct {
        [FUSE_COPY_FILE_RANGE] = { do_copy_file_range, "COPY_FILE_RANGE" },
        [FUSE_LSEEK]       = { do_lseek,       "LSEEK"       },
        [CUSE_INIT]        = { cuse_lowlevel_init, "CUSE_INIT"   },
+       [GUSE_INIT]        = { guse_lowlevel_init, "GUSE_INIT"   },
 };
 
 #define FUSE_MAXOP (sizeof(fuse_ll_ops) / sizeof(fuse_ll_ops[0]))
@@ -2809,14 +2810,20 @@ void fuse_session_process_buf_internal(struct fuse_session *se,
 
        err = EIO;
        if (!se->got_init) {
-               enum fuse_opcode expected;
+               if (se->guse_data && (in->opcode == GUSE_INIT))
+                       goto opcode_ok;
 
-               expected = se->cuse_data ? CUSE_INIT : FUSE_INIT;
-               if (in->opcode != expected)
-                       goto reply_err;
-       } else if (in->opcode == FUSE_INIT || in->opcode == CUSE_INIT)
+               if (se->cuse_data && (in->opcode == CUSE_INIT))
+                       goto opcode_ok;
+
+               if (in->opcode == FUSE_INIT)
+                       goto opcode_ok;
+
+               goto reply_err;
+       } else if (in->opcode == FUSE_INIT || in->opcode == CUSE_INIT || in->opcode == GUSE_INIT)
                goto reply_err;
 
+opcode_ok:
        err = EACCES;
        /* Implement -o allow_root */
        if (se->deny_others && in->uid != se->owner && in->uid != 0 &&
index 14cbca142271f941175b0f1868caccb190ca371f..2ae031e9bfc0116b6e6e94b18a74f67a417ccc23 100644 (file)
@@ -203,6 +203,11 @@ FUSE_3.17 {
                fuse_log_close_syslog;
 } FUSE_3.12;
 
+FUSE_3.18 {
+       global:
+               guse_lowlevel_main;
+} FUSE_3.17;
+
 # Local Variables:
 # indent-tabs-mode: t
 # End:
diff --git a/lib/guse_lowlevel.c b/lib/guse_lowlevel.c
new file mode 100644 (file)
index 0000000..3c5715c
--- /dev/null
@@ -0,0 +1,311 @@
+/*
+  GUSE: Character device in Userspace
+  Copyright (C) 2025       Nikita Shubin <n.shubin@yadro.com>
+
+  This program can be distributed under the terms of the GNU LGPLv2.
+  See the file COPYING.LIB.
+*/
+
+#include "fuse_config.h"
+#include "guse_lowlevel.h"
+#include "fuse_kernel.h"
+#include "fuse_i.h"
+#include "fuse_opt.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <errno.h>
+#include <unistd.h>
+
+struct guse_data {
+       struct guse_cdev_lowlevel_ops   glop;
+       unsigned                        max_read;
+       unsigned                        dev_major;
+       unsigned                        dev_minor;
+       unsigned                        flags;
+       unsigned                        dev_info_len;
+       char                            dev_info[];
+};
+
+static struct guse_cdev_lowlevel_ops *req_glop(fuse_req_t req)
+{
+       return &req->se->guse_data->glop;
+}
+
+static size_t guse_pack_info(int argc, const char **argv, char *buf)
+{
+       size_t size = 0;
+       int i;
+
+       for (i = 0; i < argc; i++) {
+               size_t len;
+
+               len = strlen(argv[i]) + 1;
+               size += len;
+               if (buf) {
+                       memcpy(buf, argv[i], len);
+                       buf += len;
+               }
+       }
+
+       return size;
+}
+
+static struct guse_data *guse_prep_data(const struct guse_info *ci,
+                                       const struct guse_cdev_lowlevel_ops *glop)
+{
+       struct guse_data *cd;
+       size_t dev_info_len;
+
+       dev_info_len = guse_pack_info(ci->dev_info_argc, ci->dev_info_argv,
+                                     NULL);
+
+       if (dev_info_len > CUSE_INIT_INFO_MAX) {
+               fuse_log(FUSE_LOG_ERR, "guse: dev_info (%zu) too large, limit=%u\n",
+                       dev_info_len, CUSE_INIT_INFO_MAX);
+               return NULL;
+       }
+
+       cd = calloc(1, sizeof(*cd) + dev_info_len);
+       if (!cd) {
+               fuse_log(FUSE_LOG_ERR, "guse: failed to allocate guse_data\n");
+               return NULL;
+       }
+
+       memcpy(&cd->glop, glop, sizeof(cd->glop));
+       cd->max_read = 131072;
+       cd->dev_major = ci->dev_major;
+       cd->dev_minor = ci->dev_minor;
+       cd->dev_info_len = dev_info_len;
+       cd->flags = ci->flags;
+       guse_pack_info(ci->dev_info_argc, ci->dev_info_argv, cd->dev_info);
+
+       return cd;
+}
+
+struct fuse_session *guse_lowlevel_new(struct fuse_args *args,
+                                      const struct guse_info *ci,
+                                      const struct guse_cdev_lowlevel_ops *glop,
+                                      void *userdata)
+{
+       struct fuse_lowlevel_ops lop;
+       struct guse_data *cd;
+       struct fuse_session *se;
+
+       cd = guse_prep_data(ci, glop);
+       if (!cd)
+               return NULL;
+
+       memset(&lop, 0, sizeof(lop));
+       lop.init        = glop->init;
+       lop.destroy     = glop->destroy;
+
+       /* TODO: sanity check */
+
+       /* TODO: they are all required for normal operations */
+       lop.open        = glop->open;
+       lop.read        = glop->read;
+       lop.release     = glop->release;
+       lop.ioctl       = glop->ioctl;
+       lop.poll        = glop->poll;
+
+       se = fuse_session_new(args, &lop, sizeof(lop), userdata);
+       if (!se)
+               goto out_fail;
+
+       se->guse_data = cd;
+
+       return se;
+
+out_fail:
+       free(cd);
+       return NULL;
+}
+
+static int guse_reply_init(fuse_req_t req, struct cuse_init_out *arg,
+                          char *dev_info, unsigned dev_info_len)
+{
+       struct iovec iov[3] = { { 0 } };
+
+       iov[1].iov_base = arg;
+       iov[1].iov_len = sizeof(struct cuse_init_out);
+       iov[2].iov_base = dev_info;
+       iov[2].iov_len = dev_info_len;
+
+       return fuse_send_reply_iov_nofree(req, 0, iov, 3);
+}
+
+void guse_lowlevel_init(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+{
+       struct fuse_init_in *arg = (struct fuse_init_in *) inarg;
+       struct cuse_init_out outarg;
+       struct fuse_session *se = req->se;
+       struct guse_data *cd = se->guse_data;
+       size_t bufsize = se->bufsize;
+       struct guse_cdev_lowlevel_ops *glop = req_glop(req);
+
+       (void) nodeid;
+       if (se->debug) {
+               fuse_log(FUSE_LOG_DEBUG, "GUSE_INIT: %u.%u\n", arg->major, arg->minor);
+               fuse_log(FUSE_LOG_DEBUG, "flags=0x%08x\n", arg->flags);
+       }
+       se->conn.proto_major = arg->major;
+       se->conn.proto_minor = arg->minor;
+       se->conn.capable = 0;
+       se->conn.want = 0;
+
+       if (arg->major < 7) {
+               fuse_log(FUSE_LOG_ERR, "guse: unsupported protocol version: %u.%u\n",
+                       arg->major, arg->minor);
+               fuse_reply_err(req, EPROTO);
+               return;
+       }
+
+       if (bufsize < FUSE_MIN_READ_BUFFER) {
+               fuse_log(FUSE_LOG_ERR, "guse: warning: buffer size too small: %zu\n",
+                       bufsize);
+               bufsize = FUSE_MIN_READ_BUFFER;
+       }
+
+       bufsize -= 4096;
+       if (bufsize < se->conn.max_write)
+               se->conn.max_write = bufsize;
+
+       se->got_init = 1;
+       if (se->op.init)
+               se->op.init(se->userdata, &se->conn);
+
+       memset(&outarg, 0, sizeof(outarg));
+       outarg.major = FUSE_KERNEL_VERSION;
+       outarg.minor = FUSE_KERNEL_MINOR_VERSION;
+       outarg.flags = cd->flags;
+       outarg.max_read = cd->max_read;
+       outarg.max_write = se->conn.max_write;
+       outarg.dev_major = cd->dev_major;
+       outarg.dev_minor = cd->dev_minor;
+
+       if (se->debug) {
+               fuse_log(FUSE_LOG_DEBUG, "   GUSE_INIT: %u.%u\n",
+                       outarg.major, outarg.minor);
+               fuse_log(FUSE_LOG_DEBUG, "   flags=0x%08x\n", outarg.flags);
+               fuse_log(FUSE_LOG_DEBUG, "   max_read=0x%08x\n", outarg.max_read);
+               fuse_log(FUSE_LOG_DEBUG, "   max_write=0x%08x\n", outarg.max_write);
+               fuse_log(FUSE_LOG_DEBUG, "   dev_major=%u\n", outarg.dev_major);
+               fuse_log(FUSE_LOG_DEBUG, "   dev_minor=%u\n", outarg.dev_minor);
+               fuse_log(FUSE_LOG_DEBUG, "   dev_info: %.*s\n", cd->dev_info_len,
+                       cd->dev_info);
+       }
+
+       guse_reply_init(req, &outarg, cd->dev_info, cd->dev_info_len);
+
+       if (glop->init_done)
+               glop->init_done(se->userdata);
+
+       fuse_free_req(req);
+}
+
+struct fuse_session *guse_lowlevel_setup(int argc, char *argv[],
+                                        const struct guse_info *ci,
+                                        const struct guse_cdev_lowlevel_ops *glop,
+                                        int *multithreaded, void *userdata)
+{
+       const char *devname = "/dev/guse";
+       static const struct fuse_opt kill_subtype_opts[] = {
+               FUSE_OPT_KEY("subtype=",  FUSE_OPT_KEY_DISCARD),
+               FUSE_OPT_END
+       };
+       struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+       struct fuse_session *se;
+       struct fuse_cmdline_opts opts;
+       int fd;
+       int res;
+
+       if (fuse_parse_cmdline(&args, &opts) == -1)
+               return NULL;
+       *multithreaded = !opts.singlethread;
+
+       /* Remove subtype= option */
+       res = fuse_opt_parse(&args, NULL, kill_subtype_opts, NULL);
+       if (res == -1)
+               goto out1;
+
+       /*
+        * Make sure file descriptors 0, 1 and 2 are open, otherwise chaos
+        * would ensue.
+        */
+       do {
+               fd = open("/dev/null", O_RDWR);
+               if (fd > 2)
+                       close(fd);
+       } while (fd >= 0 && fd <= 2);
+
+       se = guse_lowlevel_new(&args, ci, glop, userdata);
+       if (se == NULL)
+               goto out1;
+
+       fd = open(devname, O_RDWR);
+       if (fd == -1) {
+               if (errno == ENODEV || errno == ENOENT)
+                       fuse_log(FUSE_LOG_ERR, "guse: device not found, try 'modprobe guse' first\n");
+               else
+                       fuse_log(FUSE_LOG_ERR, "guse: failed to open %s: %s\n",
+                               devname, strerror(errno));
+               goto err_se;
+       }
+       se->fd = fd;
+
+       res = fuse_set_signal_handlers(se);
+       if (res == -1)
+               goto err_se;
+
+       res = fuse_daemonize(opts.foreground);
+       if (res == -1)
+               goto err_sig;
+
+       fuse_opt_free_args(&args);
+       return se;
+
+err_sig:
+       fuse_remove_signal_handlers(se);
+err_se:
+       fuse_session_destroy(se);
+out1:
+       free(opts.mountpoint);
+       fuse_opt_free_args(&args);
+       return NULL;
+}
+
+void guse_lowlevel_teardown(struct fuse_session *se)
+{
+       fuse_remove_signal_handlers(se);
+       fuse_session_destroy(se);
+}
+
+int guse_lowlevel_main(int argc, char *argv[], const struct guse_info *ci,
+                      const struct guse_cdev_lowlevel_ops *glop, void *userdata)
+{
+       struct fuse_session *se;
+       int multithreaded;
+       int res;
+
+       se = guse_lowlevel_setup(argc, argv, ci, glop, &multithreaded,
+                                userdata);
+       if (se == NULL)
+               return 1;
+
+       if (multithreaded) {
+               struct fuse_loop_config *config = fuse_loop_cfg_create();
+               res = fuse_session_loop_mt(se, config);
+               fuse_loop_cfg_destroy(config);
+       }
+       else
+               res = fuse_session_loop(se);
+
+       guse_lowlevel_teardown(se);
+       if (res == -1)
+               return 1;
+
+       return 0;
+}
index 34dd60772387194a13825c2137384a68d12b6dad..961f17f20328d92de06cf150aff5d6e644024635 100644 (file)
@@ -1,8 +1,9 @@
 libfuse_sources = ['fuse.c', 'fuse_i.h', 'fuse_loop.c', 'fuse_loop_mt.c',
                    'fuse_lowlevel.c', 'fuse_misc.h', 'fuse_opt.c',
                    'fuse_signals.c', 'buffer.c', 'cuse_lowlevel.c',
-                   'helper.c', 'modules/subdir.c', 'mount_util.c',
-                   'fuse_log.c', 'compat.c', 'util.c', 'util.h' ]
+                   'guse_lowlevel.c', 'helper.c', 'modules/subdir.c',
+                   'mount_util.c', 'fuse_log.c', 'compat.c',
+                   'util.c', 'util.h' ]
 
 if host_machine.system().startswith('linux')
    libfuse_sources += [ 'mount.c' ]