Re: [PATCH v7 1/6] fuse: decouple fuse_ring creation from ent registration
Miklos Szeredi <[email protected]>
| Newsgroups | dev.linux.lists.fuse-devel |
|---|---|
| Message-ID | <CAJfpegt2NTn27A_QoMnfb8gDLZpRc9WHzWW6wGBjWc+nvJxg=A@mail.gmail.com> |
On Fri, 14 Aug 2026 at 21:00, Joanne Koong <[email protected]> wrote: > > Currently, the connection's fuse_ring is created lazily on the first > FUSE_IO_URING_CMD_REGISTER command. A server registers entries from one > thread per queue (one per CPU) and those threads issue their first > REGISTER command concurrently. They then race to create the single > per-connection fuse_ring, which required open-coded handling in > fuse_uring_create() to detect and protect against concurrent creations. > > Decouple fuse_ring creation from ent registration and move it to > FUSE_INIT reply processing after a server has negotiated and set > FUSE_OVER_IO_URING. The ring is published before the connection is > marked initialized. fuse_uring_register() no longer creates the ring and > it instead uses the ring set up at init time. I tested this with loraw (a "raw" loopback tester that doesn't use libfuse) and it fails with root@kvm:~# ./loraw -u /mnt/fuse loraw: loraw.c:1010: lo_start_uring: Assertion `!cqe->res' failed. cqe->res is -22 (EINVAL). Attaching the reproducer. To compile: cp $(KERNEL_TREE)/include/uapi/linux/fuse.h fuse_kernel.h gcc loraw.c -oloraw -luring Thanks, Miklos
loraw.c
(text/x-csrc, 26.5 KB)
#define _GNU_SOURCE
#define LO_NOTHREAD 1
#include "fuse_kernel.h"
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <stddef.h>
#include <stdbool.h>
#include <string.h>
#include <limits.h>
#include <dirent.h>
#include <assert.h>
#include <errno.h>
#include <err.h>
#include <inttypes.h>
#include <sched.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/mount.h>
#include <sys/mman.h>
#include <pthread.h>
#include <semaphore.h>
#include <sys/sysinfo.h>
#include <sys/sysmacros.h>
#include <liburing.h>
/* returns -1 on error */
#define ER(_expr) \
({ typeof(_expr) _ret = (_expr); if (_ret == (typeof(_expr)) -1) err(1, #_expr); _ret; })
/* returns errno on error */
#define PE(_expr) \
({ typeof(_expr) _ret = (_expr); if (_ret != 0) { errno = _ret; err(1, #_expr); } _ret; })
/* returns -errno on error */
#define NE(_expr) \
({ typeof(_expr) _ret = (_expr); if (_ret < 0) { errno = -_ret; err(1, #_expr); } _ret; })
/* returns NULL on error */
#define NL(_expr) \
({ typeof(_expr) _ret = (_expr); if (_ret == NULL) { errx(1, #_expr " returned NULL"); } _ret; })
struct lo_inode {
struct lo_inode *next; /* protected by lo->mutex */
struct lo_inode *prev; /* protected by lo->mutex */
int fd;
int backing_id;
dev_t dev;
uint64_t refcount; /* protected by lo->mutex */
struct fuse_attr attr;
};
struct lo_file {
union {
struct lo_file *next;
int fd;
struct {
DIR *dp;
struct dirent *entry;
off_t offset;
};
};
};
struct lo_config {
int debug;
int single;
int bind;
int map;
uint64_t timeout;
const char *source;
int nothread;
int direct;
int uring;
int queue_depth;
size_t req_size;
int passthrough;
int passthrough2;
const char *mnt;
};
struct lo_data {
pthread_mutex_t mutex;
#ifdef LO_NOTHREAD
#define LO_INODE_MAX 65536
struct lo_inode inodes[LO_INODE_MAX];
struct lo_inode *free_inodes;
#define LO_FILE_MAX 65536
struct lo_file files[LO_FILE_MAX];
struct lo_file *free_files;
sem_t sem;
#endif
struct lo_config c;
struct lo_inode root;
int devfd;
int inited;
};
struct lo_chan {
int fd;
void *inbuf;
void *outbuf;
size_t bufsize;
};
struct lo_ring_req {
struct io_uring *ring;
int qid;
struct fuse_uring_req_header *rreq;
struct iovec iov[2];
uint64_t unique;
};
struct lo_req {
struct lo_data *lo;
int is_ch;
union {
struct lo_chan ch;
struct lo_ring_req rr;
};
};
#ifdef LO_NOTHREAD
static inline int lo_nothread(struct lo_data *lo)
{
return lo->c.nothread;
}
static inline void lo_mutex_init_nt(struct lo_data *lo)
{
sem_init(&lo->sem, 1, 1);
}
static inline void lo_mutex_lock_nt(struct lo_data *lo)
{
sem_wait(&lo->sem);
}
static inline void lo_mutex_unlock_nt(struct lo_data *lo)
{
sem_post(&lo->sem);
}
static inline struct lo_inode *lo_alloc_inode_nt(struct lo_data *lo)
{
struct lo_inode *inode;
lo_mutex_lock_nt(lo);
inode = lo->free_inodes;
if (inode)
lo->free_inodes = inode->next;
lo_mutex_unlock_nt(lo);
memset(inode, 0, sizeof(*inode));
return inode;
}
static inline struct lo_file *lo_alloc_file_nt(struct lo_data *lo)
{
struct lo_file *lf;
lo_mutex_lock_nt(lo);
lf = lo->free_files;
if (lf)
lo->free_files = lf->next;
lo_mutex_unlock_nt(lo);
memset(lf, 0, sizeof(*lf));
return lf;
}
static inline void lo_free_inode_locked_nt(struct lo_data *lo,
struct lo_inode *inode)
{
inode->next = lo->free_inodes;
lo->free_inodes = inode;
}
static inline void lo_free_file_locked_nt(struct lo_data *lo,
struct lo_file *lf)
{
lf->next = lo->free_files;
lo->free_files = lf;
}
static inline void lo_free_inode_nt(struct lo_data *lo, struct lo_inode *inode)
{
lo_mutex_lock_nt(lo);
lo_free_inode_locked_nt(lo, inode);
lo_mutex_unlock_nt(lo);
}
static inline void lo_free_file_nt(struct lo_data *lo, struct lo_file *lf)
{
lo_mutex_lock_nt(lo);
lo_free_file_locked_nt(lo, lf);
lo_mutex_unlock_nt(lo);
}
static inline struct lo_data *lo_alloc_lo_nt(void)
{
struct lo_data *lo;
unsigned int i;
lo = ER(mmap(NULL, sizeof(struct lo_data), PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_ANONYMOUS, -1, 0));
for (i = 0; i < LO_INODE_MAX; i++)
lo_free_inode_locked_nt(lo, &lo->inodes[i]);
for (i = 0; i < LO_FILE_MAX; i++)
lo_free_file_locked_nt(lo, &lo->files[i]);
return lo;
}
#else
#define lo_nothread(lo) ((void) lo, 0)
#define lo_alloc_inode_nt(lo) NULL
#define lo_alloc_file_nt(lo) NULL
#define lo_free_inode_nt(lo, inode) abort()
#define lo_free_file_nt(lo, lf) abort()
#define lo_alloc_lo_nt() NULL
#define lo_mutex_init_nt(lo) abort()
#define lo_mutex_lock_nt(lo) abort()
#define lo_mutex_unlock_nt(lo) abort()
#endif
static void lo_mutex_init(struct lo_data *lo)
{
if (!lo_nothread(lo))
pthread_mutex_init(&lo->mutex, NULL);
else
lo_mutex_init_nt(lo);
}
static void lo_mutex_lock(struct lo_data *lo)
{
if (!lo_nothread(lo))
pthread_mutex_lock(&lo->mutex);
else
lo_mutex_lock_nt(lo);
}
static void lo_mutex_unlock(struct lo_data *lo)
{
if (!lo_nothread(lo))
pthread_mutex_unlock(&lo->mutex);
else
lo_mutex_unlock_nt(lo);
}
static struct lo_inode *lo_alloc_inode(struct lo_data *lo)
{
if (!lo_nothread(lo))
return calloc(1, sizeof(struct lo_inode));
else
return lo_alloc_inode_nt(lo);
}
static struct lo_file *lo_alloc_file(struct lo_data *lo)
{
if (!lo_nothread(lo))
return calloc(1, sizeof(struct lo_file));
else
return lo_alloc_file_nt(lo);
}
static void lo_free_inode(struct lo_data *lo, struct lo_inode *inode)
{
if (!lo_nothread(lo))
free(inode);
else
lo_free_inode_nt(lo, inode);
}
static void lo_free_file(struct lo_data *lo, struct lo_file *lf)
{
if (!lo_nothread(lo))
free(lf);
else
lo_free_file_nt(lo, lf);
}
static struct lo_inode *lo_inode(struct lo_data *lo, uint64_t ino)
{
if (ino == FUSE_ROOT_ID)
return &lo->root;
else
return (struct lo_inode *) (uintptr_t) ino;
}
static int lo_debug(struct lo_req *req)
{
return req->lo->c.debug;
}
static void lo_reply_ch(struct lo_req *req, int error, size_t argsize)
{
struct lo_chan *lc = &req->ch;
struct fuse_in_header *inh = lc->inbuf;
struct fuse_out_header *outh = lc->outbuf;
outh->len = sizeof(struct fuse_out_header) + argsize;
outh->error = -error;
outh->unique = inh->unique;
ER(write(lc->fd, lc->outbuf, outh->len));
}
static void lo_queue_uring(struct lo_req *req, int cmd_op)
{
struct io_uring_sqe *sqe;
struct fuse_uring_cmd_req *ureq;
sqe = NL(io_uring_get_sqe(req->rr.ring));
sqe->opcode = IORING_OP_URING_CMD;
sqe->flags = IOSQE_FIXED_FILE;
sqe->fd = 0;
sqe->rw_flags = 0;
sqe->ioprio = 0;
sqe->off = 0;
sqe->cmd_op = cmd_op;
sqe->__pad1 = 0;
ureq = (struct fuse_uring_cmd_req *) sqe->cmd;
ureq->qid = req->rr.qid;
ureq->commit_id = req->rr.rreq->ring_ent_in_out.commit_id;
ureq->flags = 0;
io_uring_sqe_set_data(sqe, req);
}
static void lo_reply_uring(struct lo_req *req, int error, size_t argsize)
{
struct fuse_uring_req_header *rreq = req->rr.rreq;
struct fuse_out_header *out = (struct fuse_out_header *)&rreq->in_out;
struct fuse_uring_ent_in_out *ent_in_out = &rreq->ring_ent_in_out;
ent_in_out->payload_sz = argsize;
out->len = sizeof(struct fuse_out_header) + argsize;
out->error = -error;
out->unique = req->rr.unique;
lo_queue_uring(req, FUSE_IO_URING_CMD_COMMIT_AND_FETCH);
NE(io_uring_submit(req->rr.ring));
}
static void lo_reply(struct lo_req *req, int error, size_t argsize)
{
if (lo_debug(req)) {
fprintf(stderr, " error: %i, outsize: %zu\n", error,
sizeof(struct fuse_out_header) + argsize);
}
if (req->is_ch)
lo_reply_ch(req, error, argsize);
else
lo_reply_uring(req, error, argsize);
}
static void *lo_out_arg(struct lo_req *req)
{
if (req->is_ch)
return ((struct fuse_out_header *) req->ch.outbuf) + 1;
else
return req->rr.iov[1].iov_base;
}
static bool lo_overflow(struct lo_req *req, size_t size)
{
if (req->is_ch)
return size > req->ch.bufsize - sizeof(struct fuse_out_header);
else
return size > req->rr.iov[1].iov_len;
}
static void lo_convert_stat(const struct statx *stat, struct fuse_attr *attr)
{
memset(attr, 0, sizeof(*attr));
attr->ino = stat->stx_ino;
attr->mode = stat->stx_mode;
attr->nlink = stat->stx_nlink;
attr->uid = stat->stx_uid;
attr->gid = stat->stx_gid;
attr->rdev = makedev(stat->stx_rdev_major, stat->stx_rdev_minor);
attr->size = stat->stx_size;
attr->blksize = stat->stx_blksize;
attr->blocks = stat->stx_blocks;
attr->atime = stat->stx_atime.tv_sec;
attr->mtime = stat->stx_mtime.tv_sec;
attr->ctime = stat->stx_ctime.tv_sec;
attr->atimensec = stat->stx_atime.tv_nsec;
attr->mtimensec = stat->stx_mtime.tv_nsec;
attr->ctimensec = stat->stx_ctime.tv_nsec;
}
static void lo_getattr(struct lo_req *req, struct fuse_in_header *inh,
struct fuse_getattr_in *inarg)
{
struct lo_data *lo = req->lo;
struct fuse_attr_out *outarg = lo_out_arg(req);
(void) inarg;
if (lo_debug(req))
fprintf(stderr, "lo_getattr(ino=%"PRIu64")\n", inh->nodeid);
outarg->attr_valid = lo->c.timeout;
outarg->attr_valid_nsec = 0;
outarg->dummy = 0;
outarg->attr = lo_inode(lo, inh->nodeid)->attr;
lo_reply(req, 0, sizeof(*outarg));
}
static struct lo_inode *lo_find(struct lo_data *lo, struct statx *st)
{
struct lo_inode *p;
struct lo_inode *ret = NULL;
lo_mutex_lock(lo);
for (p = lo->root.next; p != &lo->root; p = p->next) {
if (p->attr.ino == st->stx_ino && p->dev == makedev(st->stx_dev_major, st->stx_dev_minor)) {
assert(p->refcount > 0);
ret = p;
ret->refcount++;
break;
}
}
lo_mutex_unlock(lo);
return ret;
}
static void lo_lookup(struct lo_req *req, struct fuse_in_header *inh,
char *name)
{
struct lo_data *lo = req->lo;
struct lo_inode *inode, *parent = lo_inode(lo, inh->nodeid);
struct fuse_entry_out *outarg = lo_out_arg(req);
struct statx stat;
int newfd, res, saverr;
if (lo_debug(req)) {
fprintf(stderr, " lo_lookup(parent=%"PRIu64", name=%s)\n",
inh->nodeid, name);
}
newfd = openat(parent->fd, name, O_PATH | O_NOFOLLOW);
if (newfd == -1)
goto out_err;
res = statx(newfd, "", AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW, STATX_BASIC_STATS, &stat);
if (res == -1)
goto out_err;
inode = lo_find(lo, &stat);
if (inode) {
close(newfd);
newfd = -1;
} else {
struct lo_inode *prev, *next;
saverr = ENOMEM;
inode = lo_alloc_inode(lo);
if (!inode)
goto out_err;
inode->refcount = 1;
inode->fd = newfd;
inode->dev = makedev(stat.stx_dev_major, stat.stx_dev_minor);
lo_convert_stat(&stat, &inode->attr);
lo_mutex_lock(lo);
prev = &lo->root;
next = prev->next;
next->prev = inode;
inode->next = next;
inode->prev = prev;
prev->next = inode;
lo_mutex_unlock(lo);
}
memset(outarg, 0, sizeof(*outarg));
outarg->nodeid = (uintptr_t) inode;
outarg->entry_valid = lo->c.timeout;
outarg->attr_valid = lo->c.timeout;
outarg->attr = inode->attr;
#if 0
/* FIXME: name gets overwritten with fuse_uring */
if (lo_debug(req)) {
fprintf(stderr, " %"PRIu64"/%s -> %"PRIu64"\n",
inh->nodeid, name, outarg->nodeid);
}
#endif
lo_reply(req, 0, sizeof(*outarg));
return;
out_err:
saverr = errno;
if (newfd != -1)
close(newfd);
lo_reply(req, saverr, 0);
}
static int lo_backing_open(struct lo_data *lo, int fd)
{
struct fuse_backing_map map = { .fd = fd };
int backing_id;
backing_id = ER(ioctl(lo->devfd, FUSE_DEV_IOC_BACKING_OPEN, &map));
if (lo->c.debug)
fprintf(stderr, "backing_open(%i) = %i\n", fd, backing_id);
return backing_id;
}
static void lo_backing_close(struct lo_data *lo, int backing_id)
{
if (lo->c.debug)
fprintf(stderr, "backing_close(%i)\n", backing_id);
ER(ioctl(lo->devfd, FUSE_DEV_IOC_BACKING_CLOSE, &backing_id));
}
static void lo_open(struct lo_req *req, struct fuse_in_header *inh,
struct fuse_open_in *inarg)
{
struct lo_data *lo = req->lo;
struct lo_inode *inode = lo_inode(lo, inh->nodeid);
struct fuse_open_out *outarg = lo_out_arg(req);
struct lo_file *lf;
char buf[64];
int fd;
int o_direct = lo->c.direct ? O_DIRECT : 0;
if (lo->c.passthrough) {
if (!inode->backing_id)
inode->backing_id = lo_backing_open(lo, inode->fd);
memset(outarg, 0, sizeof(*outarg));
outarg->open_flags = FOPEN_PASSTHROUGH;
outarg->backing_id = inode->backing_id;
lo_reply(req, 0, sizeof(*outarg));
return;
}
sprintf(buf, "/proc/self/fd/%i", inode->fd);
fd = open(buf, (inarg->flags & O_ACCMODE) | o_direct);
if (fd == -1) {
lo_reply(req, errno, 0);
return;
}
lf = NL(lo_alloc_file(lo));
memset(outarg, 0, sizeof(*outarg));
outarg->fh = (uintptr_t) lf;
outarg->open_flags = FOPEN_KEEP_CACHE;
if (lo->c.passthrough2) {
int backing_id = lo_backing_open(lo, fd);
outarg->open_flags = FOPEN_PASSTHROUGH;
outarg->backing_id = backing_id;
close(fd);
lf->fd = backing_id;
} else {
lf->fd = fd;
}
lo_reply(req, 0, sizeof(*outarg));
}
static struct lo_file *lo_file(uint64_t fh)
{
return (void *) (uintptr_t) fh;
}
static void lo_release(struct lo_req *req, struct fuse_in_header *inh,
struct fuse_release_in *inarg)
{
struct lo_file *lf = lo_file(inarg->fh);
(void) inh;
/* No lo_file for passthrough */
if (lf) {
if (req->lo->c.passthrough2)
lo_backing_close(req->lo, lf->fd);
else
close(lf->fd);
lo_free_file(req->lo, lf);
}
lo_reply(req, 0, 0);
}
static void lo_read(struct lo_req *req, struct fuse_in_header *inh,
struct fuse_read_in *inarg)
{
char *outarg = lo_out_arg(req);
struct lo_file *lf = lo_file(inarg->fh);
ssize_t res;
(void) inh;
if (lo_overflow(req, inarg->size)) {
lo_reply(req, EOVERFLOW, 0);
return;
}
res = pread(lf->fd, outarg, inarg->size, inarg->offset);
if (res == -1) {
lo_reply(req, errno, 0);
return;
}
lo_reply(req, 0, res);
}
static void lo_opendir(struct lo_req *req, struct fuse_in_header *inh,
struct fuse_open_in *inarg)
{
struct lo_data *lo = req->lo;
struct lo_inode *inode = lo_inode(lo, inh->nodeid);
struct fuse_open_out *outarg = lo_out_arg(req);
struct lo_file *lf;
int fd;
DIR *dp;
(void) inarg;
fd = openat(inode->fd, ".", O_RDONLY);
if (fd == -1) {
lo_reply(req, errno, 0);
return;
}
dp = fdopendir(fd);
if (dp == NULL) {
int saverr = errno;
close(fd);
lo_reply(req, saverr, 0);
return;
}
lf = NL(lo_alloc_file(lo));
memset(outarg, 0, sizeof(*outarg));
outarg->fh = (uintptr_t) lf;
outarg->open_flags = 0;
lf->dp = dp;
lo_reply(req, 0, sizeof(*outarg));
}
static void lo_releasedir(struct lo_req *req, struct fuse_in_header *inh,
struct fuse_release_in *inarg)
{
struct lo_file *lf = lo_file(inarg->fh);
(void) inh;
closedir(lf->dp);
lo_free_file(req->lo, lf);
lo_reply(req, 0, 0);
}
static void lo_readdir(struct lo_req *req, struct fuse_in_header *inh,
struct fuse_read_in *inarg)
{
void *p = lo_out_arg(req);
struct lo_file *lf = lo_file(inarg->fh);
size_t rem = inarg->size, orig_size = inarg->size;
int err = 0;
const char *name;
size_t namelen, entlen, entlen_padded;
struct fuse_dirent *dirent;
off_t nextoff;
(void) inh;
if (lo_overflow(req, inarg->size)) {
lo_reply(req, EOVERFLOW, 0);
return;
}
if ((off_t) inarg->offset != lf->offset) {
seekdir(lf->dp, inarg->offset);
lf->entry = NULL;
lf->offset = inarg->offset;
}
while (1) {
if (!lf->entry) {
errno = 0;
lf->entry = readdir(lf->dp);
if (!lf->entry) {
if (errno) {
err = errno;
break;
} else {
break;
}
}
}
nextoff = lf->entry->d_off;
name = lf->entry->d_name;
namelen = strlen(name);
entlen = FUSE_NAME_OFFSET + namelen;
entlen_padded = FUSE_DIRENT_ALIGN(entlen);
if (entlen_padded > rem)
break;
dirent = (struct fuse_dirent *) p;
dirent->ino = lf->entry->d_ino;
dirent->off = nextoff;
dirent->namelen = namelen;
dirent->type = lf->entry->d_type;
memcpy(dirent->name, name, namelen);
memset(dirent->name + namelen, 0, entlen_padded - entlen);
p += entlen_padded;
rem -= entlen_padded;
lf->entry = NULL;
lf->offset = nextoff;
}
if (err && rem == orig_size)
lo_reply(req, err, 0);
else
lo_reply(req, 0, orig_size - rem);
}
static void unref_inode(struct lo_data *lo, struct lo_inode *inode, uint64_t n)
{
if (!inode)
return;
lo_mutex_lock(lo);
assert(inode->refcount >= n);
inode->refcount -= n;
if (!inode->refcount) {
struct lo_inode *prev, *next;
prev = inode->prev;
next = inode->next;
next->prev = prev;
prev->next = next;
lo_mutex_unlock(lo);
if (inode->backing_id)
lo_backing_close(lo, inode->backing_id);
close(inode->fd);
lo_free_inode(lo, inode);
} else {
lo_mutex_unlock(lo);
}
}
static void lo_forget_one(struct lo_data *lo, uint64_t nodeid,
uint64_t nlookup)
{
struct lo_inode *inode = lo_inode(lo, nodeid);
if (lo->c.debug) {
fprintf(stderr, " forget %"PRIu64" %"PRIu64" -%"PRIu64"\n",
nodeid, inode->refcount, nlookup);
}
unref_inode(lo, inode, nlookup);
}
static void lo_forget(struct lo_data *lo, struct fuse_in_header *inh,
struct fuse_forget_in *inarg)
{
lo_forget_one(lo, inh->nodeid, inarg->nlookup);
}
static void lo_batch_forget(struct lo_data *lo, struct fuse_in_header *inh,
struct fuse_batch_forget_in *inarg,
struct fuse_forget_one *param)
{
unsigned int i;
(void) inh;
if (!param)
param = (void *) (inarg + 1);
for (i = 0; i < inarg->count; i++)
lo_forget_one(lo, param[i].nodeid, param[i].nlookup);
}
static void lo_init(struct lo_req *req, struct fuse_in_header *inh,
struct fuse_init_in *inarg)
{
struct fuse_init_out *outarg = lo_out_arg(req);
uint64_t inflags = inarg->flags;
uint64_t outflags;
(void) inh;
memset(outarg, 0, sizeof(*outarg));
if (inflags & FUSE_INIT_EXT)
inflags |= (uint64_t) inarg->flags2 << 32;
outflags = inflags & (FUSE_PARALLEL_DIROPS | FUSE_ASYNC_READ | FUSE_ASYNC_DIO | FUSE_INIT_EXT);
if (req->lo->c.passthrough || req->lo->c.passthrough2) {
if (!(inflags & FUSE_PASSTHROUGH))
errx(1, "passthrough mode not supported");
outflags |= FUSE_PASSTHROUGH;
outarg->max_stack_depth = 1;
}
if (req->lo->c.uring && !(inflags & FUSE_OVER_IO_URING))
errx(1, "uring mode not supported");
outarg->flags = outflags;
if (outflags & FUSE_INIT_EXT)
outarg->flags2 = outflags >> 32;
outarg->major = FUSE_KERNEL_VERSION;
outarg->minor = FUSE_KERNEL_MINOR_VERSION;
outarg->max_readahead = inarg->max_readahead;
req->lo->inited = 1;
lo_reply(req, 0, sizeof(*outarg));
}
static size_t lo_getreq(struct lo_chan *lc)
{
ssize_t res;
res = ER(read(lc->fd, lc->inbuf, lc->bufsize));
if ((size_t) res < sizeof(struct fuse_in_header))
errx(1, "short read from fuse device");
return res;
}
static void lo_process(struct lo_req *req, struct fuse_in_header *inh,
void *arg, void *payload, size_t len, struct io_uring_cqe *cqe)
{
if (lo_debug(req)) {
fprintf(stderr,
"%cunique: %"PRIu64", opcode: %i, nodeid: %"PRIu64", insize: %zu\n",
req->is_ch ? ' ' : cqe ? '.' : '*',
inh->unique, inh->opcode, inh->nodeid, len);
}
switch (inh->opcode) {
case FUSE_INIT:
assert(req->is_ch || !len);
lo_init(req, inh, arg);
break;
case FUSE_LOOKUP:
lo_lookup(req, inh, payload ? payload : arg);
break;
case FUSE_GETATTR:
assert(req->is_ch || !len);
lo_getattr(req, inh, arg);
break;
case FUSE_OPEN:
assert(req->is_ch || !len);
lo_open(req, inh, arg);
break;
case FUSE_RELEASE:
assert(req->is_ch || !len);
lo_release(req, inh, arg);
break;
case FUSE_READ:
assert(req->is_ch || !len);
lo_read(req, inh, arg);
break;
case FUSE_OPENDIR:
assert(req->is_ch || !len);
lo_opendir(req, inh, arg);
break;
case FUSE_RELEASEDIR:
assert(req->is_ch || !len);
lo_releasedir(req, inh, arg);
break;
case FUSE_READDIR:
assert(req->is_ch || !len);
lo_readdir(req, inh, arg);
break;
case FUSE_FORGET:
assert(req->is_ch || !len);
lo_forget(req->lo, inh, arg);
break;
case FUSE_BATCH_FORGET:
lo_batch_forget(req->lo, inh, arg, payload);
break;
default:
lo_reply(req, ENOSYS, 0);
}
}
static void lo_alloc_bufs(struct lo_chan *lc)
{
size_t outbuf_align = 0x20000;
size_t outbuf_allocsize = 0x60000;
size_t outbuf_offset = outbuf_align - sizeof(struct fuse_out_header);
lc->bufsize = 0x21000;
PE(posix_memalign(&lc->inbuf, 0x1000, lc->bufsize));
assert(outbuf_offset + lc->bufsize <= outbuf_allocsize);
PE(posix_memalign(&lc->outbuf, outbuf_align, outbuf_allocsize));
lc->outbuf += outbuf_offset;
}
struct lo_thread_data {
struct lo_data *lo;
int cpu;
};
static void lo_start_uring(struct lo_thread_data *ltd)
{
int fd, i;
struct lo_data *lo = ltd->lo;
struct io_uring ring;
struct lo_req *req;
fd = lo->devfd;
NE(io_uring_queue_init(lo->c.queue_depth, &ring, IORING_SETUP_SQE128));
NE(io_uring_register_files(&ring, &fd, 1));
for (i = 0; i < lo->c.queue_depth; i++) {
struct io_uring_sqe *sqe;
struct fuse_uring_cmd_req *ureq;
req = NL(calloc(1, sizeof(*req)));
req->lo = lo;
req->is_ch = 0;
req->rr.ring = &ring,
req->rr.qid = ltd->cpu;
/* Allocate header buffer (page-aligned) */
PE(posix_memalign((void **) &req->rr.rreq, 0x1000, sizeof(struct fuse_uring_req_header)));
/* Allocate payload buffer (page-aligned) */
req->rr.iov[1].iov_len = lo->c.req_size;
PE(posix_memalign((void **) &req->rr.iov[1].iov_base, 0x1000, req->rr.iov[1].iov_len));
if (lo->c.debug) {
fprintf(stderr, "NEW req=%p rreq=%p qid=%i\n",
req, req->rr.rreq, req->rr.qid);
}
sqe = NL(io_uring_get_sqe(&ring));
sqe->opcode = IORING_OP_URING_CMD;
sqe->flags = IOSQE_FIXED_FILE;
sqe->fd = 0;
sqe->cmd_op = FUSE_IO_URING_CMD_REGISTER;
req->rr.iov[0].iov_base = req->rr.rreq;
req->rr.iov[0].iov_len = sizeof(struct fuse_uring_req_header);
sqe->addr = (unsigned long) req->rr.iov;
sqe->len = 2;
ureq = (struct fuse_uring_cmd_req *) sqe->cmd;
ureq->qid = req->rr.qid;
io_uring_sqe_set_data(sqe, req);
}
NE(io_uring_submit(&ring));
for (;;) {
struct fuse_uring_req_header *rreq;
struct io_uring_cqe *cqe;
struct fuse_in_header *in;
struct fuse_uring_ent_in_out *ent_in_out;
NE(io_uring_wait_cqe(&ring, &cqe));
req = io_uring_cqe_get_data(cqe);
rreq = req->rr.rreq;
in = (struct fuse_in_header *)&rreq->in_out;
ent_in_out = &rreq->ring_ent_in_out;
if (lo->c.debug) {
fprintf(stderr, "CQE res=%d, commit_id=%lu, payload_sz=%u req=%p\n",
cqe->res, ent_in_out->commit_id, ent_in_out->payload_sz, req);
}
assert(!cqe->res);
req->rr.unique = in->unique;
lo_process(req, in, rreq->op_in, req->rr.iov[1].iov_base, ent_in_out->payload_sz, cqe);
io_uring_cqe_seen(req->rr.ring, cqe);
}
}
static void lo_loop(struct lo_data *lo, int fd)
{
struct lo_req req = {
.lo = lo,
.is_ch =1,
.ch.fd = fd,
};
lo_alloc_bufs(&req.ch);
while (1) {
struct fuse_in_header *inh = req.ch.inbuf;
void *arg = inh + 1;
size_t len;
len = lo_getreq(&req.ch);
lo_process(&req, inh, arg, NULL, len, NULL);
}
}
static void lo_process_init(struct lo_data *lo)
{
struct lo_req req = {
.lo = lo,
.is_ch = 1,
.ch.fd = lo->devfd,
};
struct fuse_in_header *inh;
void *arg;
size_t len;
lo_alloc_bufs(&req.ch);
inh = req.ch.inbuf;
arg = inh + 1;
len = lo_getreq(&req.ch);
lo_process(&req, inh, arg, NULL, len, NULL);
assert(lo->inited);
}
static void lo_start_common(struct lo_thread_data *ltd)
{
struct lo_data *lo = ltd->lo;
int devfd = lo->devfd;
int fd = devfd;
if (ltd->lo->c.uring) {
lo_start_uring(ltd);
return;
}
if (ltd->lo->c.bind) {
fd = ER(open("/dev/fuse", O_RDWR));
ER(ioctl(fd, FUSE_DEV_IOC_CLONE, &devfd));
}
lo_loop(lo, fd);
}
static void *lo_start_one(void *data)
{
struct lo_thread_data *ltd = data;
cpu_set_t one;
CPU_ZERO(&one);
CPU_SET(ltd->cpu, &one);
PE(pthread_setaffinity_np(pthread_self(), sizeof(one), &one));
lo_start_common(ltd);
return NULL;
}
static int lo_start_one_nt(void *data)
{
struct lo_thread_data *ltd = data;
cpu_set_t one;
CPU_ZERO(&one);
CPU_SET(ltd->cpu, &one);
ER(sched_setaffinity(0, sizeof(one), &one));
lo_start_common(data);
return 0;
}
static void lo_start_threads(struct lo_data *lo)
{
int i, n;
cpu_set_t set;
struct lo_thread_data *ltd;
ER(sched_getaffinity(0, sizeof(set), &set));
n = CPU_COUNT(&set);
for (i = 0; n && i < CPU_SETSIZE; i++) {
if (CPU_ISSET(i, &set)) {
ltd = NL(malloc(sizeof(*ltd)));
ltd->lo = lo;
ltd->cpu = i;
if (!lo_nothread(lo)) {
pthread_t id;
PE(pthread_create(&id, NULL, lo_start_one, ltd));
} else {
void *stack, *top;
size_t stack_size = 1048576;
stack = NL(malloc(stack_size));
top = stack + stack_size;
ER(clone(lo_start_one_nt, top, CLONE_FILES, ltd));
}
n--;
}
}
}
static void lo_mount(struct lo_data *lo)
{
int fs_fd, mnt_fd;
int ret;
fs_fd = ER(fsopen("fuse", 0));
ret = fsconfig(fs_fd, FSCONFIG_SET_FD, "fd", NULL, lo->devfd);
if (ret == -1) {
char opt[64];
snprintf(opt, sizeof(opt), "%i", lo->devfd);
ER(fsconfig(fs_fd, FSCONFIG_SET_STRING, "fd", opt, 0));
}
ER(fsconfig(fs_fd, FSCONFIG_SET_STRING, "rootmode", "40000", 0));
ER(fsconfig(fs_fd, FSCONFIG_SET_STRING, "user_id", "0", 0));
ER(fsconfig(fs_fd, FSCONFIG_SET_STRING, "group_id", "0", 0));
ER(fsconfig(fs_fd, FSCONFIG_CMD_CREATE, 0, 0, 0));
mnt_fd = ER(fsmount(fs_fd, 0, 0));
ER(move_mount(mnt_fd, "", AT_FDCWD, lo->c.mnt, MOVE_MOUNT_F_EMPTY_PATH));
close(mnt_fd);
close(fs_fd);
}
static void lo_usage(char *argv[])
{
errx(1, "usage: %s [-d] [-s] [-b] [-r] [-t] mountpoint", argv[0]);
}
int main(int argc, char *argv[])
{
struct lo_data *lo;
struct lo_config c = {};
char *devname = "/dev/fuse";
struct statx stat;
int ctr;
int delay_threads = 0;
if (argc < 2)
lo_usage(argv);
c.source = "/";
c.timeout = 999999;
for (ctr = 1; ctr < argc; ctr++) {
char *arg = argv[ctr];
if (arg[0] == '-') {
switch (arg[1]) {
case 'd':
c.debug = 1;
break;
case 's':
c.single = 1;
break;
case 'b':
c.bind = 1;
break;
case 'r':
c.direct = 1;
break;
case 'u':
c.uring = 1;
c.queue_depth = 24;
c.req_size = 1048576;
break;
case 'p':
c.passthrough = 1;
break;
case 'q':
c.passthrough2 = 1;
break;
#ifdef LO_NOTHREAD
case 't':
c.nothread = 1;
break;
#endif
default:
lo_usage(argv);
}
} else if (!c.mnt) {
c.mnt = arg;
} else {
lo_usage(argv);
}
}
if (c.nothread)
lo = lo_alloc_lo_nt();
else
lo = NL(calloc(1, sizeof(struct lo_data)));
lo->c = c;
lo_mutex_init(lo);
/* Don't mask creation mode, kernel already did that */
umask(0);
lo->root.next = lo->root.prev = &lo->root;
lo->root.refcount = 2;
lo->root.fd = ER(open(lo->c.source, O_PATH));
ER(statx(lo->root.fd, "", AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW, STATX_BASIC_STATS, &stat));
lo_convert_stat(&stat, &lo->root.attr);
lo->devfd = ER(open(devname, O_RDWR));
if (!lo->c.single) {
if (!lo->c.uring &&
ioctl(lo->devfd, FUSE_DEV_IOC_SYNC_INIT) == 0)
lo_start_threads(lo);
else
delay_threads = 1;
}
lo_mount(lo);
if (lo->c.uring)
lo_process_init(lo);
if (delay_threads)
lo_start_threads(lo);
lo_loop(lo, lo->devfd);
return 0;
}