diff --git a/lib/libfuse/fuse.c b/lib/libfuse/fuse.c index 5587c734f83..dcc39010067 100644 --- a/lib/libfuse/fuse.c +++ b/lib/libfuse/fuse.c @@ -1,4 +1,4 @@ -/* $OpenBSD: fuse.c,v 1.59 2026/03/10 16:20:57 deraadt Exp $ */ +/* $OpenBSD: fuse.c,v 1.60 2026/06/17 13:29:01 helg Exp $ */ /* * Copyright (c) 2013 Sylvestre Gallon * @@ -82,6 +82,7 @@ static struct fuse_opt fuse_lib_opts[] = { FUSE_OPT_KEY("fmask=%o", KEY_STUB), FUSE_LIB_OPT("umask=", set_mode), FUSE_LIB_OPT("umask=%o", umask), + FUSE_OPT_KEY("max_write=", FUSE_OPT_KEY_KEEP), FUSE_OPT_END }; @@ -98,7 +99,6 @@ static struct fuse_opt fuse_mount_opts[] = { FUSE_MOUNT_OPT("fsname=%s", fsname), FUSE_MOUNT_OPT("max_read=%u", max_read), FUSE_OPT_KEY("max_readahead", KEY_STUB), - FUSE_OPT_KEY("max_write", KEY_STUB), FUSE_MOUNT_OPT("noatime", noatime), FUSE_MOUNT_OPT("nonempty", nonempty), FUSE_MOUNT_OPT("-r", rdonly), diff --git a/lib/libfuse/fuse_chan.c b/lib/libfuse/fuse_chan.c index 7613599d994..6d62d30f7bc 100644 --- a/lib/libfuse/fuse_chan.c +++ b/lib/libfuse/fuse_chan.c @@ -1,4 +1,4 @@ -/* $OpenBSD: fuse_chan.c,v 1.2 2026/01/22 11:53:31 helg Exp $ */ +/* $OpenBSD: fuse_chan.c,v 1.3 2026/06/17 13:29:01 helg Exp $ */ /* * Copyright (c) 2025 Helg Bredow * @@ -16,6 +16,7 @@ */ #include +#include #include "debug.h" #include "fuse_private.h" @@ -33,27 +34,9 @@ DEF(fuse_chan_fd); int fuse_chan_recv(struct fuse_chan **chp, char *buf, size_t size) { - struct fuse_chan *ch = *chp; - struct fusebuf *fbuf = (struct fusebuf *)buf; - struct iovec iov[2]; ssize_t n; - if (chp == NULL || *chp == NULL || buf == NULL) - return (-EINVAL); - - /* XXX - * This will change once the kernel protocol is updated to be compatible - * with Linux. - * buf is contiguous memory but our fbuf is separated into the header - * and io structs with a pointer to the data buffer so we need to - * overlay our fbuf with pointer to data buffer. - */ - iov[0].iov_base = fbuf; - iov[0].iov_len = sizeof(fbuf->fb_hdr) + sizeof(fbuf->FD); - iov[1].iov_base = fbuf->fb_dat; - iov[1].iov_len = size - (sizeof(fbuf->fb_hdr) + sizeof(fbuf->FD)); - - n = readv(ch->fd, iov, 2); + n = read((*chp)->fd, buf, size); if (n == -1) return (-errno); @@ -67,10 +50,8 @@ fuse_chan_send(struct fuse_chan *ch, const struct iovec iov[], size_t count) ssize_t n; n = writev(ch->fd, iov, count); - if (n == -1) { - DPERROR(__func__); + if (n == -1) return (-errno); - } return (0); } diff --git a/lib/libfuse/fuse_lowlevel.c b/lib/libfuse/fuse_lowlevel.c index 5a01a2d8f7e..8c2bdfd6612 100644 --- a/lib/libfuse/fuse_lowlevel.c +++ b/lib/libfuse/fuse_lowlevel.c @@ -1,4 +1,4 @@ -/* $OpenBSD: fuse_lowlevel.c,v 1.2 2026/01/29 06:04:27 helg Exp $ */ +/* $OpenBSD: fuse_lowlevel.c,v 1.3 2026/06/17 13:29:01 helg Exp $ */ /* * Copyright (c) 2025 Helg Bredow * @@ -16,6 +16,8 @@ */ #include +#include +#include #include #include #include @@ -25,28 +27,48 @@ #include "debug.h" #include "fuse_private.h" +#if defined(__clang__) || __GNUC_PREREQ__(4, 6) +/* confirm that constants in sys/fusebuf.h and fuse_lowlevel.h match */ +static_assert(FUSE_FATTR_MODE==FUSE_SET_ATTR_MODE, "definition mismatch"); +static_assert(FUSE_FATTR_UID==FUSE_SET_ATTR_UID, "definition mismatch"); +static_assert(FUSE_FATTR_GID==FUSE_SET_ATTR_GID, "definition mismatch"); +static_assert(FUSE_FATTR_SIZE==FUSE_SET_ATTR_SIZE, "definition mismatch"); +static_assert(FUSE_FATTR_ATIME==FUSE_SET_ATTR_ATIME, "definition mismatch"); +static_assert(FUSE_FATTR_MTIME==FUSE_SET_ATTR_MTIME, "definition mismatch"); +/* TODO: not implemented in kernel yet +static_assert(FUSE_FATTR_ATIME_NOW==FUSE_SET_ATTR_ATIME_NOW, + "definition mismatch"); +static_assert(FUSE_FATTR_MTIME_NOW==FUSE_SET_ATTR_MTIME_NOW, + "definition mismatch"); +*/ +#endif + enum { KEY_HELP, KEY_VERSION, KEY_DEBUG }; +/* options supported by fuse_lowlevel_new */ +#define FUSE_LIB_OPT(o, m) {o, offsetof(struct fuse_session, fci.m), 1} static const struct fuse_opt fuse_ll_opts[] = { + /* core options, also supported by fuse_parse_cmdline(3) */ FUSE_OPT_KEY("debug", KEY_DEBUG), FUSE_OPT_KEY("-d", KEY_DEBUG), FUSE_OPT_KEY("-h", KEY_HELP), FUSE_OPT_KEY("--help", KEY_HELP), FUSE_OPT_KEY("-V", KEY_VERSION), FUSE_OPT_KEY("--version", KEY_VERSION), - FUSE_OPT_KEY("max_read=", FUSE_OPT_KEY_DISCARD), + /* fuse_lowlevel_new(3) options */ + FUSE_LIB_OPT("max_write=%u", max_write), FUSE_OPT_END }; static void dump_version(void) { - fprintf(stderr, "FUSE library version: %d.%d\n", FUSE_MAJOR_VERSION, - FUSE_MINOR_VERSION); + fprintf(stderr, "OpenBSD FUSE library version: %d.%d\n", + FUSE_MAJOR_VERSION, FUSE_MINOR_VERSION); } static void @@ -70,12 +92,12 @@ ifuse_ll_opt_proc(void *data, const char *arg, int key, break; case KEY_DEBUG: ifuse_debug_init(); - return (1); + return (0); default: fprintf(stderr, "fuse: unknown option -- %s\n", arg); } - return -1; + return (-1); } struct fuse_session * @@ -89,11 +111,23 @@ fuse_lowlevel_new(struct fuse_args *fargs, if (se == NULL) return (NULL); - if (fuse_opt_parse(fargs, NULL, fuse_ll_opts, ifuse_ll_opt_proc) == -1) { + se->fci.proto_major = FUSE_KERNEL_VERSION; + se->fci.proto_minor = FUSE_KERNEL_MINOR_VERSION; + /* default that may be overridden by fargs */ + se->fci.max_write = FUSEBUFMAXSIZE; + + if (fuse_opt_parse(fargs, se, fuse_ll_opts, ifuse_ll_opt_proc) == -1) { free(se); return (NULL); } + /* validate parsed options */ + if (se->fci.max_write > FUSEBUFMAXSIZE) { + DPRINTF("libfuse: max_write %u too large, using %u instead.\n", + se->fci.max_write, FUSEBUFMAXSIZE); + se->fci.max_write = FUSEBUFMAXSIZE; + } + if (llops->create && !llops->mknod) DPRINTF("libfuse: WARNING: filesystem supports creating files " "but does not implement mknod. No new files can be " @@ -113,12 +147,33 @@ fuse_lowlevel_new(struct fuse_args *fargs, } DEF(fuse_lowlevel_new); +static void +ifuse_stat2attr(const struct stat *stbuf, struct fuse_attr *attr) +{ + memset(attr, 0, sizeof(*attr)); + attr->ino = stbuf->st_ino; + attr->size = stbuf->st_size; + attr->blocks = stbuf->st_blocks; + attr->atime = stbuf->st_atim.tv_sec; + attr->mtime = stbuf->st_mtim.tv_sec; + attr->ctime = stbuf->st_ctim.tv_sec; + attr->atimensec = stbuf->st_atim.tv_nsec; + attr->mtimensec = stbuf->st_mtim.tv_nsec; + attr->ctimensec = stbuf->st_ctim.tv_nsec; + attr->mode = stbuf->st_mode; + attr->nlink = stbuf->st_nlink; + attr->uid = stbuf->st_uid; + attr->gid = stbuf->st_gid; + attr->rdev = stbuf->st_rdev; + attr->blksize = stbuf->st_blksize; +} + static int -ifuse_reply(fuse_req_t req, const char *data, const size_t data_size, int err) +ifuse_reply(fuse_req_t req, const void *data, const size_t data_size, int err) { struct fusebuf *fbuf; + struct fuse_out_header hdr; struct iovec iov[2]; - size_t fbuf_size; /* check for sanity */ if (data == NULL && data_size > 0) { @@ -128,31 +183,28 @@ ifuse_reply(fuse_req_t req, const char *data, const size_t data_size, int err) } fbuf = req->fbuf; - fbuf_size = sizeof(fbuf->fb_hdr) + sizeof(fbuf->FD); - fbuf->fb_err = err; - fbuf->fb_len = data_size; + hdr.unique = fbuf->fb_uuid; + hdr.len = sizeof(hdr) + data_size; + hdr.error = err; - iov[0].iov_base = fbuf; - iov[0].iov_len = fbuf_size; + iov[0].iov_base = &hdr; + iov[0].iov_len = sizeof(hdr); iov[1].iov_base = (void *)data; iov[1].iov_len = data_size; - DPRINTF("errno: %d", fbuf->fb_err); + DPRINTF("errno: %d", err); return fuse_chan_send(req->ch, iov, 2); } -static int -ifuse_reply_ok(fuse_req_t req) -{ - return ifuse_reply(req, NULL, 0, 0); -} - +/* + * fuse_reply_err takes a non-negated errno but kernel expects negated errno. + */ int fuse_reply_err(fuse_req_t req, int err) { - return ifuse_reply(req, NULL, 0, err); + return ifuse_reply(req, NULL, 0, -err); } DEF(fuse_reply_err); @@ -173,50 +225,59 @@ DEF(fuse_reply_readlink); int fuse_reply_write(fuse_req_t req, size_t size) { - struct fusebuf *fbuf; + struct fuse_write_out out; - fbuf = req->fbuf; - fbuf->fb_io_len = size; + memset(&out, 0, sizeof(out)); + out.size = size; - return ifuse_reply_ok(req); + return ifuse_reply(req, &out, sizeof(out), 0); } DEF(fuse_reply_write); int fuse_reply_attr(fuse_req_t req, const struct stat *stbuf, double attr_timeout) { - struct fusebuf *fbuf; + struct fuse_attr_out out; - fbuf = req->fbuf; - fbuf->fb_attr = *stbuf; + memset(&out, 0, sizeof(out)); + ifuse_stat2attr(stbuf, &out.attr); - return ifuse_reply_ok(req); + return ifuse_reply(req, &out, sizeof(out), 0); } DEF(fuse_reply_attr); int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e) { - struct fusebuf *fbuf; + struct fuse_entry_out out; + + memset(&out, 0, sizeof(out)); + out.nodeid = e->ino; + out.generation = e->generation; + ifuse_stat2attr(&e->attr, &out.attr); - fbuf = req->fbuf; - fbuf->fb_attr = e->attr; - fbuf->fb_ino = e->ino; DPRINTF("inode: %llu\t", e->ino); - return ifuse_reply_ok(req); + return ifuse_reply(req, &out, sizeof(out), 0); } DEF(fuse_reply_entry); int fuse_reply_statfs(fuse_req_t req, const struct statvfs *stbuf) { - struct fusebuf *fbuf; + struct fuse_statfs_out out; - fbuf = req->fbuf; - fbuf->fb_stat = *stbuf; + memset(&out, 0, sizeof(out)); + out.st.bsize = stbuf->f_bsize; + out.st.frsize = stbuf->f_frsize; + out.st.blocks = stbuf->f_blocks; + out.st.bfree = stbuf->f_bfree; + out.st.bavail = stbuf->f_bavail; + out.st.files = stbuf->f_files; + out.st.ffree = stbuf->f_ffree; + out.st.namelen = stbuf->f_namemax; - return ifuse_reply_ok(req); + return ifuse_reply(req, &out, sizeof(out), 0); } DEF(fuse_reply_statfs); @@ -240,12 +301,13 @@ DEF(fuse_reply_create); int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *ffi) { - struct fusebuf *fbuf; + struct fuse_open_out out; - fbuf = req->fbuf; - fbuf->fb_io_fd = ffi->fh; + memset(&out, 0, sizeof(out)); + out.fh = ffi->fh; + out.open_flags = ffi->flags; - return ifuse_reply_ok(req); + return ifuse_reply(req, &out, sizeof(out), 0); } DEF(fuse_reply_open); @@ -256,22 +318,19 @@ fuse_reply_none(fuse_req_t req) } DEF(fuse_reply_none); -#define GENERIC_DIRSIZ(NLEN) \ -((sizeof (struct dirent) - (MAXNAMLEN+1)) + ((NLEN+1 + 7) &~ 7)) - size_t fuse_add_direntry(fuse_req_t req, char *buf, const size_t bufsize, const char *name, const struct stat *stbuf, off_t off) { - struct dirent *dir; + struct fuse_dirent *dir; size_t namelen; size_t len; if (name == NULL) return (0); - namelen = strnlen(name, MAXNAMLEN); - len = GENERIC_DIRSIZ(namelen); + namelen = strlen(name); + len = FUSE_DIRENT_ALIGN(FUSE_NAME_OFFSET + namelen); /* NULL buf is used to request size to be calculated */ if (buf == NULL || stbuf == NULL || req == NULL) @@ -281,13 +340,14 @@ fuse_add_direntry(fuse_req_t req, char *buf, const size_t bufsize, if (bufsize < len) return (len); - dir = (struct dirent *)buf; - dir->d_fileno = stbuf->st_ino; - dir->d_type = IFTODT(stbuf->st_mode); - dir->d_reclen = len; - dir->d_off = off; - strlcpy(dir->d_name, name, sizeof(dir->d_name)); - dir->d_namlen = strlen(dir->d_name); + dir = (struct fuse_dirent *)buf; + memset(dir, 0, len); + dir->ino = stbuf->st_ino; + dir->type = IFTODT(stbuf->st_mode); + dir->off = off; + dir->namelen = namelen; + /* name is not NUL-terminated */ + memcpy(dir->name, name, namelen); return (len); } diff --git a/lib/libfuse/fuse_lowlevel.h b/lib/libfuse/fuse_lowlevel.h index 6bd21d15e06..88c0854bbc0 100644 --- a/lib/libfuse/fuse_lowlevel.h +++ b/lib/libfuse/fuse_lowlevel.h @@ -1,4 +1,4 @@ -/* $OpenBSD: fuse_lowlevel.h,v 1.2 2026/01/22 11:53:31 helg Exp $ */ +/* $OpenBSD: fuse_lowlevel.h,v 1.3 2026/06/17 13:29:01 helg Exp $ */ /* * Copyright (c) 2025 Helg Bredow * @@ -126,7 +126,6 @@ void fuse_session_reset(struct fuse_session *); void fuse_session_process(struct fuse_session *, const char *, size_t, struct fuse_chan *); - /* * FUSE Channel API Prototypes */ @@ -161,7 +160,8 @@ const struct fuse_ctx *fuse_req_ctx(fuse_req_t); void *fuse_req_userdata(fuse_req_t); /* - * Bitmasks for setattr to indicate what to set. + * Bitmasks for setattr to indicate what to set. These must be the same + * as the FUSE_FATTR_* definitions in */ #define FUSE_SET_ATTR_MODE (1 << 0) #define FUSE_SET_ATTR_UID (1 << 1) diff --git a/lib/libfuse/fuse_ops.c b/lib/libfuse/fuse_ops.c index 9527191a464..5e2765c6175 100644 --- a/lib/libfuse/fuse_ops.c +++ b/lib/libfuse/fuse_ops.c @@ -1,4 +1,4 @@ -/* $OpenBSD: fuse_ops.c,v 1.42 2026/01/29 06:04:27 helg Exp $ */ +/* $OpenBSD: fuse_ops.c,v 1.43 2026/06/17 13:29:01 helg Exp $ */ /* * Copyright (c) 2013 Sylvestre Gallon * @@ -33,7 +33,7 @@ */ static fuse_req_t ireq; -const fuse_req_t +fuse_req_t ifuse_req(void) { return (ireq); @@ -192,9 +192,6 @@ out: fuse_reply_err(req, -err); } -#define GENERIC_DIRSIZ(NLEN) \ -((sizeof (struct dirent) - (MAXNAMLEN+1)) + ((NLEN+1 + 7) &~ 7)) - /* * This function adds one directory entry to the buffer. * FUSE file systems can implement readdir in one of two ways. @@ -265,11 +262,15 @@ ifuse_fill_readdir(void *dh, const char *name, const struct stat *stbuf, /* advance buf to start of next entry and now add it for real */ buf = (char *) fd->buf + fd->len; resid = fd->size - fd->len; - len = fuse_add_direntry(ifuse_req(), buf, resid, name, &attr, off); - fd->len += len; fd->idx += len; + if (off) + fuse_add_direntry(ifuse_req(), buf, resid, name, &attr, off); + else + fuse_add_direntry(ifuse_req(), buf, resid, name, &attr, + fd->idx); + return (0); } @@ -769,23 +770,23 @@ ifuse_ops_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr, } ictx_init(req); - if (flags & FUSE_FATTR_MODE) { + if (flags & FUSE_SET_ATTR_MODE) { if (f->op.chmod) err = f->op.chmod(realname, attr->st_mode); else err = ENOSYS; } - if (!err && (flags & FUSE_FATTR_UID || flags & FUSE_FATTR_GID)) { - uid = (flags & FUSE_FATTR_UID) ? attr->st_uid : (uid_t)-1; - gid = (flags & FUSE_FATTR_GID) ? attr->st_gid : (gid_t)-1; + if (!err && (flags & FUSE_SET_ATTR_UID || flags & FUSE_SET_ATTR_GID)) { + uid = (flags & FUSE_SET_ATTR_UID) ? attr->st_uid : (uid_t)-1; + gid = (flags & FUSE_SET_ATTR_GID) ? attr->st_gid : (gid_t)-1; if (f->op.chown) err = f->op.chown(realname, uid, gid); else err = ENOSYS; } - if (!err && (flags & FUSE_FATTR_MTIME || flags & FUSE_FATTR_ATIME)) { + if (!err && (flags & FUSE_SET_ATTR_MTIME || flags & FUSE_SET_ATTR_ATIME)) { if (f->op.utimens) { ts[0] = attr->st_atim; ts[1] = attr->st_mtim; @@ -798,7 +799,7 @@ ifuse_ops_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr, err = ENOSYS; } - if (!err && (flags & FUSE_FATTR_SIZE)) { + if (!err && (flags & FUSE_SET_ATTR_SIZE)) { if (f->op.truncate) err = f->op.truncate(realname, attr->st_size); else @@ -918,16 +919,16 @@ ifuse_ops_destroy(void *userdata) } static void -ifuse_ops_forget(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup /* XXX */) +ifuse_ops_forget(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup) { struct fuse *f = (struct fuse *)fuse_req_userdata(req); struct fuse_vnode *vn; vn = tree_get(&f->vnode_tree, ino); if (vn != NULL) - unref_vn(f, vn); + unref_vn(f, vn, nlookup); - fuse_reply_err(req, 0); + fuse_reply_none(req); } static void diff --git a/lib/libfuse/fuse_private.h b/lib/libfuse/fuse_private.h index 94885883252..02553fe5e6d 100644 --- a/lib/libfuse/fuse_private.h +++ b/lib/libfuse/fuse_private.h @@ -1,4 +1,4 @@ -/* $OpenBSD: fuse_private.h,v 1.28 2026/01/29 06:04:27 helg Exp $ */ +/* $OpenBSD: fuse_private.h,v 1.29 2026/06/17 13:29:01 helg Exp $ */ /* * Copyright (c) 2013 Sylvestre Gallon * @@ -58,10 +58,11 @@ SPLAY_HEAD(tree, treeentry); struct fuse_session { struct fuse_lowlevel_ops llops; - struct fuse_chan *chan; - void *userdata; - int init; - int exit; + struct fuse_conn_info fci; + struct fuse_chan *chan; + void *userdata; + int init; + int exit; }; struct fuse_chan { @@ -122,12 +123,13 @@ struct fuse_req { #define FUSE_ROOT_INO ((ino_t)1) /* fuse_ops.c */ -const fuse_req_t ifuse_req(void); +fuse_req_t ifuse_req(void); /* fuse_subr.c */ struct fuse_vnode *alloc_vn(struct fuse *, const char *, ino_t, ino_t); void ref_vn(struct fuse_vnode *); -void unref_vn(struct fuse *, struct fuse_vnode *); +void unref_vn(struct fuse *, struct fuse_vnode *, + const uint64_t); struct fuse_vnode *get_vn_by_name_and_parent(struct fuse *, const char *, ino_t); void remove_vnode_from_name_tree(struct fuse *, diff --git a/lib/libfuse/fuse_session.c b/lib/libfuse/fuse_session.c index 002ee0b351f..8da27216dcd 100644 --- a/lib/libfuse/fuse_session.c +++ b/lib/libfuse/fuse_session.c @@ -1,4 +1,4 @@ -/* $OpenBSD: fuse_session.c,v 1.1 2026/01/22 11:53:31 helg Exp $ */ +/* $OpenBSD: fuse_session.c,v 1.2 2026/06/17 13:29:01 helg Exp $ */ /* * Copyright (c) 2025 Helg Bredow * @@ -16,6 +16,7 @@ */ #include +#include #include #include #include @@ -83,8 +84,7 @@ int fuse_session_loop(struct fuse_session *se) { struct fuse_chan *ch; - struct fusebuf fbuf; - char *buf = (char *)&fbuf; + char *buf; size_t bufsize; int err; @@ -95,15 +95,25 @@ fuse_session_loop(struct fuse_session *se) if (ch == NULL) return (-1); - /* prepare the read and write data buffer */ - fbuf.fb_dat = calloc(1, FUSEBUFMAXSIZE); - if (fbuf.fb_dat == NULL) { + /* + * Prepare the read data buffer. We need enough space for the header, + * input struct and any additional data, filenames or the buffer for + * write(2). The minimum buffer size must be large enough for the + * name and path parameters for FUSE_SYMLINK. + */ + if (se->fci.max_write > FUSEBUFMAXSIZE) + bufsize = sizeof(struct fusebuf) + FUSEBUFMAXSIZE; + else if (se->fci.max_write < PATH_MAX + NAME_MAX) + bufsize = sizeof(struct fusebuf) + PATH_MAX + NAME_MAX; + else + bufsize = sizeof(struct fusebuf) + se->fci.max_write; + + buf = calloc(1, bufsize); + if (buf == NULL) { DPERROR(__func__); return (-1); } - bufsize = sizeof(fbuf.fb_hdr) + sizeof(fbuf.FD) + FUSEBUFMAXSIZE; - while (!fuse_session_exited(se)) { err = fuse_chan_recv(&ch, buf, bufsize); if (err == -EINTR || err == -ENODEV) { @@ -117,7 +127,7 @@ fuse_session_loop(struct fuse_session *se) fuse_session_process(se, buf, bufsize, ch); } - free(fbuf.fb_dat); + free(buf); fuse_session_reset(se); return (err == 0 ? 0 : -1); @@ -127,20 +137,31 @@ DEF(fuse_session_loop); static void iprocess_init(fuse_req_t req) { + struct fusebuf *fbuf = req->fbuf; struct fuse_session *se = req->se; - struct fuse_conn_info fci; + struct fuse_conn_info *fci = &se->fci; + struct fuse_init_out out; + uint32_t major = fbuf->in.init.major; + uint32_t minor = fbuf->in.init.minor; DPRINTF("%-11s", "init"); + DPRINTF("Kernel: %d.%d\t", major, minor); + DPRINTF("libfuse: %d.%d\t", fci->proto_major, fci->proto_minor); - if (se->llops.init) { - memset(&fci, 0, sizeof(fci)); - fci.proto_minor = FUSE_MINOR_VERSION; - fci.proto_major = FUSE_MAJOR_VERSION; + if (major != fci->proto_major && minor != fci->proto_minor) + errx(1, "FUSE kernel protocol version mismatch"); - se->llops.init(se->userdata, &fci); - } + if (se->llops.init) + se->llops.init(se->userdata, fci); - fuse_reply_err(req, 0); + memset(&out, 0, sizeof(out)); + out.major = fci->proto_major; + out.minor = fci->proto_minor; + out.max_write = fci->max_write; + + DPRINTF("max_write: %u\t", out.max_write); + + fuse_reply_buf(req, (const char *)&out, sizeof(out)); se->init = 1; } @@ -162,7 +183,7 @@ iprocess_lookup(fuse_req_t req) { struct fusebuf *fbuf = req->fbuf; struct fuse_session *se = req->se; - const char *name = fbuf->fb_dat; + const char *name = fb_dat(fbuf->hdr); DPRINTF("%-11s", "lookup"); DPRINTF("inode: %llu\t", fbuf->fb_ino); @@ -179,17 +200,13 @@ iprocess_getattr(fuse_req_t req) { struct fusebuf *fbuf = req->fbuf; struct fuse_session *se = req->se; - struct fuse_file_info ffi; DPRINTF("%-11s", "getattr"); DPRINTF("inode: %llu\t", fbuf->fb_ino); if (se->llops.getattr) { - memset(&ffi, 0, sizeof(ffi)); - ffi.fh = fbuf->fb_io_fd; - ffi.fh_old = ffi.fh; - - se->llops.getattr(req, fbuf->fb_ino, &ffi); + /* fuse_getattr_in is unused */ + se->llops.getattr(req, fbuf->fb_ino, NULL); } else fuse_reply_err(req, ENOSYS); } @@ -200,18 +217,28 @@ iprocess_setattr(fuse_req_t req) struct fusebuf *fbuf = req->fbuf; struct fuse_session *se = req->se; struct fuse_file_info ffi; - struct stat *stbuf = &fbuf->fb_attr; - struct fb_io *io = fbtod(fbuf, struct fb_io *); + struct stat stbuf; + const int flags = fbuf->in.setattr.valid; DPRINTF("%-11s", "setattr"); DPRINTF("inode: %llu\t", fbuf->fb_ino); if (se->llops.setattr) { memset(&ffi, 0, sizeof(ffi)); - ffi.fh = fbuf->fb_io_fd; + ffi.fh = fbuf->in.setattr.fh; ffi.fh_old = ffi.fh; - se->llops.setattr(req, fbuf->fb_ino, stbuf, io->fi_flags, &ffi); + memset(&stbuf, 0, sizeof(stbuf)); + stbuf.st_size = fbuf->in.setattr.size; + stbuf.st_atime = fbuf->in.setattr.atime; + stbuf.st_mtime = fbuf->in.setattr.mtime; + stbuf.st_atim.tv_nsec = fbuf->in.setattr.atimensec; + stbuf.st_mtim.tv_nsec = fbuf->in.setattr.mtimensec; + stbuf.st_mode = fbuf->in.setattr.mode; + stbuf.st_uid = fbuf->in.setattr.uid; + stbuf.st_gid = fbuf->in.setattr.gid; + + se->llops.setattr(req, fbuf->fb_ino, &stbuf, flags, &ffi); } else fuse_reply_err(req, ENOSYS); } @@ -227,7 +254,7 @@ iprocess_opendir(fuse_req_t req) DPRINTF("inode: %llu\t", fbuf->fb_ino); memset(&ffi, 0, sizeof(ffi)); - ffi.flags = fbuf->fb_io_flags; + ffi.flags = fbuf->in.open.flags; if (se->llops.opendir) se->llops.opendir(req, fbuf->fb_ino, &ffi); @@ -244,16 +271,16 @@ iprocess_readdir(fuse_req_t req) DPRINTF("%-11s", "readdir"); DPRINTF("inode: %llu\t", fbuf->fb_ino); - DPRINTF("size: %lu\t", fbuf->fb_io_len); - DPRINTF("offset: %llu\t", fbuf->fb_io_off); + DPRINTF("size: %u\t", fbuf->in.read.size); + DPRINTF("offset: %llu\t", fbuf->in.read.offset); if (se->llops.readdir) { memset(&ffi, 0, sizeof(ffi)); - ffi.fh = fbuf->fb_io_fd; + ffi.fh = fbuf->in.read.fh; ffi.fh_old = ffi.fh; - se->llops.readdir(req, fbuf->fb_ino, fbuf->fb_io_len, - fbuf->fb_io_off, &ffi); + se->llops.readdir(req, fbuf->fb_ino, fbuf->in.read.size, + fbuf->in.read.offset, &ffi); } else fuse_reply_err(req, ENOSYS); } @@ -270,9 +297,9 @@ iprocess_releasedir(fuse_req_t req) if (se->llops.releasedir) { memset(&ffi, 0, sizeof(ffi)); - ffi.fh = fbuf->fb_io_fd; + ffi.fh = fbuf->in.release.fh; ffi.fh_old = ffi.fh; - ffi.flags = fbuf->fb_io_flags; + ffi.flags = fbuf->in.release.flags; se->llops.releasedir(req, fbuf->fb_ino, &ffi); } else @@ -284,16 +311,17 @@ iprocess_mkdir(fuse_req_t req) { struct fusebuf *fbuf = req->fbuf; struct fuse_session *se = req->se; + const char *name = fb_dat(fbuf->in.mkdir); DPRINTF("%-11s", "mkdir"); DPRINTF("inode: %llu\t", fbuf->fb_ino); - DPRINTF("mode: %#6o\t", fbuf->fb_io_mode); - DPRINTF("name: %s\t", fbuf->fb_dat); + DPRINTF("mode: %#6o\t", fbuf->in.mkdir.mode); + DPRINTF("name: %s\t", name); - if (se->llops.mkdir) - se->llops.mkdir(req, fbuf->fb_ino, fbuf->fb_dat, - fbuf->fb_io_mode); - else + if (se->llops.mkdir) { + req->ctx.umask = fbuf->in.mkdir.umask; + se->llops.mkdir(req, fbuf->fb_ino, name, fbuf->in.mkdir.mode); + } else fuse_reply_err(req, ENOSYS); } @@ -302,13 +330,14 @@ iprocess_rmdir(fuse_req_t req) { struct fusebuf *fbuf = req->fbuf; struct fuse_session *se = req->se; + const char *name = fb_dat(fbuf->hdr); DPRINTF("%-11s", "rmdir"); DPRINTF("inode: %llu\t", fbuf->fb_ino); - DPRINTF("name: %s\t", fbuf->fb_dat); + DPRINTF("name: %s\t", name); if (se->llops.rmdir) - se->llops.rmdir(req, fbuf->fb_ino, fbuf->fb_dat); + se->llops.rmdir(req, fbuf->fb_ino, name); else fuse_reply_err(req, ENOSYS); } @@ -318,16 +347,18 @@ iprocess_mknod(fuse_req_t req) { struct fusebuf *fbuf = req->fbuf; struct fuse_session *se = req->se; + const char *name = fb_dat(fbuf->in.mknod); DPRINTF("%-11s", "mknod"); DPRINTF("inode: %llu\t", fbuf->fb_ino); - DPRINTF("mode: %#6o\t", fbuf->fb_io_mode); - DPRINTF("name: %s\t", fbuf->fb_dat); + DPRINTF("mode: %#6o\t", fbuf->in.mknod.mode); + DPRINTF("name: %s\t", name); - if (se->llops.mknod) - se->llops.mknod(req, fbuf->fb_ino, fbuf->fb_dat, - fbuf->fb_io_mode, fbuf->fb_io_rdev); - else + if (se->llops.mknod) { + req->ctx.umask = fbuf->in.mknod.umask; + se->llops.mknod(req, fbuf->fb_ino, name, fbuf->in.mknod.mode, + fbuf->in.mknod.rdev); + } else fuse_reply_err(req, ENOSYS); } @@ -342,7 +373,7 @@ iprocess_open(fuse_req_t req) DPRINTF("inode: %llu\t", fbuf->fb_ino); memset(&ffi, 0, sizeof(ffi)); - ffi.flags = fbuf->fb_io_flags; + ffi.flags = fbuf->in.open.flags; if (se->llops.open) se->llops.open(req, fbuf->fb_ino, &ffi); @@ -359,17 +390,17 @@ iprocess_read(fuse_req_t req) DPRINTF("%-11s", "read"); DPRINTF("inode: %llu\t", fbuf->fb_ino); - DPRINTF("size: %lu\t", fbuf->fb_io_len); - DPRINTF("offset: %llu\t", fbuf->fb_io_off); + DPRINTF("size: %u\t", fbuf->in.read.size); + DPRINTF("offset: %llu\t", fbuf->in.read.offset); if (se->llops.read) { memset(&ffi, 0, sizeof(ffi)); - ffi.fh = fbuf->fb_io_fd; + ffi.fh = fbuf->in.read.fh; ffi.fh_old = ffi.fh; - ffi.flags = fbuf->fb_io_flags; + ffi.flags = fbuf->in.read.flags; - se->llops.read(req, fbuf->fb_ino, fbuf->fb_io_len, - fbuf->fb_io_off, &ffi); + se->llops.read(req, fbuf->fb_ino, fbuf->in.read.size, + fbuf->in.read.offset, &ffi); } else fuse_reply_err(req, ENOSYS); } @@ -383,17 +414,17 @@ iprocess_write(fuse_req_t req) DPRINTF("%-11s", "write"); DPRINTF("inode: %llu\t", fbuf->fb_ino); - DPRINTF("size: %lu\t", fbuf->fb_io_len); - DPRINTF("offset: %llu\t", fbuf->fb_io_off); + DPRINTF("size: %u\t", fbuf->in.write.size); + DPRINTF("offset: %llu\t", fbuf->in.write.offset); if (se->llops.write) { memset(&ffi, 0, sizeof(ffi)); - ffi.fh = fbuf->fb_io_fd; + ffi.fh = fbuf->in.write.fh; ffi.fh_old = ffi.fh; - ffi.writepage = fbuf->fb_io_flags & 1; // XXX + ffi.writepage = fbuf->in.write.flags & 1; // XXX - se->llops.write(req, fbuf->fb_ino, fbuf->fb_dat, - fbuf->fb_io_len, fbuf->fb_io_off, &ffi); + se->llops.write(req, fbuf->fb_ino, fb_dat(fbuf->in.write), + fbuf->in.write.size, fbuf->in.write.offset, &ffi); } else fuse_reply_err(req, ENOSYS); } @@ -404,20 +435,18 @@ iprocess_fsync(fuse_req_t req) struct fusebuf *fbuf = req->fbuf; struct fuse_session *se = req->se; struct fuse_file_info ffi; + const uint32_t fsync_flags = fbuf->in.fsync.fsync_flags & 1; DPRINTF("%-11s", "fsync"); DPRINTF("inode: %llu\t", fbuf->fb_ino); + DPRINTF("flags: %u", fsync_flags); if (se->llops.fsync) { memset(&ffi, 0, sizeof(ffi)); - ffi.fh = fbuf->fb_io_fd; + ffi.fh = fbuf->in.fsync.fh; ffi.fh_old = ffi.fh; - /* - * fdatasync(2) is just a wrapper around fsync(2) so datasync - * is always false. - */ - se->llops.fsync(req, fbuf->fb_ino, 0 /* datasync */, &ffi); + se->llops.fsync(req, fbuf->fb_ino, fsync_flags, &ffi); } else fuse_reply_err(req, ENOSYS); } @@ -434,7 +463,7 @@ iprocess_flush(fuse_req_t req) if (se->llops.flush) { memset(&ffi, 0, sizeof(ffi)); - ffi.fh = fbuf->fb_io_fd; + ffi.fh = fbuf->in.flush.fh; ffi.fh_old = ffi.fh; ffi.flush = 1; @@ -455,9 +484,9 @@ iprocess_release(fuse_req_t req) if (se->llops.release) { memset(&ffi, 0, sizeof(ffi)); - ffi.fh = fbuf->fb_io_fd; + ffi.fh = fbuf->in.release.fh; ffi.fh_old = ffi.fh; - ffi.flags = fbuf->fb_io_flags; + ffi.flags = fbuf->in.release.flags; se->llops.release(req, fbuf->fb_ino, &ffi); } else @@ -469,14 +498,16 @@ iprocess_forget(fuse_req_t req) { struct fusebuf *fbuf = req->fbuf; struct fuse_session *se = req->se; + const uint64_t nlookup = fbuf->in.forget.nlookup; DPRINTF("%-11s", "forget"); DPRINTF("inode: %llu\t", fbuf->fb_ino); + DPRINTF("nlookup: %llu\t", nlookup); if (se->llops.forget) - se->llops.forget(req, fbuf->fb_ino, 1 /* TODO */); + se->llops.forget(req, fbuf->fb_ino, nlookup); else - fuse_reply_err(req, 0); + fuse_reply_none(req); } static void @@ -488,9 +519,9 @@ iprocess_symlink(fuse_req_t req) const char *name; int len; - name = fbuf->fb_dat; - len = strnlen(name, fbuf->fb_len); - target = &fbuf->fb_dat[len + 1]; + name = fb_dat(fbuf->hdr); + len = strlen(name); + target = &name[len + 1]; DPRINTF("%-11s", "symlink"); DPRINTF("inode: %llu\t", fbuf->fb_ino); @@ -523,15 +554,16 @@ iprocess_link(fuse_req_t req) { struct fusebuf *fbuf = req->fbuf; struct fuse_session *se = req->se; - const char *name = fbuf->fb_dat; + const char *name = fb_dat(fbuf->in.link); + const uint64_t oldnodeid = fbuf->in.link.oldnodeid; DPRINTF("%-11s", "link"); DPRINTF("inode: %llu\t", fbuf->fb_ino); - DPRINTF("inode: %llu\t", fbuf->fb_io_ino); + DPRINTF("inode: %llu\t", oldnodeid); DPRINTF("name: %s\t", name); if (se->llops.link) - se->llops.link(req, fbuf->fb_io_ino, fbuf->fb_ino, name); + se->llops.link(req, oldnodeid, fbuf->fb_ino, name); else fuse_reply_err(req, ENOSYS); } @@ -541,13 +573,14 @@ iprocess_unlink(fuse_req_t req) { struct fusebuf *fbuf = req->fbuf; struct fuse_session *se = req->se; + const char *name = fb_dat(fbuf->hdr); DPRINTF("%-11s", "unlink"); DPRINTF("inode: %llu\t", fbuf->fb_ino); - DPRINTF("name: %s\t", fbuf->fb_dat); + DPRINTF("name: %s\t", name); if (se->llops.unlink) - se->llops.unlink(req, fbuf->fb_ino, fbuf->fb_dat); + se->llops.unlink(req, fbuf->fb_ino, name); else fuse_reply_err(req, ENOSYS); } @@ -557,23 +590,23 @@ iprocess_rename(fuse_req_t req) { struct fusebuf *fbuf = req->fbuf; struct fuse_session *se = req->se; + const uint64_t newdir = fbuf->in.rename.newdir; const char *target; const char *name; int len; - name = fbuf->fb_dat; - len = strnlen(name, fbuf->fb_len); - target = &fbuf->fb_dat[len + 1]; + name = fb_dat(fbuf->in.rename); + len = strlen(name); + target = &name[len + 1]; DPRINTF("%-11s", "rename"); DPRINTF("inode: %llu\t", fbuf->fb_ino); DPRINTF("name: %s\t", name); - DPRINTF("inode: %llu\t", fbuf->fb_io_ino); + DPRINTF("newdir: %llu\t", newdir); DPRINTF("target: %s\t", target); if (se->llops.rename) - se->llops.rename(req, fbuf->fb_ino, name, fbuf->fb_io_ino, - target); + se->llops.rename(req, fbuf->fb_ino, name, newdir, target); else fuse_reply_err(req, ENOSYS); } @@ -607,86 +640,88 @@ fuse_session_process(struct fuse_session *se, const char *buf, size_t len, req.ctx.uid = fbuf->fb_uid; req.ctx.gid = fbuf->fb_gid; req.ctx.pid = fbuf->fb_tid; - req.ctx.umask = fbuf->fb_umask; + + /* later set in create, mknod, mkdir */ + req.ctx.umask = 0; /* need to at least have the header for the next check */ - if (len < sizeof(fbuf->fb_hdr)) + if (len < sizeof(fbuf->hdr)) return; - if (len < sizeof(fbuf->fb_hdr) + sizeof(fbuf->FD) + fbuf->fb_len) + if (len < fbuf->hdr.len) return; switch (fbuf->fb_type) { - case FBT_INIT: + case FUSE_INIT: iprocess_init(&req); break; - case FBT_DESTROY: + case FUSE_DESTROY: iprocess_destroy(&req); break; - case FBT_LOOKUP: + case FUSE_LOOKUP: iprocess_lookup(&req); break; - case FBT_GETATTR: + case FUSE_GETATTR: iprocess_getattr(&req); break; - case FBT_SETATTR: + case FUSE_SETATTR: iprocess_setattr(&req); break; - case FBT_OPENDIR: + case FUSE_OPENDIR: iprocess_opendir(&req); break; - case FBT_READDIR: + case FUSE_READDIR: iprocess_readdir(&req); break; - case FBT_RELEASEDIR: + case FUSE_RELEASEDIR: iprocess_releasedir(&req); break; - case FBT_MKDIR: + case FUSE_MKDIR: iprocess_mkdir(&req); break; - case FBT_RMDIR: + case FUSE_RMDIR: iprocess_rmdir(&req); break; - case FBT_MKNOD: + case FUSE_MKNOD: iprocess_mknod(&req); break; - case FBT_OPEN: + case FUSE_OPEN: iprocess_open(&req); break; - case FBT_READ: + case FUSE_READ: iprocess_read(&req); break; - case FBT_WRITE: + case FUSE_WRITE: iprocess_write(&req); break; - case FBT_FSYNC: + case FUSE_FSYNC: iprocess_fsync(&req); break; - case FBT_FLUSH: + case FUSE_FLUSH: iprocess_flush(&req); break; - case FBT_RELEASE: + case FUSE_RELEASE: iprocess_release(&req); break; - case FBT_RECLAIM: + case FUSE_FORGET: iprocess_forget(&req); break; - case FBT_SYMLINK: + case FUSE_SYMLINK: iprocess_symlink(&req); break; - case FBT_READLINK: + case FUSE_READLINK: iprocess_readlink(&req); break; - case FBT_LINK: + case FUSE_LINK: iprocess_link(&req); break; - case FBT_UNLINK: + case FUSE_UNLINK: iprocess_unlink(&req); break; - case FBT_RENAME: + case FUSE_RENAME: iprocess_rename(&req); break; - case FBT_STATFS: + case FUSE_STATFS: iprocess_statfs(&req); break; default: diff --git a/lib/libfuse/fuse_subr.c b/lib/libfuse/fuse_subr.c index 92fbf20b828..d715b8c1155 100644 --- a/lib/libfuse/fuse_subr.c +++ b/lib/libfuse/fuse_subr.c @@ -1,4 +1,4 @@ -/* $OpenBSD: fuse_subr.c,v 1.13 2026/01/29 06:04:27 helg Exp $ */ +/* $OpenBSD: fuse_subr.c,v 1.14 2026/06/17 13:29:01 helg Exp $ */ /* * Copyright (c) 2013 Sylvestre Gallon * @@ -69,13 +69,14 @@ ref_vn(struct fuse_vnode *vn) } void -unref_vn(struct fuse *f, struct fuse_vnode *vn) +unref_vn(struct fuse *f, struct fuse_vnode *vn, const uint64_t nlookup) { - if (--vn->ref == 0) { + vn->ref -= nlookup; + if (vn->ref == 0) { tree_pop(&f->vnode_tree, vn->ino); remove_vnode_from_name_tree(f, vn); if (vn->parent != NULL) - unref_vn(f, vn->parent); + unref_vn(f, vn->parent, 1); free(vn); } } diff --git a/sys/miscfs/fuse/fuse_device.c b/sys/miscfs/fuse/fuse_device.c index 963cb73664a..ff3d7c9d53f 100644 --- a/sys/miscfs/fuse/fuse_device.c +++ b/sys/miscfs/fuse/fuse_device.c @@ -1,4 +1,4 @@ -/* $OpenBSD: fuse_device.c,v 1.49 2026/01/22 11:53:31 helg Exp $ */ +/* $OpenBSD: fuse_device.c,v 1.50 2026/06/17 13:29:01 helg Exp $ */ /* * Copyright (c) 2012-2013 Sylvestre Gallon * @@ -303,7 +303,6 @@ fuseread(dev_t dev, struct uio *uio, int ioflag) { struct fuse_d *fd; struct fusebuf *fbuf; - struct fb_hdr hdr; int error = 0; fd = fuse_lookup(minor(dev)); @@ -342,20 +341,16 @@ fuseread(dev_t dev, struct uio *uio, int ioflag) } /* We get the whole fusebuf or nothing */ - if (uio->uio_resid < sizeof(fbuf->fb_hdr) + sizeof(fbuf->FD) + + if (uio->uio_resid < sizeof(fbuf->hdr) + fbuf->op_in_len + fbuf->fb_len) { error = EINVAL; goto end; } - /* Do not send kernel pointers */ - memcpy(&hdr.fh_next, &fbuf->fb_next, sizeof(fbuf->fb_next)); - memset(&fbuf->fb_next, 0, sizeof(fbuf->fb_next)); - - error = uiomove(&fbuf->fb_hdr, sizeof(fbuf->fb_hdr), uio); + error = uiomove(&fbuf->hdr, sizeof(fbuf->hdr), uio); if (error) goto end; - error = uiomove(&fbuf->FD, sizeof(fbuf->FD), uio); + error = uiomove(&fbuf->op, fbuf->op_in_len, uio); if (error) goto end; if (fbuf->fb_len > 0) { @@ -367,8 +362,6 @@ fuseread(dev_t dev, struct uio *uio, int ioflag) #ifdef FUSE_DEBUG fuse_dump_buff((char *)fbuf, sizeof(struct fusebuf)); #endif - /* Restore kernel pointers */ - memcpy(&fbuf->fb_next, &hdr.fh_next, sizeof(fbuf->fb_next)); free(fbuf->fb_dat, M_FUSEFS, fbuf->fb_len); fbuf->fb_dat = NULL; @@ -376,6 +369,13 @@ fuseread(dev_t dev, struct uio *uio, int ioflag) /* Move the fbuf to the wait queue */ SIMPLEQ_REMOVE_HEAD(&fd->fd_fbufs_in, fb_next); stat_fbufs_in--; + + /* FUSE_FORGET has no response */ + if (fbuf->fb_type == FUSE_FORGET) { + fb_delete(fbuf); + goto end; + } + SIMPLEQ_INSERT_TAIL(&fd->fd_fbufs_wait, fbuf, fb_next); stat_fbufs_wait++; @@ -391,37 +391,35 @@ fusewrite(dev_t dev, struct uio *uio, int ioflag) struct fusebuf *lastfbuf; struct fuse_d *fd; struct fusebuf *fbuf; - struct fb_hdr hdr; + struct fuse_out_header hdr; int error = 0; fd = fuse_lookup(minor(dev)); if (fd == NULL) - return (ENXIO); + return (ENODEV); - /* Check for sanity - must receive more than just the header */ - if (uio->uio_resid <= sizeof(hdr)) { + /* Check for sanity - must receive at least the header */ + if (uio->uio_resid < sizeof(hdr)) { error = EINVAL; goto out; } + /* Read the header */ if ((error = uiomove(&hdr, sizeof(hdr), uio)) != 0) goto out; - /* Check for sanity */ - if (hdr.fh_len > FUSEBUFMAXSIZE) { - error = EINVAL; - goto out; - } - - /* We get the whole fusebuf or nothing */ - if (uio->uio_resid != sizeof(fbuf->FD) + hdr.fh_len) { - error = EINVAL; + /* + * A unique value of zero means daemon is notifying us and hdr.error + * contains notification type. Currently unsupported. + */ + if (hdr.unique == 0) { + error = 0; goto out; } /* looking for uuid in fd_fbufs_wait */ SIMPLEQ_FOREACH(fbuf, &fd->fd_fbufs_wait, fb_next) { - if (fbuf->fb_uuid == hdr.fh_uuid) + if (fbuf->fb_uuid == hdr.unique) break; lastfbuf = fbuf; @@ -432,49 +430,93 @@ fusewrite(dev_t dev, struct uio *uio, int ioflag) } /* Update fb_hdr */ - fbuf->fb_len = hdr.fh_len; - fbuf->fb_err = hdr.fh_err; - fbuf->fb_ino = hdr.fh_ino; + fbuf->fb_err = -hdr.error; - /* Check for corrupted fbufs */ - if ((fbuf->fb_len && fbuf->fb_err) || fbuf->fb_len > fbuf->fb_io_len || - SIMPLEQ_EMPTY(&fd->fd_fbufs_wait)) { - printf("fuse: dropping corrupted fusebuf: %zu: %zu: %d\n", - fbuf->fb_io_len, fbuf->fb_len, fbuf->fb_err); + /* Don't expect out struct or data if there was an error */ + if (fbuf->fb_err) { + if (uio->uio_resid > 0) { + error = EINVAL; + fbuf->fb_err = EIO; + } + goto end; + } + + /* get operation output */ + if (fbuf->op_out_len > 0) { + if ((error = uiomove(&fbuf->op, fbuf->op_out_len, uio)) != 0) { + fbuf->fb_err = error; + goto end; + } + } + + /* Calculate the length of the data buffer to expect */ + if (fbuf->op_out_buf) { + fbuf->fb_len = hdr.len - sizeof(hdr) - fbuf->op_out_len; + if (fbuf->fb_len > fd->fd_fmp->max_read || fbuf->fb_len < 0) { + printf("fuse: invalid fusebuf read size: %llu " + "opcode=%d\n", fbuf->fb_len, fbuf->fb_type); + error = EINVAL; + fbuf->fb_err = EIO; + goto end; + } + } else + fbuf->fb_len = 0; + + /* validate remaining data */ + if (uio->uio_resid != fbuf->fb_len) { error = EINVAL; fbuf->fb_err = EIO; goto end; } - /* Get the missing data from the fbuf */ - error = uiomove(&fbuf->FD, sizeof(fbuf->FD), uio); - if (error) - return error; - - fbuf->fb_dat = NULL; if (fbuf->fb_len > 0) { - fbuf->fb_dat = malloc(fbuf->fb_len, M_FUSEFS, - M_WAITOK | M_ZERO); - error = uiomove(fbuf->fb_dat, fbuf->fb_len, uio); - if (error) { + fbuf->fb_dat = malloc(fbuf->fb_len, M_FUSEFS, M_WAITOK); + if ((error = uiomove(fbuf->fb_dat, fbuf->fb_len, uio)) != 0) { free(fbuf->fb_dat, M_FUSEFS, fbuf->fb_len); fbuf->fb_dat = NULL; + fbuf->fb_err = error; goto end; } } -#ifdef FUSE_DEBUG - fuse_dump_buff((char *)fbuf, sizeof(struct fusebuf)); -#endif - - switch (fbuf->fb_type) { - case FBT_INIT: + if (fbuf->fb_type == FUSE_INIT && fbuf->fb_err == 0) { + /* + * We don't support userspace with a smaller major version and + * it's up to userspace implementations to fall back to our + * version if they are capable of a later version. + */ + if (fbuf->op.out.init.major != FUSE_KERNEL_VERSION) { + printf("fuse: unsupported major version: %d.%d\n", + fbuf->op.out.init.major, fbuf->op.out.init.minor); + error = EINVAL; + goto end; + } + /* + * If the major versions match then both shall use the smallest + * of the two minor versions for communication. 7.9 is the + * smallest version less than what we support where the ABI has + * not changed. Supporting an earlier version would require + * conditional handling of some FUSE input arguments. If the + * daemon supports a later version then it must fall back to + * ours. + */ + if (fbuf->op.out.init.minor < 9) { + printf("fuse: unsupported minor version: %d.%d\n", + fbuf->op.out.init.major, fbuf->op.out.init.minor); + error = EINVAL; + goto end; + } + /* + * max_write determines the size of buffer to send to the file + * system daemon when writing so ensure that it's sane. + */ + fd->fd_fmp->max_write = MIN(fbuf->op.out.init.max_write, + FUSEBUFMAXSIZE); + if (fd->fd_fmp->max_write == 0) + fd->fd_fmp->max_write = FUSEBUFMAXSIZE; fd->fd_fmp->sess_init = 1; - break ; - case FBT_DESTROY: - fd->fd_fmp = NULL; - break ; - } + } + end: /* Remove the fbuf from the wait queue */ if (fbuf == SIMPLEQ_FIRST(&fd->fd_fbufs_wait)) @@ -488,7 +530,7 @@ end: * FBT_INIT doesn't expect a response. Otherwise let the VFS * syscall that is waiting on this fbuf know the reponse is ready. */ - if (fbuf->fb_type == FBT_INIT) + if (fbuf->fb_type == FUSE_INIT) fb_delete(fbuf); else wakeup(fbuf); diff --git a/sys/miscfs/fuse/fuse_file.c b/sys/miscfs/fuse/fuse_file.c index c056cd9ecac..30a52128cf7 100644 --- a/sys/miscfs/fuse/fuse_file.c +++ b/sys/miscfs/fuse/fuse_file.c @@ -1,4 +1,4 @@ -/* $OpenBSD: fuse_file.c,v 1.10 2024/10/31 13:55:21 claudio Exp $ */ +/* $OpenBSD: fuse_file.c,v 1.11 2026/06/17 13:29:01 helg Exp $ */ /* * Copyright (c) 2012-2013 Sylvestre Gallon * @@ -36,8 +36,8 @@ fusefs_file_open(struct fusefs_mnt *fmp, struct fusefs_node *ip, return (0); fbuf = fb_setup(0, ip->i_number, - ((isdir) ? FBT_OPENDIR : FBT_OPEN), p); - fbuf->fb_io_flags = flags; + ((isdir) ? FUSE_OPENDIR : FUSE_OPEN), p); + fbuf->op.in.open.flags = flags; error = fb_queue(fmp->dev, fbuf); if (error) { @@ -45,7 +45,7 @@ fusefs_file_open(struct fusefs_mnt *fmp, struct fusefs_node *ip, return (error); } - ip->fufh[fufh_type].fh_id = fbuf->fb_io_fd; + ip->fufh[fufh_type].fh_id = fbuf->op.out.open.fh; ip->fufh[fufh_type].fh_type = fufh_type; fb_delete(fbuf); @@ -61,13 +61,13 @@ fusefs_file_close(struct fusefs_mnt *fmp, struct fusefs_node * ip, if (fmp->sess_init) { fbuf = fb_setup(0, ip->i_number, - ((isdir) ? FBT_RELEASEDIR : FBT_RELEASE), p); - fbuf->fb_io_fd = ip->fufh[fufh_type].fh_id; - fbuf->fb_io_flags = flags; + ((isdir) ? FUSE_RELEASEDIR : FUSE_RELEASE), p); + fbuf->op.in.release.fh = ip->fufh[fufh_type].fh_id; + fbuf->op.in.release.flags = flags; error = fb_queue(fmp->dev, fbuf); if (error && (error != ENOSYS)) - printf("fusefs: file error %d\n", error); + printf("fusefs: file close error: %d\n", error); fb_delete(fbuf); } diff --git a/sys/miscfs/fuse/fuse_lookup.c b/sys/miscfs/fuse/fuse_lookup.c index eef08f90f4b..5b6cc7af4ee 100644 --- a/sys/miscfs/fuse/fuse_lookup.c +++ b/sys/miscfs/fuse/fuse_lookup.c @@ -1,4 +1,4 @@ -/* $OpenBSD: fuse_lookup.c,v 1.23 2026/01/22 11:53:31 helg Exp $ */ +/* $OpenBSD: fuse_lookup.c,v 1.24 2026/06/17 13:29:01 helg Exp $ */ /* * Copyright (c) 2012-2013 Sylvestre Gallon * @@ -82,7 +82,7 @@ fusefs_lookup(void *v) /* got a real entry */ fbuf = fb_setup(cnp->cn_namelen + 1, dp->i_number, - FBT_LOOKUP, p); + FUSE_LOOKUP, p); memcpy(fbuf->fb_dat, cnp->cn_nameptr, cnp->cn_namelen); fbuf->fb_dat[cnp->cn_namelen] = '\0'; @@ -119,8 +119,8 @@ fusefs_lookup(void *v) return (ENOENT); } - nid = fbuf->fb_ino; - nvtype = IFTOVT(fbuf->fb_attr.st_mode); + nid = fbuf->op.out.entry.nodeid; + nvtype = IFTOVT(fbuf->op.out.entry.attr.mode); fb_delete(fbuf); /* @@ -159,6 +159,7 @@ fusefs_lookup(void *v) goto reclaim; tdp->v_type = nvtype; + VTOI(tdp)->i_parent_cache = dp->i_number; *vpp = tdp; cnp->cn_flags |= SAVENAME; @@ -193,6 +194,9 @@ fusefs_lookup(void *v) } *vpp = tdp; + /* Didn't actually make a call but vget increments lookup */ + VTOI(tdp)->nlookup--; + } else if (nid == dp->i_number) { vref(vdp); *vpp = vdp; @@ -203,6 +207,7 @@ fusefs_lookup(void *v) goto reclaim; tdp->v_type = nvtype; + VTOI(tdp)->i_parent_cache = dp->i_number; /* * Cache the parent if it's a directory so that we can resolve @@ -222,11 +227,10 @@ fusefs_lookup(void *v) return (error); reclaim: - if (nid != dp->i_number && nid != FUSE_ROOTINO) { - fbuf = fb_setup(0, nid, FBT_RECLAIM, p); - if (fb_queue(fmp->dev, fbuf)) - printf("fusefs: libfuse vnode reclaim failed\n"); - fb_delete(fbuf); + if (nid != dp->i_number && nid != FUSE_ROOT_ID) { + fbuf = fb_setup(0, nid, FUSE_FORGET, p); + fbuf->op.in.forget.nlookup = 1; + fuse_device_queue_fbuf(fmp->dev, fbuf); /* no response */ } return (error); } diff --git a/sys/miscfs/fuse/fuse_vfsops.c b/sys/miscfs/fuse/fuse_vfsops.c index 9a35b585950..85138394679 100644 --- a/sys/miscfs/fuse/fuse_vfsops.c +++ b/sys/miscfs/fuse/fuse_vfsops.c @@ -1,4 +1,4 @@ -/* $OpenBSD: fuse_vfsops.c,v 1.49 2025/09/20 13:53:36 mpi Exp $ */ +/* $OpenBSD: fuse_vfsops.c,v 1.50 2026/06/17 13:29:01 helg Exp $ */ /* * Copyright (c) 2012-2013 Sylvestre Gallon * @@ -68,7 +68,7 @@ const struct vfsops fusefs_vfsops = { struct pool fusefs_fbuf_pool; -#define PENDING 2 /* FBT_INIT reply not yet received */ +#define PENDING 2 /* FUSE_INIT reply not yet received */ int fusefs_mount(struct mount *mp, const char *path, void *data, @@ -111,6 +111,12 @@ fusefs_mount(struct mount *mp, const char *path, void *data, else fmp->max_read = FUSEBUFMAXSIZE; + /* + * Initialise to a safe value just to be sure. This will be + * overwritten when the file system responds to FUSE_INIT. + */ + fmp->max_write = BLKDEV_IOSIZE; + fmp->allow_other = args->allow_other; mp->mnt_data = fmp; @@ -124,7 +130,11 @@ fusefs_mount(struct mount *mp, const char *path, void *data, strlcpy(mp->mnt_stat.f_mntfromspec, "fusefs", MNAMELEN); fuse_device_set_fmp(fmp, 1); - fbuf = fb_setup(0, 0, FBT_INIT, p); + fbuf = fb_setup(0, 0, FUSE_INIT, p); + fbuf->op.in.init.major = FUSE_KERNEL_VERSION; + fbuf->op.in.init.minor = FUSE_KERNEL_MINOR_VERSION; + fbuf->op.in.init.max_readahead = 0; + fbuf->op.in.init.flags = 0; /* OpenBSD supports nothing... */ /* cannot tsleep on mount */ fuse_device_queue_fbuf(fmp->dev, fbuf); @@ -157,7 +167,7 @@ fusefs_unmount(struct mount *mp, int mntflags, struct proc *p) return (error); if (fmp->sess_init && fmp->sess_init != PENDING) { - fbuf = fb_setup(0, 0, FBT_DESTROY, p); + fbuf = fb_setup(0, 0, FUSE_DESTROY, p); error = fb_queue(fmp->dev, fbuf); @@ -182,7 +192,7 @@ fusefs_root(struct mount *mp, struct vnode **vpp) struct vnode *nvp; int error; - if ((error = VFS_VGET(mp, FUSE_ROOTINO, &nvp)) != 0) + if ((error = VFS_VGET(mp, FUSE_ROOT_ID, &nvp)) != 0) return (error); nvp->v_type = VDIR; @@ -214,10 +224,10 @@ fusefs_statfs(struct mount *mp, struct statfs *sbp, struct proc *p) copy_statfs_info(sbp, mp); /* - * Both FBT_INIT and FBT_STATFS are sent to the FUSE file system + * Both FUSE_INIT and FUSE_STATFS are sent to the FUSE file system * daemon when it is mounted. However, the daemon is the process * that called mount(2) so to prevent a deadlock return dummy - * values until the response to FBT_INIT init is received. All + * values until the response to FUSE_INIT init is received. All * other VFS syscalls are queued. */ if (!fmp->sess_init || fmp->sess_init == PENDING) { @@ -231,7 +241,7 @@ fusefs_statfs(struct mount *mp, struct statfs *sbp, struct proc *p) sbp->f_iosize = 0; sbp->f_namemax = 0; } else { - fbuf = fb_setup(0, FUSE_ROOTINO, FBT_STATFS, p); + fbuf = fb_setup(0, FUSE_ROOT_ID, FUSE_STATFS, p); error = fb_queue(fmp->dev, fbuf); @@ -240,15 +250,25 @@ fusefs_statfs(struct mount *mp, struct statfs *sbp, struct proc *p) return (error); } - sbp->f_bavail = fbuf->fb_stat.f_bavail; - sbp->f_bfree = fbuf->fb_stat.f_bfree; - sbp->f_blocks = fbuf->fb_stat.f_blocks; - sbp->f_files = fbuf->fb_stat.f_files; - sbp->f_ffree = fbuf->fb_stat.f_ffree; - sbp->f_favail = fbuf->fb_stat.f_favail; - sbp->f_bsize = fbuf->fb_stat.f_frsize; - sbp->f_iosize = fbuf->fb_stat.f_bsize; - sbp->f_namemax = fbuf->fb_stat.f_namemax; + sbp->f_blocks = fbuf->op.out.statfs.st.blocks; + sbp->f_bfree = fbuf->op.out.statfs.st.bfree; + sbp->f_bavail = fbuf->op.out.statfs.st.bavail; + sbp->f_files = fbuf->op.out.statfs.st.files; + sbp->f_ffree = fbuf->op.out.statfs.st.ffree; + sbp->f_favail = fbuf->op.out.statfs.st.ffree; + sbp->f_bsize = fbuf->op.out.statfs.st.frsize; + sbp->f_namemax = fbuf->op.out.statfs.st.namelen; + sbp->f_iosize = fbuf->op.out.statfs.st.bsize; + + /* + * Programs use this to allocate an i/o buffer so ensure it's + * sane. + * + * XXX Should this be larger? + */ + if (sbp->f_iosize > BLKDEV_IOSIZE) + sbp->f_iosize = BLKDEV_IOSIZE; + fb_delete(fbuf); } @@ -276,8 +296,10 @@ retry: /* * check if vnode is in hash. */ - if ((*vpp = fuse_ihashget(fmp->dev, ino)) != NULL) + if ((*vpp = fuse_ihashget(fmp->dev, ino)) != NULL) { + VTOI(*vpp)->nlookup++; return (0); + } /* * if not create it @@ -295,6 +317,7 @@ retry: ip->i_vnode = nvp; ip->i_dev = fmp->dev; ip->i_number = ino; + ip->nlookup = 1; for (i = 0; i < FUFH_MAXTYPE; i++) ip->fufh[i].fh_type = FUFH_INVALID; @@ -311,7 +334,7 @@ retry: ip->i_ump = fmp; - if (ino == FUSE_ROOTINO) + if (ino == FUSE_ROOT_ID) nvp->v_flag |= VROOT; else { /* diff --git a/sys/miscfs/fuse/fuse_vnops.c b/sys/miscfs/fuse/fuse_vnops.c index a744b5b21e2..cc26a55a388 100644 --- a/sys/miscfs/fuse/fuse_vnops.c +++ b/sys/miscfs/fuse/fuse_vnops.c @@ -1,4 +1,4 @@ -/* $OpenBSD: fuse_vnops.c,v 1.76 2026/01/29 06:04:27 helg Exp $ */ +/* $OpenBSD: fuse_vnops.c,v 1.77 2026/06/17 13:29:01 helg Exp $ */ /* * Copyright (c) 2012-2013 Sylvestre Gallon * @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -329,8 +330,8 @@ fusefs_close(void *v) if (ip->fufh[fufh_type].fh_type == FUFH_INVALID) return (EBADF); - fbuf = fb_setup(0, ip->i_number, FBT_FLUSH, ap->a_p); - fbuf->fb_io_fd = ip->fufh[fufh_type].fh_id; + fbuf = fb_setup(0, ip->i_number, FUSE_FLUSH, ap->a_p); + fbuf->op.in.flush.fh = ip->fufh[fufh_type].fh_id; error = fb_queue(fmp->dev, fbuf); fb_delete(fbuf); if (error == ENOSYS) { @@ -405,7 +406,7 @@ fusefs_getattr(void *v) struct ucred *cred = p->p_ucred; struct fusefs_node *ip; struct fusebuf *fbuf; - struct stat *st; + struct fuse_attr *st; int error = 0; ip = VTOI(vp); @@ -423,7 +424,7 @@ fusefs_getattr(void *v) vap->va_mode = S_IRUSR | S_IXUSR; else vap->va_mode = S_IRWXU; - vap->va_nlink = 1; + vap->va_nlink = 2; vap->va_uid = fmp->mp->mnt_stat.f_owner; vap->va_gid = fmp->mp->mnt_stat.f_owner; vap->va_fsid = fmp->mp->mnt_stat.f_fsid.val[0]; @@ -441,7 +442,7 @@ fusefs_getattr(void *v) if (!fmp->sess_init) return (ENXIO); - fbuf = fb_setup(0, ip->i_number, FBT_GETATTR, p); + fbuf = fb_setup(0, ip->i_number, FUSE_GETATTR, p); error = fb_queue(fmp->dev, fbuf); if (error) { @@ -449,31 +450,36 @@ fusefs_getattr(void *v) return (error); } - st = &fbuf->fb_attr; + st = &fbuf->op.out.attr.attr; /* opendir(3) expects blocksize to be greater than zero. */ - if (st->st_blksize == 0) - st->st_blksize = S_BLKSIZE; + if (st->blksize == 0) + st->blksize = S_BLKSIZE; + else if (st->blksize > BLKDEV_IOSIZE) + st->blksize = BLKDEV_IOSIZE; /* calculate blocks of held disk space if fs didn't do it */ - if (st->st_blocks == 0 && st->st_size > 0) - st->st_blocks = (st->st_size + S_BLKSIZE - 1) / S_BLKSIZE; + if (st->blocks == 0 && st->size > 0) + st->blocks = (st->size + S_BLKSIZE - 1) / S_BLKSIZE; memset(vap, 0, sizeof(*vap)); - vap->va_type = IFTOVT(st->st_mode); - vap->va_mode = st->st_mode & ~S_IFMT; - vap->va_nlink = st->st_nlink; - vap->va_uid = st->st_uid; - vap->va_gid = st->st_gid; + vap->va_type = IFTOVT(st->mode); + vap->va_mode = st->mode & ~S_IFMT; + vap->va_nlink = st->nlink; + vap->va_uid = st->uid; + vap->va_gid = st->gid; vap->va_fsid = fmp->mp->mnt_stat.f_fsid.val[0]; - vap->va_fileid = st->st_ino; - vap->va_size = st->st_size; - vap->va_blocksize = st->st_blksize; - vap->va_atime = st->st_atim; - vap->va_mtime = st->st_mtim; - vap->va_ctime = st->st_ctim; - vap->va_rdev = st->st_rdev; - vap->va_bytes = st->st_blocks * S_BLKSIZE; + vap->va_fileid = st->ino; + vap->va_size = st->size; + vap->va_blocksize = st->blksize; + vap->va_atime.tv_sec = st->atime; + vap->va_atime.tv_nsec = st->atimensec; + vap->va_mtime.tv_sec = st->mtime; + vap->va_mtime.tv_nsec = st->mtimensec; + vap->va_ctime.tv_sec = st->ctime; + vap->va_ctime.tv_nsec = st->ctimensec; + vap->va_rdev = st->rdev; + vap->va_bytes = st->blocks * S_BLKSIZE; fb_delete(fbuf); return (error); @@ -490,7 +496,6 @@ fusefs_setattr(void *v) struct proc *p = ap->a_p; struct fusefs_mnt *fmp; struct fusebuf *fbuf; - struct fb_io *io; int error = 0; fmp = (struct fusefs_mnt *)ip->i_ump; @@ -516,17 +521,16 @@ fusefs_setattr(void *v) if (fmp->undef_op & UNDEF_SETATTR) return (ENOSYS); - fbuf = fb_setup(sizeof(*io), ip->i_number, FBT_SETATTR, p); - io = fbtod(fbuf, struct fb_io *); - io->fi_flags = 0; + fbuf = fb_setup(0, ip->i_number, FUSE_SETATTR, p); + fbuf->op.in.setattr.valid = 0; if (vap->va_uid != (uid_t)VNOVAL) { if (vp->v_mount->mnt_flag & MNT_RDONLY) { error = EROFS; goto out; } - fbuf->fb_attr.st_uid = vap->va_uid; - io->fi_flags |= FUSE_FATTR_UID; + fbuf->op.in.setattr.uid |= vap->va_uid; + fbuf->op.in.setattr.valid |= FUSE_FATTR_UID; } if (vap->va_gid != (gid_t)VNOVAL) { @@ -534,8 +538,8 @@ fusefs_setattr(void *v) error = EROFS; goto out; } - fbuf->fb_attr.st_gid = vap->va_gid; - io->fi_flags |= FUSE_FATTR_GID; + fbuf->op.in.setattr.gid |= vap->va_gid; + fbuf->op.in.setattr.valid |= FUSE_FATTR_GID; } if (vap->va_size != VNOVAL) { @@ -559,8 +563,8 @@ fusefs_setattr(void *v) break; } - fbuf->fb_attr.st_size = vap->va_size; - io->fi_flags |= FUSE_FATTR_SIZE; + fbuf->op.in.setattr.size |= vap->va_size; + fbuf->op.in.setattr.valid |= FUSE_FATTR_SIZE; } if (vap->va_atime.tv_nsec != VNOVAL) { @@ -568,8 +572,9 @@ fusefs_setattr(void *v) error = EROFS; goto out; } - fbuf->fb_attr.st_atim = vap->va_atime; - io->fi_flags |= FUSE_FATTR_ATIME; + fbuf->op.in.setattr.atime = vap->va_atime.tv_sec; + fbuf->op.in.setattr.atimensec = vap->va_atime.tv_nsec; + fbuf->op.in.setattr.valid |= FUSE_FATTR_ATIME; } if (vap->va_mtime.tv_nsec != VNOVAL) { @@ -577,8 +582,9 @@ fusefs_setattr(void *v) error = EROFS; goto out; } - fbuf->fb_attr.st_mtim = vap->va_mtime; - io->fi_flags |= FUSE_FATTR_MTIME; + fbuf->op.in.setattr.mtime = vap->va_mtime.tv_sec; + fbuf->op.in.setattr.mtimensec = vap->va_mtime.tv_nsec; + fbuf->op.in.setattr.valid |= FUSE_FATTR_MTIME; } /* XXX should set a flag if (vap->va_vaflags & VA_UTIMES_CHANGE) */ @@ -599,11 +605,11 @@ fusefs_setattr(void *v) goto out; } - fbuf->fb_attr.st_mode = vap->va_mode & ALLPERMS; - io->fi_flags |= FUSE_FATTR_MODE; + fbuf->op.in.setattr.mode = vap->va_mode & ALLPERMS; + fbuf->op.in.setattr.valid |= FUSE_FATTR_MODE; } - if (!io->fi_flags) { + if (!fbuf->op.in.setattr.valid) { goto out; } @@ -667,9 +673,9 @@ fusefs_link(void *v) } fbuf = fb_setup(cnp->cn_namelen + 1, dip->i_number, - FBT_LINK, p); + FUSE_LINK, p); - fbuf->fb_io_ino = ip->i_number; + fbuf->op.in.link.oldnodeid = ip->i_number; memcpy(fbuf->fb_dat, cnp->cn_nameptr, cnp->cn_namelen); fbuf->fb_dat[cnp->cn_namelen] = '\0'; @@ -728,7 +734,7 @@ fusefs_symlink(void *v) len = strlen(target) + 1; fbuf = fb_setup(len + cnp->cn_namelen + 1, dp->i_number, - FBT_SYMLINK, p); + FUSE_SYMLINK, p); memcpy(fbuf->fb_dat, cnp->cn_nameptr, cnp->cn_namelen); fbuf->fb_dat[cnp->cn_namelen] = '\0'; @@ -743,7 +749,15 @@ fusefs_symlink(void *v) goto bad; } - if ((error = VFS_VGET(fmp->mp, fbuf->fb_ino, &tdp))) { + /* symlink returns a fuse_entry_out with the ino of the new link */ + if (fbuf->op.out.entry.nodeid == 0 || + fbuf->op.out.entry.nodeid == FUSE_ROOT_ID) { + error = EIO; + fb_delete(fbuf); + goto bad; + } + + if ((error = VFS_VGET(fmp->mp, fbuf->op.out.entry.nodeid, &tdp))) { fb_delete(fbuf); goto bad; } @@ -767,16 +781,20 @@ fusefs_readdir(void *v) struct fusefs_node *ip; struct fusefs_mnt *fmp; struct fusebuf *fbuf; - struct dirent *dp; - char *edp; + struct fuse_dirent *fdp; + struct dirent de; struct vnode *vp; struct proc *p; struct uio *uio; + size_t read_size; + uint32_t fresid; + off_t foffset, freclen; int error = 0, eofflag = 0, diropen = 0; vp = ap->a_vp; uio = ap->a_uio; p = uio->uio_procp; + foffset = uio->uio_offset; ip = VTOI(vp); fmp = (struct fusefs_mnt *)ip->i_ump; @@ -784,31 +802,35 @@ fusefs_readdir(void *v) if (!fmp->sess_init) return (ENXIO); + /* + * Some file systems expect that the kernel always uses the same + * read buffer size. In the event that uio_resid is a multiple of + * max_read, we need to ensure that we use a consistent buffer size + * in the loop. + */ + read_size = MIN(uio->uio_resid, fmp->max_read); + /* * Basic check to ensure buffer is large enough for at least one * dirent with maximum allowed name length. */ - if (uio->uio_resid < sizeof(struct dirent)) + if (read_size < sizeof(struct dirent)) return (EINVAL); if (ip->fufh[FUFH_RDONLY].fh_type == FUFH_INVALID) { error = fusefs_file_open(fmp, ip, FUFH_RDONLY, O_RDONLY, 1, p); if (error) return (error); - - diropen = 1; + else + diropen = 1; } - while (uio->uio_resid > 0) { - fbuf = fb_setup(0, ip->i_number, FBT_READDIR, p); - - fbuf->fb_io_fd = ip->fufh[FUFH_RDONLY].fh_id; - fbuf->fb_io_off = uio->uio_offset; - fbuf->fb_io_len = MIN(uio->uio_resid, fmp->max_read); - - /* might not have enough space for another dirent */ - if (fbuf->fb_io_len < sizeof(struct dirent)) - break; + /* loop until we run out of buffer space */ + while (!error && uio->uio_resid >= read_size) { + fbuf = fb_setup(0, ip->i_number, FUSE_READDIR, p); + fbuf->op.in.read.fh = ip->fufh[FUFH_RDONLY].fh_id; + fbuf->op.in.read.offset = foffset; + fbuf->op.in.read.size = read_size; error = fb_queue(fmp->dev, fbuf); if (error) { @@ -816,58 +838,78 @@ fusefs_readdir(void *v) break; } - /* ack end of readdir */ + /* + * Ack end of readdir. Only used by getcwd(3), getdents(3) + * relies on a repeat call that returns no entries. + */ if (fbuf->fb_len == 0) { eofflag = 1; fb_delete(fbuf); break; } - /* validate the returned dirents */ - dp = (struct dirent *)fbuf->fb_dat; - edp = fbuf->fb_dat + fbuf->fb_len; - while ((char *)dp < edp) { + /* validate and convert the returned dirents */ + fresid = fbuf->fb_len; + fdp = (struct fuse_dirent *)fbuf->fb_dat; + + while (uio->uio_resid > 0 && fresid > FUSE_NAME_OFFSET) { + + /* get the size of the FUSE dirent */ + freclen = FUSE_DIRENT_SIZE(fdp); + /* check for partial dirent */ - if ((char *)dp + offsetof(struct dirent, d_name) >= edp - || dp->d_reclen <= offsetof(struct dirent, d_name) - || (char *)dp + dp->d_reclen > edp) { - error = EINVAL; + if (fresid < freclen) + break; + + /* check for sane name length */ + if (fdp->namelen == 0 || fdp->namelen > MAXNAMLEN) { + error = EIO; break; } - /* check name doesn't extend past end of dirent */ - if (dp->d_namlen + offsetof(struct dirent, d_name) >= - dp->d_reclen) { - error = EINVAL; - break; - } - /* - * If name doesn't extend to end of dirent then set - * remaining space to NULL. This is not usually the - * case but we can't trust userspace. - */ - memset(dp->d_name + dp->d_namlen, 0, dp->d_reclen - - dp->d_namlen - offsetof(struct dirent, d_name)); /* check for illegal character in file name */ - if (memchr(dp->d_name, '/', dp->d_namlen) != NULL) { - error = EINVAL; + if (memchr(fdp->name, '/', fdp->namelen) != NULL) { + error = EIO; break; } - dp = (struct dirent *)((char *)dp + dp->d_reclen); - } - if (error) { - fb_delete(fbuf); - break; - } - if ((error = uiomove(fbuf->fb_dat, fbuf->fb_len, uio))) { - fb_delete(fbuf); - break; + /* copy FUSE dirent into struct dirent */ + memset(&de, 0, sizeof(de)); + de.d_namlen = fdp->namelen; + de.d_reclen = DIRENT_RECSIZE(de.d_namlen); + if (uio->uio_resid < de.d_reclen) + goto out; + /* + * d_off is used by telldir, seekdir, readdir libc + * functions and expect the file system's offset. This + * will be passed back to this function so needs to be + * the FUSE offset. + */ + de.d_off = fdp->off; + de.d_fileno = fdp->ino; + de.d_type = fdp->type; + memcpy(de.d_name, fdp->name, de.d_namlen); + /* pad with NUL */ + /* XXX necessary if we memset 0 above? + * or is this better? */ + memset(de.d_name + de.d_namlen, 0, de.d_reclen + - de.d_namlen - offsetof(struct dirent, d_name)); + + if ((error = uiomove(&de, de.d_reclen, uio))) + break; + + /* advance to next FUSE dirent */ + fresid -= freclen; + foffset = fdp->off; + fdp = (struct fuse_dirent *)((char *)fdp + freclen); } fb_delete(fbuf); } +out: + uio->uio_offset = foffset; + if (!error && ap->a_eofflag != NULL) *ap->a_eofflag = eofflag; @@ -949,11 +991,7 @@ fusefs_readlink(void *v) if (fmp->undef_op & UNDEF_READLINK) return (ENOSYS); - fbuf = fb_setup(0, ip->i_number, FBT_READLINK, p); - - fbuf->fb_io_off = uio->uio_offset; - fbuf->fb_io_len = MIN(uio->uio_resid, fmp->max_read); - + fbuf = fb_setup(0, ip->i_number, FUSE_READLINK, p); error = fb_queue(fmp->dev, fbuf); if (error) { @@ -991,7 +1029,7 @@ fusefs_reclaim(void *v) struct fusefs_filehandle *fufh = NULL; struct fusefs_mnt *fmp; struct fusebuf *fbuf; - int type, error = 0; + int type; fmp = (struct fusefs_mnt *)ip->i_ump; @@ -1008,12 +1046,11 @@ fusefs_reclaim(void *v) /* * If the fuse connection is opened ask libfuse to free the vnodes. */ - if (fmp->sess_init && ip->i_number != FUSE_ROOTINO) { - fbuf = fb_setup(0, ip->i_number, FBT_RECLAIM, p); - error = fb_queue(fmp->dev, fbuf); - if (error) - printf("fusefs: vnode reclaim failed: %d\n", error); - fb_delete(fbuf); + if (fmp->sess_init && ip->i_number != FUSE_ROOT_ID) { + fbuf = fb_setup(0, ip->i_number, FUSE_FORGET, p); + fbuf->op.in.forget.nlookup = ip->nlookup; + fuse_device_queue_fbuf(fmp->dev, fbuf); + /* FUSE_FORGET has no response */ } /* @@ -1074,9 +1111,10 @@ fusefs_create(void *v) } fbuf = fb_setup(cnp->cn_namelen + 1, ip->i_number, - FBT_MKNOD, p); + FUSE_MKNOD, p); - fbuf->fb_io_mode = mode; + fbuf->op.in.mknod.mode = mode; + fbuf->op.in.mknod.umask = p->p_p->ps_fd->fd_cmask; memcpy(fbuf->fb_dat, cnp->cn_nameptr, cnp->cn_namelen); fbuf->fb_dat[cnp->cn_namelen] = '\0'; @@ -1089,10 +1127,17 @@ fusefs_create(void *v) goto out; } - if ((error = VFS_VGET(fmp->mp, fbuf->fb_ino, &tdp))) + /* mknod returns a fuse_entry_out with the ino of the new file */ + if (fbuf->op.out.entry.nodeid == 0 || + fbuf->op.out.entry.nodeid == FUSE_ROOT_ID) { + error = EIO; + goto out; + } + + if ((error = VFS_VGET(fmp->mp, fbuf->op.out.entry.nodeid, &tdp))) goto out; - tdp->v_type = IFTOVT(fbuf->fb_attr.st_mode); + tdp->v_type = VREG; *vpp = tdp; VN_KNOTE(ap->a_dvp, NOTE_WRITE); @@ -1131,11 +1176,12 @@ fusefs_mknod(void *v) } fbuf = fb_setup(cnp->cn_namelen + 1, ip->i_number, - FBT_MKNOD, p); + FUSE_MKNOD, p); - fbuf->fb_io_mode = MAKEIMODE(vap->va_type, vap->va_mode); + fbuf->op.in.mknod.mode = MAKEIMODE(vap->va_type, vap->va_mode); + fbuf->op.in.mknod.umask = p->p_p->ps_fd->fd_cmask; if (vap->va_rdev != VNOVAL) - fbuf->fb_io_rdev = vap->va_rdev; + fbuf->op.in.mknod.rdev = vap->va_rdev; memcpy(fbuf->fb_dat, cnp->cn_nameptr, cnp->cn_namelen); fbuf->fb_dat[cnp->cn_namelen] = '\0'; @@ -1148,10 +1194,21 @@ fusefs_mknod(void *v) goto out; } - if ((error = VFS_VGET(fmp->mp, fbuf->fb_ino, &tdp))) + /* mknod returns a fuse_entry_out with the ino of the new file */ + if (fbuf->op.out.entry.nodeid == 0 || + fbuf->op.out.entry.nodeid == FUSE_ROOT_ID) { + error = EIO; + goto out; + } + + if ((error = VFS_VGET(fmp->mp, fbuf->op.out.entry.nodeid, &tdp))) goto out; - tdp->v_type = IFTOVT(fbuf->fb_attr.st_mode); + /* + * Don't trust the type returned by the file system and assume it + * created what it was asked. + */ + tdp->v_type = vap->va_type; *vpp = tdp; VN_KNOTE(ap->a_dvp, NOTE_WRITE); @@ -1194,12 +1251,12 @@ fusefs_read(void *v) return (EINVAL); while (uio->uio_resid > 0) { - fbuf = fb_setup(0, ip->i_number, FBT_READ, p); + fbuf = fb_setup(0, ip->i_number, FUSE_READ, p); size = MIN(uio->uio_resid, fmp->max_read); - fbuf->fb_io_fd = fusefs_fd_get(ip, FUFH_RDONLY); - fbuf->fb_io_off = uio->uio_offset; - fbuf->fb_io_len = size; + fbuf->op.in.read.fh = fusefs_fd_get(ip, FUFH_RDONLY); + fbuf->op.in.read.offset = uio->uio_offset; + fbuf->op.in.read.size = size; error = fb_queue(fmp->dev, fbuf); @@ -1240,6 +1297,15 @@ fusefs_write(void *v) ip = VTOI(vp); fmp = (struct fusefs_mnt *)ip->i_ump; + /* + * XXX + * fmp->max_write will not be set to the value wanted by the file + * system if sess_init == PENDING. It is not currently possible for + * this to be the case since a write operation cannot take place on + * a vnode until after VOP_LOOKUP(9) has successfully returned. At + * which point, the file system must have responded to FUSE_INIT and + * sess_init will be 1 and max_write will be correctly set. + */ if (!fmp->sess_init) return (ENXIO); if (uio->uio_resid == 0) @@ -1253,12 +1319,12 @@ fusefs_write(void *v) } while (uio->uio_resid > 0) { - len = MIN(uio->uio_resid, fmp->max_read); - fbuf = fb_setup(len, ip->i_number, FBT_WRITE, p); + len = MIN(uio->uio_resid, fmp->max_write); + fbuf = fb_setup(len, ip->i_number, FUSE_WRITE, p); - fbuf->fb_io_fd = fusefs_fd_get(ip, FUFH_WRONLY); - fbuf->fb_io_off = uio->uio_offset; - fbuf->fb_io_len = len; + fbuf->op.in.write.fh = fusefs_fd_get(ip, FUFH_WRONLY); + fbuf->op.in.write.offset = uio->uio_offset; + fbuf->op.in.write.size = len; if ((error = uiomove(fbuf->fb_dat, len, uio))) { printf("fusefs: uio error %i\n", error); @@ -1270,8 +1336,8 @@ fusefs_write(void *v) if (error) break; - diff = len - fbuf->fb_io_len; - if (fbuf->fb_io_len > len) { + diff = len - fbuf->op.out.write.size; + if (fbuf->op.out.write.size > len) { error = EINVAL; break; } @@ -1382,14 +1448,14 @@ abortit: } fbuf = fb_setup(fcnp->cn_namelen + tcnp->cn_namelen + 2, - dp->i_number, FBT_RENAME, p); + dp->i_number, FUSE_RENAME, p); memcpy(fbuf->fb_dat, fcnp->cn_nameptr, fcnp->cn_namelen); fbuf->fb_dat[fcnp->cn_namelen] = '\0'; memcpy(fbuf->fb_dat + fcnp->cn_namelen + 1, tcnp->cn_nameptr, tcnp->cn_namelen); fbuf->fb_dat[fcnp->cn_namelen + tcnp->cn_namelen + 1] = '\0'; - fbuf->fb_io_ino = VTOI(tdvp)->i_number; + fbuf->op.in.rename.newdir = VTOI(tdvp)->i_number; error = fb_queue(fmp->dev, fbuf); @@ -1449,9 +1515,10 @@ fusefs_mkdir(void *v) } fbuf = fb_setup(cnp->cn_namelen + 1, ip->i_number, - FBT_MKDIR, p); + FUSE_MKDIR, p); - fbuf->fb_io_mode = MAKEIMODE(vap->va_type, vap->va_mode); + fbuf->op.in.mkdir.mode = MAKEIMODE(vap->va_type, vap->va_mode); + fbuf->op.in.mkdir.umask = p->p_p->ps_fd->fd_cmask; memcpy(fbuf->fb_dat, cnp->cn_nameptr, cnp->cn_namelen); fbuf->fb_dat[cnp->cn_namelen] = '\0'; @@ -1464,12 +1531,19 @@ fusefs_mkdir(void *v) goto out; } - if ((error = VFS_VGET(fmp->mp, fbuf->fb_ino, &tdp))) { + /* mkdir returns a fuse_entry_out with the ino of the new directory */ + if (fbuf->op.out.entry.nodeid == 0 || + fbuf->op.out.entry.nodeid == FUSE_ROOT_ID) { + error = EIO; + goto out; + } + + if ((error = VFS_VGET(fmp->mp, fbuf->op.out.entry.nodeid, &tdp))) { fb_delete(fbuf); goto out; } - tdp->v_type = IFTOVT(fbuf->fb_attr.st_mode); + tdp->v_type = VDIR; *vpp = tdp; VN_KNOTE(ap->a_dvp, NOTE_WRITE | NOTE_LINK); @@ -1517,7 +1591,7 @@ fusefs_rmdir(void *v) VN_KNOTE(dvp, NOTE_WRITE | NOTE_LINK); fbuf = fb_setup(cnp->cn_namelen + 1, dp->i_number, - FBT_RMDIR, p); + FUSE_RMDIR, p); memcpy(fbuf->fb_dat, cnp->cn_nameptr, cnp->cn_namelen); fbuf->fb_dat[cnp->cn_namelen] = '\0'; @@ -1575,7 +1649,7 @@ fusefs_remove(void *v) } fbuf = fb_setup(cnp->cn_namelen + 1, dp->i_number, - FBT_UNLINK, p); + FUSE_UNLINK, p); memcpy(fbuf->fb_dat, cnp->cn_nameptr, cnp->cn_namelen); fbuf->fb_dat[cnp->cn_namelen] = '\0'; @@ -1674,8 +1748,14 @@ fusefs_fsync(void *v) if (fufh->fh_type == FUFH_WRONLY || fufh->fh_type == FUFH_RDWR) { - fbuf = fb_setup(0, ip->i_number, FBT_FSYNC, p); - fbuf->fb_io_fd = fufh->fh_id; + fbuf = fb_setup(0, ip->i_number, FUSE_FSYNC, p); + fbuf->op.in.fsync.fh = fufh->fh_id; + + /* + * fdatasync(2) is just a wrapper around fsync(2) so + * datasync is always false. + */ + fbuf->op.in.fsync.fsync_flags = 0; /* Always behave as if ap->a_waitfor = MNT_WAIT. */ error = fb_queue(fmp->dev, fbuf); diff --git a/sys/miscfs/fuse/fusebuf.c b/sys/miscfs/fuse/fusebuf.c index 3e5c13d18a0..524d2b14964 100644 --- a/sys/miscfs/fuse/fusebuf.c +++ b/sys/miscfs/fuse/fusebuf.c @@ -1,4 +1,4 @@ -/* $OpenBSD: fusebuf.c,v 1.18 2021/03/11 13:31:35 jsg Exp $ */ +/* $OpenBSD: fusebuf.c,v 1.19 2026/06/17 13:29:01 helg Exp $ */ /* * Copyright (c) 2012-2013 Sylvestre Gallon * @@ -35,7 +35,82 @@ fb_setup(size_t len, ino_t ino, int op, struct proc *p) { struct fusebuf *fbuf; - fbuf = pool_get(&fusefs_fbuf_pool, PR_WAITOK | PR_ZERO); + KASSERT(len <= FUSEBUFMAXSIZE); + + fbuf = pool_get(&fusefs_fbuf_pool, PR_WAITOK | PR_ZERO); + + fbuf->op_in_len = 0; + fbuf->op_out_len = 0; + fbuf->op_out_buf = 0; + switch(op) { + case FUSE_GETATTR: + fbuf->op_in_len = sizeof(struct fuse_getattr_in); + fbuf->op_out_len = sizeof(struct fuse_attr_out); + break; + case FUSE_SETATTR: + fbuf->op_in_len = sizeof(struct fuse_setattr_in); + fbuf->op_out_len = sizeof(struct fuse_attr_out); + break; + case FUSE_READLINK: + fbuf->op_out_buf = 1; + break; + case FUSE_FLUSH: + fbuf->op_in_len = sizeof(struct fuse_flush_in); + break; + case FUSE_INIT: + fbuf->op_in_len = sizeof(struct fuse_init_in); + fbuf->op_out_len = sizeof(struct fuse_init_out); + break; + case FUSE_OPEN: + case FUSE_OPENDIR: + fbuf->op_in_len = sizeof(struct fuse_open_in); + fbuf->op_out_len = sizeof(struct fuse_open_out); + break; + case FUSE_READ: + case FUSE_READDIR: + fbuf->op_in_len = sizeof(struct fuse_read_in); + fbuf->op_out_buf = 1; + break; + case FUSE_MKDIR: + fbuf->op_in_len = sizeof(struct fuse_mkdir_in); + fbuf->op_out_len = sizeof(struct fuse_entry_out); + break; + case FUSE_MKNOD: + fbuf->op_in_len = sizeof(struct fuse_mknod_in); + fbuf->op_out_len = sizeof(struct fuse_entry_out); + break; + case FUSE_FSYNC: + fbuf->op_in_len = sizeof(struct fuse_fsync_in); + break; + case FUSE_RENAME: + fbuf->op_in_len = sizeof(struct fuse_rename_in); + break; + case FUSE_LINK: + fbuf->op_in_len = sizeof(struct fuse_link_in); + fbuf->op_out_len = sizeof(struct fuse_entry_out); + break; + case FUSE_SYMLINK: + fbuf->op_out_len = sizeof(struct fuse_entry_out); + break; + case FUSE_RELEASE: + case FUSE_RELEASEDIR: + fbuf->op_in_len = sizeof(struct fuse_release_in); + break; + case FUSE_WRITE: + fbuf->op_in_len = sizeof(struct fuse_write_in); + fbuf->op_out_len = sizeof(struct fuse_write_out); + break; + case FUSE_FORGET: + fbuf->op_in_len = sizeof(struct fuse_forget_in); + break; + case FUSE_LOOKUP: + fbuf->op_out_len = sizeof(struct fuse_entry_out); + break; + case FUSE_STATFS: + fbuf->op_out_len = sizeof(struct fuse_statfs_out); + break; + } + fbuf->hdr.len = sizeof(fbuf->hdr) + fbuf->op_in_len + len; fbuf->fb_len = len; fbuf->fb_err = 0; arc4random_buf(&fbuf->fb_uuid, sizeof fbuf->fb_uuid); @@ -48,7 +123,6 @@ fb_setup(size_t len, ino_t ino, int op, struct proc *p) fbuf->fb_tid = p->p_tid + THREAD_PID_OFFSET; fbuf->fb_uid = p->p_ucred->cr_uid; fbuf->fb_gid = p->p_ucred->cr_gid; - fbuf->fb_umask = p->p_p->ps_fd->fd_cmask; if (len == 0) fbuf->fb_dat = NULL; else diff --git a/sys/miscfs/fuse/fusefs.h b/sys/miscfs/fuse/fusefs.h index 564d408a0d1..57d6cfd539a 100644 --- a/sys/miscfs/fuse/fusefs.h +++ b/sys/miscfs/fuse/fusefs.h @@ -1,4 +1,4 @@ -/* $OpenBSD: fusefs.h,v 1.15 2025/09/08 17:25:46 helg Exp $ */ +/* $OpenBSD: fusefs.h,v 1.16 2026/06/17 13:29:01 helg Exp $ */ /* * Copyright (c) 2012-2013 Sylvestre Gallon * @@ -41,6 +41,7 @@ struct fusefs_mnt { struct mount *mp; uint32_t undef_op; int max_read; + int max_write; int sess_init; int allow_other; dev_t dev; @@ -74,11 +75,6 @@ void fuse_device_cleanup(dev_t); void fuse_device_queue_fbuf(dev_t, struct fusebuf *); void fuse_device_set_fmp(struct fusefs_mnt *, int); -/* - * The root inode is the root of the file system. Inode 0 can't be used for - * normal purposes. - */ -#define FUSE_ROOTINO ((ino_t)1) #define VFSTOFUSEFS(mp) ((struct fusefs_mnt *)((mp)->mnt_data)) #endif /* _KERNEL */ diff --git a/sys/miscfs/fuse/fusefs_node.h b/sys/miscfs/fuse/fusefs_node.h index 91e03ce4e82..d4b14a7f2e9 100644 --- a/sys/miscfs/fuse/fusefs_node.h +++ b/sys/miscfs/fuse/fusefs_node.h @@ -1,4 +1,4 @@ -/* $OpenBSD: fusefs_node.h,v 1.6 2026/01/22 11:53:31 helg Exp $ */ +/* $OpenBSD: fusefs_node.h,v 1.7 2026/06/17 13:29:01 helg Exp $ */ /* * Copyright (c) 2012-2013 Sylvestre Gallon * @@ -49,6 +49,9 @@ struct fusefs_node { /** meta **/ off_t filesize; + + /** lookup count needed by FUSE_FORGET **/ + uint64_t nlookup; }; #ifdef ITOV diff --git a/sys/sys/fusebuf.h b/sys/sys/fusebuf.h index 1f4ba7df1d5..2e86390134b 100644 --- a/sys/sys/fusebuf.h +++ b/sys/sys/fusebuf.h @@ -1,4 +1,4 @@ -/* $OpenBSD: fusebuf.h,v 1.16 2025/09/06 06:15:52 helg Exp $ */ +/* $OpenBSD: fusebuf.h,v 1.17 2026/06/17 13:29:01 helg Exp $ */ /* * Copyright (c) 2013 Sylvestre Gallon * Copyright (c) 2013 Martin Pieuchot @@ -19,36 +19,25 @@ #ifndef _SYS_FUSEBUF_H_ #define _SYS_FUSEBUF_H_ +#include + /* * Maximum size of the read or write buffer sent from the kernel for VFS * syscalls: read, readdir, readlink, write. */ #define FUSEBUFMAXSIZE (4096*1024) -/* header at beginning of each fusebuf: */ -struct fb_hdr { - SIMPLEQ_ENTRY(fusebuf) fh_next; /* next buffer in chain */ - size_t fh_len; /* Amount of data */ - int fh_err; /* errno to pass back */ - int fh_type; /* type of data */ - ino_t fh_ino; /* Inode of this fusebuf */ - uint64_t fh_uuid; /* Uuid to track the answer */ - pid_t fh_tid; /* calling proc thread id */ - uid_t fh_uid; /* calling proc uid */ - gid_t fh_gid; /* calling proc gid */ - mode_t fh_umask; /* calling proc umask */ -}; +/** Linux FUSE kernel interface major version we somewhat emulate. */ +#define FUSE_KERNEL_VERSION 7 -/* header for fuse file operations (like read/write/mkdir): */ -struct fb_io { - uint64_t fi_fd; /* fd where the io is performed */ - ino_t fi_ino; /* ino for the io */ - off_t fi_off; /* offset for the io */ - size_t fi_len; /* Length of data */ - mode_t fi_mode; /* mode for fd */ - uint32_t fi_flags; /* flags on transfer */ - dev_t fi_rdev; /* dev for mknod */ -}; +/** Linux FUSE kernel interface minor version we somewhat emulate. */ +#define FUSE_KERNEL_MINOR_VERSION 19 + +/* + * The root inode is the root of the mounted FUSE file system. + * Also note that inode 0 can't be used for normal purposes. + */ +#define FUSE_ROOT_ID ((ino_t)1) /* * An operation is issued by the kernel through fuse(4) when the @@ -62,43 +51,6 @@ struct fb_io { * When the userland file system answers to this operation it uses * the same ID (fh_uuid). */ -struct fusebuf { - struct fb_hdr fb_hdr; - union { - struct statvfs FD_stat; /* vfs statfs */ - struct stat FD_attr; /* for attr vnops */ - struct fb_io FD_io; /* for file io vnops */ - } FD; - uint8_t *fb_dat; /* data's */ -}; - -#define fb_next fb_hdr.fh_next -#define fb_len fb_hdr.fh_len -#define fb_err fb_hdr.fh_err -#define fb_type fb_hdr.fh_type -#define fb_ino fb_hdr.fh_ino -#define fb_uuid fb_hdr.fh_uuid -#define fb_tid fb_hdr.fh_tid -#define fb_uid fb_hdr.fh_uid -#define fb_gid fb_hdr.fh_gid -#define fb_umask fb_hdr.fh_umask - -#define fb_stat FD.FD_stat -#define fb_attr FD.FD_attr -#define fb_io_fd FD.FD_io.fi_fd -#define fb_io_ino FD.FD_io.fi_ino -#define fb_io_off FD.FD_io.fi_off -#define fb_io_len FD.FD_io.fi_len -#define fb_io_mode FD.FD_io.fi_mode -#define fb_io_flags FD.FD_io.fi_flags -#define fb_io_rdev FD.FD_io.fi_rdev - -/* - * Macros for type conversion - * fbtod(fb,t) - convert fusebuf pointer to data pointer of correct - * type - */ -#define fbtod(fb,t) ((t)((fb)->fb_dat)) /* flags needed by setattr */ #define FUSE_FATTR_MODE (1 << 0) @@ -107,42 +59,368 @@ struct fusebuf { #define FUSE_FATTR_SIZE (1 << 3) #define FUSE_FATTR_ATIME (1 << 4) #define FUSE_FATTR_MTIME (1 << 5) -#define FUSE_FATTR_FH (1 << 6) /* fusebuf types */ -#define FBT_LOOKUP 0 -#define FBT_GETATTR 1 -#define FBT_SETATTR 2 -#define FBT_READLINK 3 -#define FBT_SYMLINK 4 -#define FBT_MKNOD 5 -#define FBT_MKDIR 6 -#define FBT_UNLINK 7 -#define FBT_RMDIR 8 -#define FBT_RENAME 9 -#define FBT_LINK 10 -#define FBT_OPEN 11 -#define FBT_READ 12 -#define FBT_WRITE 13 -#define FBT_STATFS 14 -#define FBT_RELEASE 16 -#define FBT_FSYNC 17 -#define FBT_FLUSH 18 -#define FBT_INIT 19 -#define FBT_OPENDIR 20 -#define FBT_READDIR 21 -#define FBT_RELEASEDIR 22 -#define FBT_FSYNCDIR 23 -#define FBT_ACCESS 24 -#define FBT_DESTROY 26 -#define FBT_RECLAIM 27 +#define FUSE_LOOKUP 1 +#define FUSE_GETATTR 3 +#define FUSE_SETATTR 4 +#define FUSE_READLINK 5 +#define FUSE_SYMLINK 6 +#define FUSE_MKNOD 8 +#define FUSE_MKDIR 9 +#define FUSE_UNLINK 10 +#define FUSE_RMDIR 11 +#define FUSE_RENAME 12 +#define FUSE_LINK 13 +#define FUSE_OPEN 14 +#define FUSE_READ 15 +#define FUSE_WRITE 16 +#define FUSE_STATFS 17 +#define FUSE_RELEASE 18 +#define FUSE_FSYNC 20 +#define FUSE_FLUSH 25 +#define FUSE_INIT 26 +#define FUSE_OPENDIR 27 +#define FUSE_READDIR 28 +#define FUSE_RELEASEDIR 29 +#define FUSE_DESTROY 38 +#define FUSE_FORGET 2 + +struct fuse_attr { + uint64_t ino; + uint64_t size; + uint64_t blocks; + uint64_t atime; + uint64_t mtime; + uint64_t ctime; + uint32_t atimensec; + uint32_t mtimensec; + uint32_t ctimensec; + uint32_t mode; + uint32_t nlink; + uint32_t uid; + uint32_t gid; + uint32_t rdev; + uint32_t blksize; + uint32_t padding; +}; + + +struct fuse_entry_out { + uint64_t nodeid; /* Inode number */ + uint64_t generation; /* Not implemented */ + uint64_t entry_valid; /* Not implemented */ + uint64_t attr_valid; /* Not implemented */ + uint32_t entry_valid_nsec; /* Not implemented */ + uint32_t attr_valid_nsec; /* Not implemented */ + struct fuse_attr attr; +}; + +struct fuse_forget_in { + uint64_t nlookup; +}; + +struct fuse_getattr_in { + uint32_t getattr_flags; /* Not implemented */ + uint32_t dummy; + uint64_t fh; /* Not implemented */ +}; + +struct fuse_attr_out { + uint64_t attr_valid; /* Not implemented */ + uint32_t attr_valid_nsec; /* Not implemented */ + uint32_t dummy; + struct fuse_attr attr; +}; + +struct fuse_mknod_in { + uint32_t mode; + uint32_t rdev; + uint32_t umask; + uint32_t padding; +}; + +struct fuse_mkdir_in { + uint32_t mode; + uint32_t umask; +}; + +struct fuse_rename_in { + uint64_t newdir; +}; + +struct fuse_link_in { + uint64_t oldnodeid; +}; + +struct fuse_setattr_in { + uint32_t valid; + uint32_t padding; + uint64_t fh; + uint64_t size; + uint64_t lock_owner; + uint64_t atime; + uint64_t mtime; + uint64_t unused2; + uint32_t atimensec; + uint32_t mtimensec; + uint32_t unused3; + uint32_t mode; + uint32_t unused4; + uint32_t uid; + uint32_t gid; + uint32_t unused5; +}; + +struct fuse_open_in { + uint32_t flags; + uint32_t unused; +}; + +struct fuse_open_out { + uint64_t fh; + uint32_t open_flags; + uint32_t padding; +}; + +struct fuse_release_in { + uint64_t fh; + uint32_t flags; + uint32_t release_flags; + uint64_t lock_owner; +}; + +struct fuse_flush_in { + uint64_t fh; + uint32_t unused; + uint32_t padding; + uint64_t lock_owner; +}; + +struct fuse_read_in { + uint64_t fh; + uint64_t offset; + uint32_t size; + uint32_t read_flags; + uint64_t lock_owner; + uint32_t flags; + uint32_t padding; +}; + +struct fuse_write_in { + uint64_t fh; + uint64_t offset; + uint32_t size; + uint32_t write_flags; + uint64_t lock_owner; + uint32_t flags; + uint32_t padding; +}; + +struct fuse_write_out { + uint32_t size; + uint32_t padding; +}; + +struct fuse_kstatfs { + uint64_t blocks; + uint64_t bfree; + uint64_t bavail; + uint64_t files; + uint64_t ffree; + uint32_t bsize; + uint32_t namelen; + uint32_t frsize; + uint32_t padding; + uint32_t spare[6]; +}; + +struct fuse_statfs_out { + struct fuse_kstatfs st; +}; + +struct fuse_fsync_in { + uint64_t fh; + uint32_t fsync_flags; //XXX + uint32_t padding; +}; + +struct fuse_init_in { + uint32_t major; + uint32_t minor; + uint32_t max_readahead; + uint32_t flags; +}; + +struct fuse_init_out { + uint32_t major; + uint32_t minor; + uint32_t max_readahead; /* Not implemented */ + uint32_t flags; /* Not implemented */ + uint16_t max_background; /* Not implemented */ + uint16_t congestion_threshold; /* Not implemented */ + uint32_t max_write; +}; + +struct fuse_in_header { + uint32_t len; + uint32_t opcode; + uint64_t unique; + uint64_t nodeid; + uint32_t uid; + uint32_t gid; + uint32_t pid; + uint32_t padding; +}; + +struct fuse_out_header { + uint32_t len; + int32_t error; + uint64_t unique; +}; + +struct fuse_dirent { + uint64_t ino; + uint64_t off; + uint32_t namelen; + uint32_t type; + char name[]; /* unterminated string */ +}; + +#define FUSE_NAME_OFFSET offsetof(struct fuse_dirent, name) +#define FUSE_DIRENT_ALIGN(x) \ + (((x) + sizeof(uint64_t) - 1) & ~(sizeof(uint64_t) - 1)) +#define FUSE_DIRENT_SIZE(d) \ + FUSE_DIRENT_ALIGN(FUSE_NAME_OFFSET + (d)->namelen) #ifdef _KERNEL +/* + * An operation is issued by the kernel through fuse(4) when the + * userland file system needs to execute an action (mkdir(2), + * link(2), etc). + * + * When the userland file system answers to this operation it uses + * the same ID (fb_uuid). + */ +struct fusebuf { + SIMPLEQ_ENTRY(fusebuf) next; /* next buffer in chain */ + struct fuse_in_header hdr; + int32_t error; /* error returned by daemon */ + size_t op_in_len; /* size of input */ + size_t op_out_len; /* size of output */ + uint8_t op_out_buf; /* whether to expect data */ + union { + union { + struct fuse_forget_in forget; + struct fuse_getattr_in getattr; + struct fuse_setattr_in setattr; + struct fuse_mknod_in mknod; + struct fuse_mkdir_in mkdir; + struct fuse_rename_in rename; + struct fuse_link_in link; + struct fuse_open_in open; + struct fuse_read_in read; + struct fuse_write_in write; + struct fuse_release_in release; + struct fuse_fsync_in fsync; + struct fuse_flush_in flush; + struct fuse_init_in init; + } in; + union { + struct fuse_entry_out entry; + struct fuse_attr_out attr; + struct fuse_open_out open; + struct fuse_write_out write; + struct fuse_statfs_out statfs; + struct fuse_init_out init; + } out; + } op; + uint64_t dat_len; + uint8_t *dat; +}; + +#define fb_dat dat +#define fb_next next +#define fb_err error +#define fb_len dat_len +#define fb_type hdr.opcode +#define fb_uuid hdr.unique +#define fb_ino hdr.nodeid +#define fb_tid hdr.pid +#define fb_uid hdr.uid +#define fb_gid hdr.gid + /* fusebuf prototypes */ struct fusebuf *fb_setup(size_t, ino_t, int, struct proc *); int fb_queue(dev_t, struct fusebuf *); void fb_delete(struct fusebuf *); +#else /* _KERNEL */ + +/* + * Userland fusebuf more directly maps to the protocol binary layout for each + * file operation request (input). This consists of a header followed by an + * optional input struct with data immediately following either the header or + * input struct. + * + * FUSE_LOOKUP: hdr, dat (filename) + * FUSE_GETATTR: hdr, getattr + * FUSE_SETATTR: hdr, setattr + * FUSE_READLINK: hdr + * FUSE_SYMLINK: hdr, dat (source\0target) + * FUSE_MKNOD: hdr, mknod, dat (filename) + * FUSE_MKDIR: hdr, mkdir, dat (dirname) + * FUSE_UNLINK: hdr, dat (filename) + * FUSE_RMDIR: hdr, dat (dirname) + * FUSE_RENAME: hdr, rename, dat (oldname\0newname) + * FUSE_LINK: hdr, dat (source\0target) + * FUSE_OPEN: hdr, open + * FUSE_READ: hdr, read + * FUSE_WRITE: hdr, write, dat + * FUSE_STATFS: hdr, statfs + * FUSE_RELEASE: hdr, release + * FUSE_FSYNC: hdr, fsync + * FUSE_FLUSH: hdr, flush + * FUSE_INIT: hdr, init + * FUSE_OPENDIR: hdr, open + * FUSE_READDIR: hdr, read + * FUSE_RELEASEDIR: hdr, release + * FUSE_DESTROY: hdr + * FUSE_FORGET: hdr, forget + */ +struct fusebuf { + struct fuse_in_header hdr; + union { + struct fuse_forget_in forget; + struct fuse_getattr_in getattr; + struct fuse_setattr_in setattr; + struct fuse_mknod_in mknod; + struct fuse_mkdir_in mkdir; + struct fuse_rename_in rename; + struct fuse_link_in link; + struct fuse_open_in open; + struct fuse_read_in read; + struct fuse_write_in write; + struct fuse_release_in release; + struct fuse_fsync_in fsync; + struct fuse_flush_in flush; + struct fuse_init_in init; + } in; +}; + +#define fb_type hdr.opcode +#define fb_uuid hdr.unique +#define fb_ino hdr.nodeid +#define fb_tid hdr.pid +#define fb_uid hdr.uid +#define fb_gid hdr.gid + +/* + * Macro to get the additional data for operations that have it. + * The data starts immediately after the related input struct. + */ +#define fb_dat(in) (((char *)&(in)) + sizeof(in)) + #endif /* _KERNEL */ #endif /* _SYS_FUSEBUF_H_ */