This example demonstrates how to implement a character device in userspace ("CUSE"). This is only allowed for root. The character device should appear in /dev under the specified name. It can be tested with the cuse_client.c program.
You should now have a new /dev/mydevice character device. To "unmount" it, kill the "cuse" process.
#define FUSE_USE_VERSION 31
#include <cuse_lowlevel.h>
#include <fuse_opt.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include "ioctl.h"
static void *cusexmp_buf;
static size_t cusexmp_size;
static const char *usage =
"usage: cusexmp [options]\n"
"\n"
"options:\n"
"    --help|-h             print this help message\n"
"    --maj=MAJ|-M MAJ      device major number\n"
"    --min=MIN|-m MIN      device minor number\n"
"    --name=NAME|-n NAME   device name (mandatory)\n"
"    -d   -o debug         enable debug output (implies -f)\n"
"    -f                    foreground operation\n"
"    -s                    disable multi-threaded operation\n"
"\n";
static int cusexmp_resize(size_t new_size)
{
        void *new_buf;
        if (new_size == cusexmp_size)
                return 0;
        new_buf = realloc(cusexmp_buf, new_size);
        if (!new_buf && new_size)
                return -ENOMEM;
        if (new_size > cusexmp_size)
                memset(new_buf + cusexmp_size, 0, new_size - cusexmp_size);
        cusexmp_buf = new_buf;
        cusexmp_size = new_size;
        return 0;
}
static int cusexmp_expand(size_t new_size)
{
        if (new_size > cusexmp_size)
                return cusexmp_resize(new_size);
        return 0;
}
{
}
static void cusexmp_read(
fuse_req_t req, 
size_t size, off_t off,
 {
        (void)fi;
        if (off >= cusexmp_size)
                off = cusexmp_size;
        if (size > cusexmp_size - off)
                size = cusexmp_size - off;
}
static void cusexmp_write(
fuse_req_t req, 
const char *buf, 
size_t size,
 {
        (void)fi;
        if (cusexmp_expand(off + size)) {
                return;
        }
        memcpy(cusexmp_buf + off, buf, size);
}
static void fioc_do_rw(
fuse_req_t req, 
void *addr, 
const void *in_buf,
                        size_t in_bufsz, size_t out_bufsz, int is_read)
{
        const struct fioc_rw_arg *arg;
        struct iovec in_iov[2], out_iov[3], iov[3];
        size_t cur_size;
        
        in_iov[0].iov_base = addr;
        in_iov[0].iov_len = sizeof(*arg);
        if (!in_bufsz) {
                return;
        }
        arg = in_buf;
        in_buf += sizeof(*arg);
        in_bufsz -= sizeof(*arg);
        
        out_iov[0].iov_base =
                addr + offsetof(struct fioc_rw_arg, prev_size);
        out_iov[0].iov_len = sizeof(arg->prev_size);
        out_iov[1].iov_base =
                addr + offsetof(struct fioc_rw_arg, new_size);
        out_iov[1].iov_len = sizeof(arg->new_size);
        
        if (is_read) {
                out_iov[2].iov_base = arg->buf;
                out_iov[2].iov_len = arg->size;
                if (!out_bufsz) {
                        return;
                }
        } else {
                in_iov[1].iov_base = arg->buf;
                in_iov[1].iov_len = arg->size;
                if (arg->size && !in_bufsz) {
                        return;
                }
        }
        
        cur_size = cusexmp_size;
        iov[0].iov_base = &cur_size;
        iov[0].iov_len = sizeof(cur_size);
        iov[1].iov_base = &cusexmp_size;
        iov[1].iov_len = sizeof(cusexmp_size);
        if (is_read) {
                size_t off = arg->offset;
                size_t size = arg->size;
                if (off >= cusexmp_size)
                        off = cusexmp_size;
                if (size > cusexmp_size - off)
                        size = cusexmp_size - off;
                iov[2].iov_base = cusexmp_buf + off;
                iov[2].iov_len = size;
        } else {
                if (cusexmp_expand(arg->offset + in_bufsz)) {
                        return;
                }
                memcpy(cusexmp_buf + arg->offset, in_buf, in_bufsz);
        }
}
static void cusexmp_ioctl(
fuse_req_t req, 
int cmd, 
void *arg,
                           const void *in_buf, size_t in_bufsz, size_t out_bufsz)
{
        int is_read = 0;
        (void)fi;
        if (flags & FUSE_IOCTL_COMPAT) {
                return;
        }
        switch (cmd) {
        case FIOC_GET_SIZE:
                if (!out_bufsz) {
                        struct iovec iov = { arg, sizeof(size_t) };
                } else
                                         sizeof(cusexmp_size));
                break;
        case FIOC_SET_SIZE:
                if (!in_bufsz) {
                        struct iovec iov = { arg, sizeof(size_t) };
                } else {
                        cusexmp_resize(*(size_t *)in_buf);
                }
                break;
        case FIOC_READ:
                is_read = 1;
                
        case FIOC_WRITE:
                fioc_do_rw(req, arg, in_buf, in_bufsz, out_bufsz, is_read);
                break;
        default:
        }
}
struct cusexmp_param {
        unsigned                major;
        unsigned                minor;
        char                    *dev_name;
        int                     is_help;
};
#define CUSEXMP_OPT(t, p) { t, offsetof(struct cusexmp_param, p), 1 }
static const struct fuse_opt cusexmp_opts[] = {
         CUSEXMP_OPT("-M %u",            major),
        CUSEXMP_OPT("--maj=%u",         major),
        CUSEXMP_OPT("-m %u",            minor),
        CUSEXMP_OPT("--min=%u",         minor),
        CUSEXMP_OPT("-n %s",            dev_name),
        CUSEXMP_OPT("--name=%s",        dev_name),
};
static int cusexmp_process_arg(void *data, const char *arg, int key,
{
        struct cusexmp_param *param = data;
        (void)outargs;
        (void)arg;
        switch (key) {
        case 0:
                param->is_help = 1;
                fprintf(stderr, "%s", usage);
        default:
                return 1;
        }
}
static const struct cuse_lowlevel_ops cusexmp_clop = {
        .open           = cusexmp_open,
        .read           = cusexmp_read,
        .write          = cusexmp_write,
        .ioctl          = cusexmp_ioctl,
};
int main(int argc, char **argv)
{
        struct cusexmp_param param = { 0, 0, NULL, 0 };
        char dev_name[128] = "DEVNAME=";
        const char *dev_info_argv[] = { dev_name };
        struct cuse_info ci;
        int ret = 1;
        if (
fuse_opt_parse(&args, ¶m, cusexmp_opts, cusexmp_process_arg)) {
                 printf("failed to parse option\n");
                free(param.dev_name);
                goto out;
        }
        if (!param.is_help) {
                if (!param.dev_name) {
                        fprintf(stderr, "Error: device name missing\n");
                        goto out;
                }
                strncat(dev_name, param.dev_name, sizeof(dev_name) - sizeof("DEVNAME="));
                free(param.dev_name);
        }
        memset(&ci, 0, sizeof(ci));
        ci.dev_major = param.major;
        ci.dev_minor = param.minor;
        ci.dev_info_argc = 1;
        ci.dev_info_argv = dev_info_argv;
        ci.flags = CUSE_UNRESTRICTED_IOCTL;
        ret = cuse_lowlevel_main(args.
argc, args.
argv, &ci, &cusexmp_clop, NULL);
out:
        return ret;
}