aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--man/man4/9pfuse.462
-rw-r--r--src/cmd/9pfuse/COPYRIGHT17
-rw-r--r--src/cmd/9pfuse/a.h51
-rw-r--r--src/cmd/9pfuse/fuse.c772
-rw-r--r--src/cmd/9pfuse/fuse_kernel.h312
-rw-r--r--src/cmd/9pfuse/main.c1169
-rw-r--r--src/cmd/9pfuse/mkfile12
7 files changed, 2395 insertions, 0 deletions
diff --git a/man/man4/9pfuse.4 b/man/man4/9pfuse.4
new file mode 100644
index 00000000..8d70885b
--- /dev/null
+++ b/man/man4/9pfuse.4
@@ -0,0 +1,62 @@
+.TH 9PFUSE 4
+.SH NAME
+9pfuse \- mount 9P service via FUSE
+.SH SYNOPSIS
+.B 9pfuse
+[
+.B -D
+]
+[
+.B -a
+.I t
+]
+[
+.B -e
+.I t
+]
+.I addr
+.I mtpt
+.SH DESCRIPTION
+.I 9pfuse
+mounts the 9P service running at
+.I addr
+onto
+.I mtpt
+using the FUSE user-level file system driver.
+.PP
+.I 9pfuse
+sets up the initial mount and then forks itself
+into the background, where it serves the FUSE
+protocol, translating the requests into 9P.
+.PP
+The options are:
+.TP
+.B -D
+Print each FUSE and 9P message to standard error.
+.TP
+.B -a\fI t
+Set the kernel cache timeout for attribute information
+to
+.I t
+(default 1.0) seconds.
+.TP
+.B -e\fI t
+Set the kernel cache timeout for directory entries to
+.I t
+(default 1.0) seconds.
+.PD
+.PP
+The
+.I fusermount
+binary must exist in the current search path.
+.PP
+FUSE is available for Linux 2.4.21 and later,
+Linux 2.6, and FreeBSD 6.x and later.
+.SH SEE ALSO
+FUSE Homepage,
+.HR http://fuse.sourceforge.net
+.PP
+FUSE for FreeBSD,
+.HR http://fuse4bsd.creo.hu
+.SH SOURCE
+.B \*9/src/cmd/9pfuse
diff --git a/src/cmd/9pfuse/COPYRIGHT b/src/cmd/9pfuse/COPYRIGHT
new file mode 100644
index 00000000..f9f2fe2a
--- /dev/null
+++ b/src/cmd/9pfuse/COPYRIGHT
@@ -0,0 +1,17 @@
+The files in this directory are subject to the following license.
+
+The author of this software is Russ Cox.
+
+ Copyright (c) 2006 Russ Cox
+
+Permission to use, copy, modify, and distribute this software for any
+purpose without fee is hereby granted, provided that this entire notice
+is included in all copies of any software which is or includes a copy
+or modification of this software and in all copies of the supporting
+documentation for such software.
+
+THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+WARRANTY. IN PARTICULAR, THE AUTHOR MAKES NO REPRESENTATION OR WARRANTY
+OF ANY KIND CONCERNING THE MERCHANTABILITY OF THIS SOFTWARE OR ITS
+FITNESS FOR ANY PARTICULAR PURPOSE.
+
diff --git a/src/cmd/9pfuse/a.h b/src/cmd/9pfuse/a.h
new file mode 100644
index 00000000..c6e5b020
--- /dev/null
+++ b/src/cmd/9pfuse/a.h
@@ -0,0 +1,51 @@
+#include <u.h>
+#include <errno.h>
+#include <sys/uio.h>
+#include <sys/socket.h>
+#include <libc.h>
+#include <fcall.h>
+#include <thread.h>
+#include <9pclient.h>
+#include "fuse_kernel.h"
+
+/* Somehow the FUSE guys forgot to define this one! */
+struct fuse_create_out {
+ struct fuse_entry_out e;
+ struct fuse_open_out o;
+};
+
+typedef struct FuseMsg FuseMsg;
+struct FuseMsg
+{
+ FuseMsg *next;
+ uchar *buf;
+ int nbuf;
+ struct fuse_in_header *hdr; /* = buf */
+ void *tx; /* = hdr+1 */
+};
+
+extern int debug;
+
+extern int fusefd;
+extern int fuseeof;
+extern int fusebufsize;
+extern int fusemaxwrite;
+extern FuseMsg *fusemsglist;
+extern char *fusemtpt;
+
+void freefusemsg(FuseMsg *m);
+int fusefmt(Fmt*);
+void initfuse(char *mtpt);
+FuseMsg* readfusemsg(void);
+void replyfuse(FuseMsg *m, void *arg, int narg);
+void replyfuseerrno(FuseMsg *m, int e);
+void replyfuseerrstr(FuseMsg*);
+void request9p(Fcall *tx);
+
+void* emalloc(size_t n);
+void* erealloc(void *p, size_t n);
+char* estrdup(char *p);
+
+int errstr2errno(void);
+void unmountatexit(void);
+
diff --git a/src/cmd/9pfuse/fuse.c b/src/cmd/9pfuse/fuse.c
new file mode 100644
index 00000000..0e61f72f
--- /dev/null
+++ b/src/cmd/9pfuse/fuse.c
@@ -0,0 +1,772 @@
+#include "a.h"
+
+int fusefd;
+int fuseeof;
+int fusebufsize;
+int fusemaxwrite;
+FuseMsg *fusemsglist;
+
+FuseMsg*
+allocfusemsg(void)
+{
+ FuseMsg *m;
+ void *vbuf;
+
+ if((m = fusemsglist) != nil){
+ fusemsglist = m->next;
+ return m;
+ }
+ m = emalloc(sizeof(*m) + fusebufsize);
+ vbuf = m+1;
+ m->buf = vbuf;
+ m->nbuf = 0;
+ m->hdr = vbuf;
+ m->tx = m->hdr+1;
+ return m;
+}
+
+void
+freefusemsg(FuseMsg *m)
+{
+ m->next = fusemsglist;
+ fusemsglist = m;
+}
+
+FuseMsg*
+readfusemsg(void)
+{
+ FuseMsg *m;
+ int n;
+
+ m = allocfusemsg();
+ errno = 0;
+ /*
+ * The FUSE kernel device apparently guarantees
+ * that this read will return exactly one message.
+ * You get an error return if you ask for just the
+ * length (first 4 bytes).
+ * FUSE returns an ENODEV error, not EOF,
+ * when the connection is unmounted.
+ */
+ if((n = read(fusefd, m->buf, fusebufsize)) < 0){
+ if(errno != ENODEV)
+ sysfatal("readfusemsg: %r");
+ }
+ if(n <= 0){
+ fuseeof = 1;
+ freefusemsg(m);
+ return nil;
+ }
+ m->nbuf = n;
+ if(m->hdr->len != n)
+ sysfatal("readfusemsg: got %d wanted %d",
+ n, m->hdr->len);
+ m->hdr->len -= sizeof(m->hdr);
+
+ /*
+ * Paranoia.
+ * Make sure lengths are long enough.
+ * Make sure string arguments are NUL terminated.
+ * (I don't trust the kernel module.)
+ */
+ switch(m->hdr->opcode){
+ default:
+ /*
+ * Could sysfatal here, but can also let message go
+ * and assume higher-level code will return an
+ * "I don't know what you mean" error and recover.
+ */
+ break;
+ case FUSE_LOOKUP:
+ case FUSE_UNLINK:
+ case FUSE_RMDIR:
+ case FUSE_REMOVEXATTR:
+ /* just a string */
+ if(((char*)m->tx)[m->hdr->len-1] != 0)
+ bad:
+ sysfatal("readfusemsg: bad message");
+ break;
+ case FUSE_FORGET:
+ if(m->hdr->len < sizeof(struct fuse_forget_in))
+ goto bad;
+ break;
+ case FUSE_GETATTR:
+ break;
+ case FUSE_SETATTR:
+ if(m->hdr->len < sizeof(struct fuse_setattr_in))
+ goto bad;
+ break;
+ case FUSE_READLINK:
+ break;
+ case FUSE_SYMLINK:
+ /* two strings */
+ if(((char*)m->tx)[m->hdr->len-1] != 0
+ || memchr(m->tx, 0, m->hdr->len-1) == 0)
+ goto bad;
+ break;
+ case FUSE_MKNOD:
+ if(m->hdr->len <= sizeof(struct fuse_mknod_in)
+ || ((char*)m->tx)[m->hdr->len-1] != 0)
+ goto bad;
+ break;
+ case FUSE_MKDIR:
+ if(m->hdr->len <= sizeof(struct fuse_mkdir_in)
+ || ((char*)m->tx)[m->hdr->len-1] != 0)
+ goto bad;
+ break;
+ case FUSE_RENAME:
+ /* a struct and two strings */
+ if(m->hdr->len <= sizeof(struct fuse_rename_in)
+ || ((char*)m->tx)[m->hdr->len-1] != 0
+ || memchr((uchar*)m->tx+sizeof(struct fuse_rename_in), 0, m->hdr->len-sizeof(struct fuse_rename_in)-1) == 0)
+ goto bad;
+ break;
+ case FUSE_LINK:
+ if(m->hdr->len <= sizeof(struct fuse_link_in)
+ || ((char*)m->tx)[m->hdr->len-1] != 0)
+ goto bad;
+ break;
+ case FUSE_OPEN:
+ case FUSE_OPENDIR:
+ if(m->hdr->len < sizeof(struct fuse_open_in))
+ goto bad;
+ break;
+ case FUSE_READ:
+ case FUSE_READDIR:
+ if(m->hdr->len < sizeof(struct fuse_read_in))
+ goto bad;
+ break;
+ case FUSE_WRITE:
+ /* no strings, but check that write length is sane */
+ if(m->hdr->len < sizeof(struct fuse_write_in)+((struct fuse_write_in*)m->tx)->size)
+ goto bad;
+ break;
+ case FUSE_STATFS:
+ break;
+ case FUSE_RELEASE:
+ case FUSE_RELEASEDIR:
+ if(m->hdr->len < sizeof(struct fuse_release_in))
+ goto bad;
+ break;
+ case FUSE_FSYNC:
+ case FUSE_FSYNCDIR:
+ if(m->hdr->len < sizeof(struct fuse_fsync_in))
+ goto bad;
+ break;
+ case FUSE_SETXATTR:
+ /* struct and two strings */
+ if(m->hdr->len <= sizeof(struct fuse_setxattr_in)
+ || ((char*)m->tx)[m->hdr->len-1] != 0
+ || memchr((uchar*)m->tx+sizeof(struct fuse_setxattr_in), 0, m->hdr->len-sizeof(struct fuse_setxattr_in)-1) == 0)
+ goto bad;
+ break;
+ case FUSE_GETXATTR:
+ /* struct and one string */
+ if(m->hdr->len <= sizeof(struct fuse_getxattr_in)
+ || ((char*)m->tx)[m->hdr->len-1] != 0)
+ goto bad;
+ break;
+ case FUSE_LISTXATTR:
+ if(m->hdr->len < sizeof(struct fuse_getxattr_in))
+ goto bad;
+ break;
+ case FUSE_FLUSH:
+ if(m->hdr->len < sizeof(struct fuse_flush_in))
+ goto bad;
+ break;
+ case FUSE_INIT:
+ if(m->hdr->len < sizeof(struct fuse_init_in))
+ goto bad;
+ break;
+ case FUSE_ACCESS:
+ if(m->hdr->len < sizeof(struct fuse_access_in))
+ goto bad;
+ break;
+ case FUSE_CREATE:
+ if(m->hdr->len <= sizeof(struct fuse_open_in)
+ || ((char*)m->tx)[m->hdr->len-1] != 0)
+ goto bad;
+ break;
+ }
+ if(debug)
+ fprint(2, "FUSE -> %G\n", m->hdr, m->tx);
+ return m;
+}
+
+/*
+ * Reply to FUSE request m using additonal
+ * argument buffer arg of size narg bytes.
+ * Perhaps should free the FuseMsg here?
+ */
+void
+replyfuse(FuseMsg *m, void *arg, int narg)
+{
+ struct iovec vec[2];
+ struct fuse_out_header hdr;
+ int nvec;
+
+ hdr.len = sizeof hdr + narg;
+ hdr.error = 0;
+ hdr.unique = m->hdr->unique;
+ if(debug)
+ fprint(2, "FUSE <- %#G\n", m->hdr, &hdr, arg);
+
+ vec[0].iov_base = &hdr;
+ vec[0].iov_len = sizeof hdr;
+ nvec = 1;
+ if(arg && narg){
+ vec[1].iov_base = arg;
+ vec[1].iov_len = narg;
+ nvec++;
+ }
+ if(writev(fusefd, vec, nvec) < 0)
+ sysfatal("replyfuse: %r");
+}
+
+/*
+ * Reply to FUSE request m with errno e.
+ */
+void
+replyfuseerrno(FuseMsg *m, int e)
+{
+ struct fuse_out_header hdr;
+
+ hdr.len = sizeof hdr;
+ hdr.error = -e; /* FUSE sends negative errnos. */
+ hdr.unique = m->hdr->unique;
+ if(debug)
+ fprint(2, "FUSE <- %#G\n", m->hdr, &hdr, 0);
+ if(write(fusefd, &hdr, sizeof hdr) < 0)
+ sysfatal("replyfuseerror: %r");
+}
+
+void
+replyfuseerrstr(FuseMsg *m)
+{
+ replyfuseerrno(m, errstr2errno());
+}
+
+/*
+ * Mounts a fuse file system on mtpt and returns
+ * a file descriptor for the corresponding fuse
+ * message conversation.
+ */
+int
+mountfuse(char *mtpt)
+{
+ int p[2], pid, fd;
+ char buf[20];
+
+ if(socketpair(AF_UNIX, SOCK_STREAM, 0, p) < 0)
+ return -1;
+ pid = fork();
+ if(pid < 0)
+ return -1;
+ if(pid == 0){
+ close(p[1]);
+ snprint(buf, sizeof buf, "%d", p[0]);
+ putenv("_FUSE_COMMFD", buf);
+ execlp("fusermount", "fusermount", "--", mtpt, nil);
+ fprint(2, "exec fusermount: %r\n");
+ _exit(1);
+ }
+ close(p[0]);
+ fd = recvfd(p[1]);
+ close(p[1]);
+ waitpid();
+ return fd;
+}
+
+void
+unmountfuse(char *mtpt)
+{
+ int pid;
+
+ pid = fork();
+ if(pid < 0)
+ return;
+ if(pid == 0){
+ atexitdont(unmountatexit);
+ execlp("fusermount", "fusermount", "-u", "-z", "--", mtpt, nil);
+ fprint(2, "exec fusermount -u: %r\n");
+ _exit(1);
+ }
+ waitpid();
+}
+
+char *fusemtpt;
+void
+unmountatexit(void)
+{
+ if(fusemtpt)
+ unmountfuse(fusemtpt);
+}
+
+void
+initfuse(char *mtpt)
+{
+ FuseMsg *m;
+ struct fuse_init_in *tx;
+ struct fuse_init_out rx;
+
+ fusemtpt = mtpt;
+
+ /*
+ * The 4096 is for the message headers.
+ * It's a lot, but it's what the FUSE libraries ask for.
+ */
+ fusemaxwrite = getpagesize();
+ fusebufsize = 4096 + fusemaxwrite;
+
+ if((fusefd = mountfuse(mtpt)) < 0)
+ sysfatal("mountfuse: %r");
+
+ if((m = readfusemsg()) == nil)
+ sysfatal("readfusemsg: %r");
+ if(m->hdr->opcode != FUSE_INIT)
+ sysfatal("fuse: expected FUSE_INIT (26) got %d", m->hdr->opcode);
+ tx = m->tx;
+
+ /*
+ * Complain if the kernel is too new.
+ * We could forge ahead, but at least the one time I tried,
+ * the kernel rejected the newer version by making the
+ * writev fail in replyfuse, which is a much more confusing
+ * error message. In the future, might be nice to try to
+ * support older versions that differ only slightly.
+ */
+ if(tx->major < FUSE_KERNEL_VERSION
+ || (tx->major == FUSE_KERNEL_VERSION && tx->minor < FUSE_KERNEL_MINOR_VERSION))
+ sysfatal("fuse: too kernel version %d.%d older than program version %d.%d",
+ tx->major, tx->minor, FUSE_KERNEL_VERSION, FUSE_KERNEL_MINOR_VERSION);
+
+ memset(&rx, 0, sizeof rx);
+ rx.major = FUSE_KERNEL_VERSION;
+ rx.minor = FUSE_KERNEL_MINOR_VERSION;
+ rx.max_write = fusemaxwrite;
+ replyfuse(m, &rx, sizeof rx);
+ freefusemsg(m);
+}
+
+/*
+ * Print FUSE messages. Assuming it is installed as %G,
+ * use %G with hdr, arg arguments to format a request,
+ * and %#G with reqhdr, hdr, arg arguments to format a response.
+ * The reqhdr is necessary in the %#G form because the
+ * response does not contain an opcode tag.
+ */
+int
+fusefmt(Fmt *fmt)
+{
+ struct fuse_in_header *hdr = va_arg(fmt->args, void*);
+ if((fmt->flags&FmtSharp) == 0){ /* "%G", hdr, arg */
+ void *a = va_arg(fmt->args, void*);
+ fmtprint(fmt, "len %d unique %#llux uid %d gid %d pid %d ",
+ hdr->len, hdr->unique, hdr->uid, hdr->gid, hdr->pid);
+
+ switch(hdr->opcode){
+ default: {
+ fmtprint(fmt, "??? opcode %d", hdr->opcode);
+ break;
+ }
+ case FUSE_LOOKUP: {
+ fmtprint(fmt, "Lookup nodeid %#llux name %#q",
+ hdr->nodeid, a);
+ break;
+ }
+ case FUSE_FORGET: {
+ struct fuse_forget_in *tx = a;
+ /* nlookup (a ref count) is a vlong! */
+ fmtprint(fmt, "Forget nodeid %#llux nlookup %lld",
+ hdr->nodeid, tx->nlookup);
+ break;
+ }
+ case FUSE_GETATTR: {
+ fmtprint(fmt, "Getattr nodeid %#llux", hdr->nodeid);
+ break;
+ }
+ case FUSE_SETATTR: {
+ struct fuse_setattr_in *tx = a;
+ fmtprint(fmt, "Setattr nodeid %#llux", hdr->nodeid);
+ if(tx->valid&FATTR_FH)
+ fmtprint(fmt, " fh %#llux", tx->fh);
+ if(tx->valid&FATTR_SIZE)
+ fmtprint(fmt, " size %lld", tx->size);
+ if(tx->valid&FATTR_ATIME)
+ fmtprint(fmt, " atime %.20g", tx->atime+tx->atimensec*1e-9);
+ if(tx->valid&FATTR_MTIME)
+ fmtprint(fmt, " mtime %.20g", tx->mtime+tx->mtimensec*1e-9);
+ if(tx->valid&FATTR_MODE)
+ fmtprint(fmt, " mode %#uo", tx->mode);
+ if(tx->valid&FATTR_UID)
+ fmtprint(fmt, " uid %d", tx->uid);
+ if(tx->valid&FATTR_GID)
+ fmtprint(fmt, " gid %d", tx->gid);
+ break;
+ }
+ case FUSE_READLINK: {
+ fmtprint(fmt, "Readlink nodeid %#llux", hdr->nodeid);
+ break;
+ }
+ case FUSE_SYMLINK: {
+ char *old, *new;
+
+ old = a;
+ new = a + strlen(a) + 1;
+ fmtprint(fmt, "Symlink nodeid %#llux old %#q new %#q",
+ hdr->nodeid, old, new);
+ break;
+ }
+ case FUSE_MKNOD: {
+ struct fuse_mknod_in *tx = a;
+ fmtprint(fmt, "Mknod nodeid %#llux mode %#uo rdev %#ux name %#q",
+ hdr->nodeid, tx->mode, tx->rdev, tx+1);
+ break;
+ }
+ case FUSE_MKDIR: {
+ struct fuse_mkdir_in *tx = a;
+ fmtprint(fmt, "Mkdir nodeid %#llux mode %#uo name %#q",
+ hdr->nodeid, tx->mode, tx+1);
+ break;
+ }
+ case FUSE_UNLINK: {
+ fmtprint(fmt, "Unlink nodeid %#llux name %#q",
+ hdr->nodeid, a);
+ break;
+ }
+ case FUSE_RMDIR: {
+ fmtprint(fmt, "Rmdir nodeid %#llux name %#q",
+ hdr->nodeid, a);
+ break;
+ }
+ case FUSE_RENAME: {
+ struct fuse_rename_in *tx = a;
+ char *old = (char*)(tx+1);
+ char *new = old + strlen(old) + 1;
+ fmtprint(fmt, "Rename nodeid %#llux old %#q newdir %#llux new %#q",
+ hdr->nodeid, old, tx->newdir, new);
+ break;
+ }
+ case FUSE_LINK: {
+ struct fuse_link_in *tx = a;
+ fmtprint(fmt, "Link oldnodeid %#llux nodeid %#llux name %#q",
+ tx->oldnodeid, hdr->nodeid, tx+1);
+ break;
+ }
+ case FUSE_OPEN: {
+ struct fuse_open_in *tx = a;
+ /* Should one or both of flags and mode be octal? */
+ fmtprint(fmt, "Open nodeid %#llux flags %#ux mode %#ux",
+ hdr->nodeid, tx->flags, tx->mode);
+ break;
+ }
+ case FUSE_READ: {
+ struct fuse_read_in *tx = a;
+ fmtprint(fmt, "Read nodeid %#llux fh %#llux offset %lld size %ud",
+ hdr->nodeid, tx->fh, tx->offset, tx->size);
+ break;
+ }
+ case FUSE_WRITE: {
+ struct fuse_write_in *tx = a;
+ fmtprint(fmt, "Write nodeid %#llux fh %#llux offset %lld size %ud flags %#ux",
+ hdr->nodeid, tx->fh, tx->offset, tx->size, tx->write_flags);
+ break;
+ }
+ case FUSE_STATFS: {
+ fmtprint(fmt, "Statfs");
+ break;
+ }
+ case FUSE_RELEASE: {
+ struct fuse_release_in *tx = a;
+ fmtprint(fmt, "Release nodeid %#llux fh %#llux flags %#ux",
+ hdr->nodeid, tx->fh, tx->flags);
+ break;
+ }
+ case FUSE_FSYNC: {
+ struct fuse_fsync_in *tx = a;
+ fmtprint(fmt, "Fsync nodeid %#llux fh %#llux flags %#ux",
+ hdr->nodeid, tx->fh, tx->fsync_flags);
+ break;
+ }
+ case FUSE_SETXATTR: {
+ struct fuse_setxattr_in *tx = a;
+ char *name = (char*)(tx+1);
+ char *value = name + strlen(name) + 1;
+ fmtprint(fmt, "Setxattr nodeid %#llux size %d flags %#ux name %#q value %#q",
+ hdr->nodeid, tx->size, tx->flags, name, value);
+ break;
+ }
+ case FUSE_GETXATTR: {
+ struct fuse_getxattr_in *tx = a;
+ fmtprint(fmt, "Getxattr nodeid %#llux size %d name %#q",
+ hdr->nodeid, tx->size, tx+1);
+ break;
+ }
+ case FUSE_LISTXATTR: {
+ struct fuse_getxattr_in *tx = a;
+ fmtprint(fmt, "Listxattr nodeid %#llux size %d",
+ hdr->nodeid, tx->size);
+ break;
+ }
+ case FUSE_REMOVEXATTR: {
+ fmtprint(fmt, "Removexattr nodeid %#llux name %#q",
+ hdr->nodeid, a);
+ break;
+ }
+ case FUSE_FLUSH: {
+ struct fuse_flush_in *tx = a;
+ fmtprint(fmt, "Flush nodeid %#llux fh %#llux flags %#ux",
+ hdr->nodeid, tx->fh, tx->flush_flags);
+ break;
+ }
+ case FUSE_INIT: {
+ struct fuse_init_in *tx = a;
+ fmtprint(fmt, "Init major %d minor %d",
+ tx->major, tx->minor);
+ break;
+ }
+ case FUSE_OPENDIR: {
+ struct fuse_open_in *tx = a;
+ fmtprint(fmt, "Opendir nodeid %#llux flags %#ux mode %#ux",
+ hdr->nodeid, tx->flags, tx->mode);
+ break;
+ }
+ case FUSE_READDIR: {
+ struct fuse_read_in *tx = a;
+ fmtprint(fmt, "Readdir nodeid %#llux fh %#llux offset %lld size %ud",
+ hdr->nodeid, tx->fh, tx->offset, tx->size);
+ break;
+ }
+ case FUSE_RELEASEDIR: {
+ struct fuse_release_in *tx = a;
+ fmtprint(fmt, "Releasedir nodeid %#llux fh %#llux flags %#ux",
+ hdr->nodeid, tx->fh, tx->flags);
+ break;
+ }
+ case FUSE_FSYNCDIR: {
+ struct fuse_fsync_in *tx = a;
+ fmtprint(fmt, "Fsyncdir nodeid %#llux fh %#llux flags %#ux",
+ hdr->nodeid, tx->fh, tx->fsync_flags);
+ break;
+ }
+ case FUSE_ACCESS: {
+ struct fuse_access_in *tx = a;
+ fmtprint(fmt, "Access nodeid %#llux mask %#ux",
+ hdr->nodeid, tx->mask);
+ break;
+ }
+ case FUSE_CREATE: {
+ struct fuse_open_in *tx = a;
+ fmtprint(fmt, "Create nodeid %#llx flags %#ux mode %#ux name %#q",
+ hdr->nodeid, tx->flags, tx->mode, tx+1);
+ break;
+ }
+ }
+ }else{ /* "%#G", reqhdr, hdr, arg - use reqhdr only for type */
+ struct fuse_out_header *ohdr = va_arg(fmt->args, void*);
+ void *a = va_arg(fmt->args, void*);
+ int len = ohdr->len - sizeof *ohdr;
+ fmtprint(fmt, "unique %#llux ", ohdr->unique);
+ if(ohdr->error){
+ fmtprint(fmt, "error %d %s", ohdr->error, strerror(-ohdr->error));
+ }else
+ switch(hdr->opcode){
+ default: {
+ fmtprint(fmt, "??? opcode %d", hdr->opcode);
+ break;
+ }
+ case FUSE_LOOKUP: {
+ /*
+ * For a negative entry, can send back ENOENT
+ * or rx->ino == 0.
+ * In protocol version 7.4 and before, can only use
+ * the ENOENT method.
+ * Presumably the benefit of sending rx->ino == 0
+ * is that you can specify the length of time to cache
+ * the negative result.
+ */
+ struct fuse_entry_out *rx;
+ fmtprint(fmt, "(Lookup) ");
+ fmt_entry_out:
+ rx = a;
+ fmtprint(fmt, "nodeid %#llux gen %#llux entry_valid %.20g attr_valid %.20g ",
+ rx->nodeid, rx->generation,
+ rx->entry_valid+rx->entry_valid_nsec*1e-9,
+ rx->attr_valid+rx->attr_valid_nsec*1e-9);
+ fmtprint(fmt, " ino %#llux size %lld blocks %lld atime %.20g mtime %.20g ctime %.20g mode %#uo nlink %d uid %d gid %d rdev %#ux",
+ rx->attr.ino, rx->attr.size, rx->attr.blocks,
+ rx->attr.atime+rx->attr.atimensec*1e-9,
+ rx->attr.mtime+rx->attr.mtimensec*1e-9,
+ rx->attr.ctime+rx->attr.ctimensec*1e-9,
+ rx->attr.mode, rx->attr.nlink, rx->attr.uid,
+ rx->attr.gid, rx->attr.rdev);
+ break;
+ }
+ case FUSE_FORGET: {
+ /* Can't happen! No reply. */
+ fmtprint(fmt, "(Forget) can't happen");
+ break;
+ }
+ case FUSE_GETATTR: {
+ struct fuse_attr_out *rx;
+ fmtprint(fmt, "(Getattr) ");
+ fmt_attr_out:
+ rx = a;
+ fmtprint(fmt, "attr_valid %.20g",
+ rx->attr_valid+rx->attr_valid_nsec*1e-9);
+ fmtprint(fmt, " ino %#llux size %lld blocks %lld atime %.20g mtime %.20g ctime %.20g mode %#uo nlink %d uid %d gid %d rdev %#ux",
+ rx->attr.ino, rx->attr.size, rx->attr.blocks,
+ rx->attr.atime+rx->attr.atimensec*1e-9,
+ rx->attr.mtime+rx->attr.mtimensec*1e-9,
+ rx->attr.ctime+rx->attr.ctimensec*1e-9,
+ rx->attr.mode, rx->attr.nlink, rx->attr.uid,
+ rx->attr.gid, rx->attr.rdev);
+ break;
+ }
+ case FUSE_SETATTR: {
+ fmtprint(fmt, "(Setattr) ");
+ goto fmt_attr_out;
+ break;
+ }
+ case FUSE_READLINK: {
+ fmtprint(fmt, "(Readlink) %#.*q",
+ utfnlen(a, len), a);
+ break;
+ }
+ case FUSE_SYMLINK: {
+ fmtprint(fmt, "(Symlink) ");
+ goto fmt_entry_out;
+ break;
+ }
+ case FUSE_MKNOD: {
+ fmtprint(fmt, "(Mknod) ");
+ goto fmt_entry_out;
+ break;
+ }
+ case FUSE_MKDIR: {
+ fmtprint(fmt, "(Mkdir) ");
+ goto fmt_entry_out;
+ break;
+ }
+ case FUSE_UNLINK: {
+ fmtprint(fmt, "(Unlink)");
+ break;
+ }
+ case FUSE_RMDIR: {
+ fmtprint(fmt, "(Rmdir)");
+ break;
+ }
+ case FUSE_RENAME: {
+ fmtprint(fmt, "(Rename)");
+ break;
+ }
+ case FUSE_LINK: {
+ fmtprint(fmt, "(Link) ");
+ goto fmt_entry_out;
+ break;
+ }
+ case FUSE_OPEN: {
+ struct fuse_open_out *rx;
+ fmtprint(fmt, "(Open) ");
+ fmt_open_out:
+ rx = a;
+ fmtprint(fmt, "fh %#llux flags %#ux", rx->fh, rx->open_flags);
+ break;
+ }
+ case FUSE_READ: {
+ fmtprint(fmt, "(Read) size %d", len);
+ break;
+ }
+ case FUSE_WRITE: {
+ struct fuse_write_out *rx = a;
+ fmtprint(fmt, "(Write) size %d", rx->size);
+ break;
+ }
+ case FUSE_STATFS: {
+ /*
+ * Before protocol version 7.4, only first 48 bytes are used.
+ */
+ struct fuse_statfs_out *rx = a;
+ fmtprint(fmt, "(Statfs) blocks %lld bfree %lld bavail %lld files %lld ffree %lld bsize %ud namelen %ud frsize %ud",
+ rx->st.blocks, rx->st.bfree, rx->st.bavail,
+ rx->st.files, rx->st.ffree, rx->st.bsize,
+ rx->st.namelen, rx->st.frsize);
+ break;
+ }
+ case FUSE_RELEASE: {
+ fmtprint(fmt, "(Release)");
+ break;
+ }
+ case FUSE_FSYNC: {
+ fmtprint(fmt, "(Fsync)");
+ break;
+ }
+ case FUSE_SETXATTR: {
+ fmtprint(fmt, "(Serxattr)");
+ break;
+ }
+ case FUSE_GETXATTR: {
+ fmtprint(fmt, "(Getxattr) size %d", len);
+ break;
+ }
+ case FUSE_LISTXATTR: {
+ fmtprint(fmt, "(Lisrxattr) size %d", len);
+ break;
+ }
+ case FUSE_REMOVEXATTR: {
+ fmtprint(fmt, "(Removexattr)");
+ break;
+ }
+ case FUSE_FLUSH: {
+ fmtprint(fmt, "(Flush)");
+ break;
+ }
+ case FUSE_INIT: {
+ struct fuse_init_out *rx = a;
+ fmtprint(fmt, "(Init) major %d minor %d max_write %d",
+ rx->major, rx->minor, rx->max_write);
+ break;
+ }
+ case FUSE_OPENDIR: {
+ fmtprint(fmt, "(Opendir) ");
+ goto fmt_open_out;
+ break;
+ }
+ case FUSE_READDIR: {
+ fmtprint(fmt, "(Readdir) size %d", len);
+ break;
+ }
+ case FUSE_RELEASEDIR: {
+ fmtprint(fmt, "(Releasedir)");
+ break;
+ }
+ case FUSE_FSYNCDIR: {
+ fmtprint(fmt, "(Fsyncdir)");
+ break;
+ }
+ case FUSE_ACCESS: {
+ fmtprint(fmt, "(Access)");
+ break;
+ }
+ case FUSE_CREATE: {
+ struct fuse_create_out *rx = a;
+ fmtprint(fmt, "(Create) ");
+ fmtprint(fmt, "nodeid %#llux gen %#llux entry_valid %.20g attr_valid %.20g ",
+ rx->e.nodeid, rx->e.generation,
+ rx->e.entry_valid+rx->e.entry_valid_nsec*1e-9,
+ rx->e.attr_valid+rx->e.attr_valid_nsec*1e-9);
+ fmtprint(fmt, " ino %#llux size %lld blocks %lld atime %.20g mtime %.20g ctime %.20g mode %#uo nlink %d uid %d gid %d rdev %#ux",
+ rx->e.attr.ino, rx->e.attr.size, rx->e.attr.blocks,
+ rx->e.attr.atime+rx->e.attr.atimensec*1e-9,
+ rx->e.attr.mtime+rx->e.attr.mtimensec*1e-9,
+ rx->e.attr.ctime+rx->e.attr.ctimensec*1e-9,
+ rx->e.attr.mode, rx->e.attr.nlink, rx->e.attr.uid,
+ rx->e.attr.gid, rx->e.attr.rdev);
+ fmtprint(fmt, " fh %#llux flags %#ux", rx->o.fh, rx->o.open_flags);
+ break;
+ }
+ }
+ }
+ return 0;
+}
+
diff --git a/src/cmd/9pfuse/fuse_kernel.h b/src/cmd/9pfuse/fuse_kernel.h
new file mode 100644
index 00000000..67843acb
--- /dev/null
+++ b/src/cmd/9pfuse/fuse_kernel.h
@@ -0,0 +1,312 @@
+/* This file defines the kernel interface of FUSE */
+
+/*
+ This -- and only this -- header file may also be distributed under
+ the terms of the BSD Licence as follows:
+
+ Copyright (C) 2001-2006 Miklos Szeredi. All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ SUCH DAMAGE.
+*/
+
+/* RSC changed these lines */
+#include <inttypes.h>
+#define __u64 uint64_t
+#define __u32 uint32_t
+#define __s32 int32_t
+
+/** Version number of this interface */
+#define FUSE_KERNEL_VERSION 7
+
+/** Minor version number of this interface */
+#define FUSE_KERNEL_MINOR_VERSION 5
+
+/** The node ID of the root inode */
+#define FUSE_ROOT_ID 1
+
+/** The major number of the fuse character device */
+#define FUSE_MAJOR 10
+
+/** The minor number of the fuse character device */
+#define FUSE_MINOR 229
+
+/* Make sure all structures are padded to 64bit boundary, so 32bit
+ userspace works under 64bit kernels */
+
+struct fuse_attr {
+ __u64 ino;
+ __u64 size;
+ __u64 blocks;
+ __u64 atime;
+ __u64 mtime;
+ __u64 ctime;
+ __u32 atimensec;
+ __u32 mtimensec;
+ __u32 ctimensec;
+ __u32 mode;
+ __u32 nlink;
+ __u32 uid;
+ __u32 gid;
+ __u32 rdev;
+};
+
+struct fuse_kstatfs {
+ __u64 blocks;
+ __u64 bfree;
+ __u64 bavail;
+ __u64 files;
+ __u64 ffree;
+ __u32 bsize;
+ __u32 namelen;
+ __u32 frsize;
+ __u32 padding;
+ __u32 spare[6];
+};
+
+#define FATTR_MODE (1 << 0)
+#define FATTR_UID (1 << 1)
+#define FATTR_GID (1 << 2)
+#define FATTR_SIZE (1 << 3)
+#define FATTR_ATIME (1 << 4)
+#define FATTR_MTIME (1 << 5)
+#define FATTR_FH (1 << 6)
+
+/**
+ * Flags returned by the OPEN request
+ *
+ * FOPEN_DIRECT_IO: bypass page cache for this open file
+ * FOPEN_KEEP_CACHE: don't invalidate the data cache on open
+ */
+#define FOPEN_DIRECT_IO (1 << 0)
+#define FOPEN_KEEP_CACHE (1 << 1)
+
+enum fuse_opcode {
+ FUSE_LOOKUP = 1,
+ FUSE_FORGET = 2, /* no reply */
+ FUSE_GETATTR = 3,
+ FUSE_SETATTR = 4,
+ FUSE_READLINK = 5,
+ FUSE_SYMLINK = 6,
+ FUSE_MKNOD = 8,
+ FUSE_MKDIR = 9,
+ FUSE_UNLINK = 10,
+ FUSE_RMDIR = 11,
+ FUSE_RENAME = 12,
+ FUSE_LINK = 13,
+ FUSE_OPEN = 14,
+ FUSE_READ = 15,
+ FUSE_WRITE = 16,
+ FUSE_STATFS = 17,
+ FUSE_RELEASE = 18,
+ FUSE_FSYNC = 20,
+ FUSE_SETXATTR = 21,
+ FUSE_GETXATTR = 22,
+ FUSE_LISTXATTR = 23,
+ FUSE_REMOVEXATTR = 24,
+ FUSE_FLUSH = 25,
+ FUSE_INIT = 26,
+ FUSE_OPENDIR = 27,
+ FUSE_READDIR = 28,
+ FUSE_RELEASEDIR = 29,
+ FUSE_FSYNCDIR = 30,
+ FUSE_ACCESS = 34,
+ FUSE_CREATE = 35
+};
+
+/* The read buffer is required to be at least 8k, but may be much larger */
+#define FUSE_MIN_READ_BUFFER 8192
+
+struct fuse_entry_out {
+ __u64 nodeid; /* Inode ID */
+ __u64 generation; /* Inode generation: nodeid:gen must
+ be unique for the fs's lifetime */
+ __u64 entry_valid; /* Cache timeout for the name */
+ __u64 attr_valid; /* Cache timeout for the attributes */
+ __u32 entry_valid_nsec;
+ __u32 attr_valid_nsec;
+ struct fuse_attr attr;
+};
+
+struct fuse_forget_in {
+ __u64 nlookup;
+};
+
+struct fuse_attr_out {
+ __u64 attr_valid; /* Cache timeout for the attributes */
+ __u32 attr_valid_nsec;
+ __u32 dummy;
+ struct fuse_attr attr;
+};
+
+struct fuse_mknod_in {
+ __u32 mode;
+ __u32 rdev;
+};
+
+struct fuse_mkdir_in {
+ __u32 mode;
+ __u32 padding;
+};
+
+struct fuse_rename_in {
+ __u64 newdir;
+};
+
+struct fuse_link_in {
+ __u64 oldnodeid;
+};
+
+struct fuse_setattr_in {
+ __u32 valid;
+ __u32 padding;
+ __u64 fh;
+ __u64 size;
+ __u64 unused1;
+ __u64 atime;
+ __u64 mtime;
+ __u64 unused2;
+ __u32 atimensec;
+ __u32 mtimensec;
+ __u32 unused3;
+ __u32 mode;
+ __u32 unused4;
+ __u32 uid;
+ __u32 gid;
+ __u32 unused5;
+};
+
+struct fuse_open_in {
+ __u32 flags;
+ __u32 mode;
+};
+
+struct fuse_open_out {
+ __u64 fh;
+ __u32 open_flags;
+ __u32 padding;
+};
+
+struct fuse_release_in {
+ __u64 fh;
+ __u32 flags;
+ __u32 padding;
+};
+
+struct fuse_flush_in {
+ __u64 fh;
+ __u32 flush_flags;
+ __u32 padding;
+};
+
+struct fuse_read_in {
+ __u64 fh;
+ __u64 offset;
+ __u32 size;
+ __u32 padding;
+};
+
+struct fuse_write_in {
+ __u64 fh;
+ __u64 offset;
+ __u32 size;
+ __u32 write_flags;
+};
+
+struct fuse_write_out {
+ __u32 size;
+ __u32 padding;
+};
+
+#define FUSE_COMPAT_STATFS_SIZE 48
+
+struct fuse_statfs_out {
+ struct fuse_kstatfs st;
+};
+
+struct fuse_fsync_in {
+ __u64 fh;
+ __u32 fsync_flags;
+ __u32 padding;
+};
+
+struct fuse_setxattr_in {
+ __u32 size;
+ __u32 flags;
+};
+
+struct fuse_getxattr_in {
+ __u32 size;
+ __u32 padding;
+};
+
+struct fuse_getxattr_out {
+ __u32 size;
+ __u32 padding;
+};
+
+struct fuse_access_in {
+ __u32 mask;
+ __u32 padding;
+};
+
+struct fuse_init_in {
+ __u32 major;
+ __u32 minor;
+};
+
+struct fuse_init_out {
+ __u32 major;
+ __u32 minor;
+ __u32 unused[3];
+ __u32 max_write;
+};
+
+struct fuse_in_header {
+ __u32 len;
+ __u32 opcode;
+ __u64 unique;
+ __u64 nodeid;
+ __u32 uid;
+ __u32 gid;
+ __u32 pid;
+ __u32 padding;
+};
+
+struct fuse_out_header {
+ __u32 len;
+ __s32 error;
+ __u64 unique;
+};
+
+/* RSC changed name[0] to name[1] for old C compilers */
+struct fuse_dirent {
+ __u64 ino;
+ __u64 off;
+ __u32 namelen;
+ __u32 type;
+ char name[1];
+};
+
+#define FUSE_NAME_OFFSET ((unsigned) ((struct fuse_dirent *) 0)->name)
+#define FUSE_DIRENT_ALIGN(x) (((x) + sizeof(__u64) - 1) & ~(sizeof(__u64) - 1))
+#define FUSE_DIRENT_SIZE(d) \
+ FUSE_DIRENT_ALIGN(FUSE_NAME_OFFSET + (d)->namelen)
diff --git a/src/cmd/9pfuse/main.c b/src/cmd/9pfuse/main.c
new file mode 100644
index 00000000..15d47146
--- /dev/null
+++ b/src/cmd/9pfuse/main.c
@@ -0,0 +1,1169 @@
+/*
+ * 9P to FUSE translator. Acts as FUSE server, 9P client.
+ * Mounts 9P servers via FUSE kernel module.
+ *
+ * There are four procs in this threaded program
+ * (ignoring the one that runs main and then exits).
+ * The first proc reads FUSE requests from /dev/fuse.
+ * It sends the requests over a channel to a second proc,
+ * which serves the requests. Each request runs in a
+ * thread in that second proc. Those threads do write
+ * FUSE replies, which in theory might block, but in practice don't.
+ * The 9P interactions are handled by lib9pclient, which
+ * allocates two more procs, one for reading and one for
+ * writing the 9P connection. Thus the many threads in the
+ * request proc can do 9P interactions without blocking.
+ *
+ * TODO: graceful shutdown.
+ */
+
+#define _GNU_SOURCE 1 /* for O_DIRECTORY */
+#include "a.h"
+
+int debug;
+char *argv0;
+void fusedispatch(void*);
+Channel *fusechan;
+
+enum
+{
+ STACK = 8192
+};
+
+/*
+ * The number of seconds that the kernel can cache
+ * returned file attributes. FUSE's default is 1.0.
+ * I haven't experimented with using 0.
+ */
+double attrtimeout = 1.0;
+
+/*
+ * The number of seconds that the kernel can cache
+ * the returned entry nodeids returned by lookup.
+ * I haven't experimented with other values.
+ */
+double entrytimeout = 1.0;
+
+CFsys *fsys;
+CFid *fsysroot;
+void init9p(char*);
+
+void
+usage(void)
+{
+ fprint(2, "usage: 9pfuse [-D] [-a attrtimeout] address mtpt\n");
+ exit(1);
+}
+
+void fusereader(void*);
+
+void
+threadmain(int argc, char **argv)
+{
+ ARGBEGIN{
+ case 'D':
+ chatty9pclient++;
+ debug++;
+ break;
+ case 'a':
+ attrtimeout = atof(EARGF(usage()));
+ break;
+ default:
+ usage();
+ }ARGEND
+
+ if(argc != 2)
+ usage();
+
+ quotefmtinstall();
+ fmtinstall('F', fcallfmt);
+ fmtinstall('M', dirmodefmt);
+ fmtinstall('G', fusefmt);
+
+ init9p(argv[0]);
+ initfuse(argv[1]);
+
+ fusechan = chancreate(sizeof(void*), 0);
+ proccreate(fusedispatch, nil, STACK);
+ sendp(fusechan, nil); /* sync */
+
+ proccreate(fusereader, nil, STACK);
+ threadexits(0);
+}
+
+void
+fusereader(void *v)
+{
+ FuseMsg *m;
+
+ while((m = readfusemsg()) != nil)
+ sendp(fusechan, m);
+
+ fusemtpt = nil; /* no need to unmount */
+ threadexitsall(0);
+}
+
+void
+init9p(char *addr)
+{
+ int fd;
+
+ if((fd = dial(netmkaddr(addr, "tcp", "564"), nil, nil, nil)) < 0)
+ sysfatal("dial %s: %r", addr);
+ if((fsys = fsmount(fd, "")) == nil)
+ sysfatal("fsmount: %r");
+ fsysroot = fsroot(fsys);
+}
+
+int
+errstr2errno(void)
+{
+ /* TODO: a better job */
+ return EPERM;
+}
+
+/*
+ * FUSE uses nodeids to refer to active "struct inodes"
+ * (9P's unopened fids). FUSE uses fhs to refer to active
+ * "struct fuse_files" (9P's opened fids). The choice of
+ * numbers is up to us except that nodeid 1 is the root directory.
+ * We use the same number space for both and call the
+ * bookkeeping structure a FuseFid.
+ *
+ * FUSE requires nodeids to have associated generation
+ * numbers. If we reuse a nodeid, we have to bump the
+ * generation number to guarantee that the nodeid,gen
+ * combination is never reused.
+ *
+ * There are also inode numbers returned in directory reads
+ * and file attributes, but these do NOT need to match the nodeids.
+ * We use a combination of qid.path and qid.type as the inode
+ * number.
+ */
+/*
+ * TO DO: reference count the fids.
+ */
+typedef struct Fusefid Fusefid;
+struct Fusefid
+{
+ Fusefid *next;
+ CFid *fid;
+ int ref;
+ int id;
+ int gen;
+ int isnodeid;
+
+ /* directory read state */
+ Dir *d0;
+ Dir *d;
+ int nd;
+ int off;
+};
+
+Fusefid **fusefid;
+int nfusefid;
+Fusefid *freefusefidlist;
+
+Fusefid*
+allocfusefid(void)
+{
+ Fusefid *f;
+
+ if((f = freefusefidlist) == nil){
+ f = emalloc(sizeof *f);
+ fusefid = erealloc(fusefid, (nfusefid+1)*sizeof *fusefid);
+ f->id = nfusefid;
+fprint(2, "allocfusefid %d %p\n", f->id, f);
+ fusefid[f->id] = f;
+ nfusefid++;
+ }else
+ freefusefidlist = f->next;
+ f->next = nil;
+ f->ref = 1;
+ f->isnodeid = -1;
+ return f;
+}
+
+void
+freefusefid(Fusefid *f)
+{
+ if(--f->ref > 0)
+ return;
+ assert(f->ref == 0);
+ if(f->fid)
+ fsclose(f->fid);
+ if(f->d0)
+ free(f->d0);
+ f->off = 0;
+ f->d0 = nil;
+ f->fid = nil;
+ f->d = nil;
+ f->nd = 0;
+ f->next = freefusefidlist;
+ f->isnodeid = -1;
+ freefusefidlist = f;
+}
+
+uvlong
+_alloc(CFid *fid, int isnodeid)
+{
+ Fusefid *ff;
+
+ ff = allocfusefid();
+ ff->fid = fid;
+ ff->isnodeid = isnodeid;
+ ff->gen++;
+ return ff->id+2; /* skip 0 and 1 */
+}
+
+uvlong
+allocfh(CFid *fid)
+{
+ return _alloc(fid, 0);
+}
+uvlong
+allocnodeid(CFid *fid)
+{
+ return _alloc(fid, 1);
+}
+
+Fusefid*
+lookupfusefid(uvlong id, int isnodeid)
+{
+ Fusefid *ff;
+ if(id < 2 || id >= nfusefid+2)
+ return nil;
+ ff = fusefid[(int)id-2];
+ if(ff->isnodeid != isnodeid)
+ return nil;
+ return ff;
+}
+
+CFid*
+_lookupcfid(uvlong id, int isnodeid)
+{
+ Fusefid *ff;
+
+ if((ff = lookupfusefid(id, isnodeid)) == nil)
+ return nil;
+ return ff->fid;
+}
+
+CFid*
+fh2fid(uvlong fh)
+{
+ return _lookupcfid(fh, 0);
+}
+
+CFid*
+nodeid2fid(uvlong nodeid)
+{
+ if(nodeid == 1)
+ return fsysroot;
+ return _lookupcfid(nodeid, 1);
+}
+
+uvlong
+qid2inode(Qid q)
+{
+ return q.path | ((uvlong)q.type<<56);
+}
+
+void
+dir2attr(Dir *d, struct fuse_attr *attr)
+{
+ attr->ino = qid2inode(d->qid);
+ attr->size = d->length;
+ attr->blocks = (d->length+8191)/8192;
+ attr->atime = d->atime;
+ attr->mtime = d->mtime;
+ attr->ctime = d->mtime; /* not right */
+ attr->atimensec = 0;
+ attr->mtimensec = 0;
+ attr->ctimensec = 0;
+ attr->mode = d->mode&0777;
+ if(d->mode&DMDIR)
+ attr->mode |= S_IFDIR;
+ else
+ attr->mode |= S_IFREG;
+ attr->nlink = 1; /* works for directories! - see FUSE FAQ */
+ attr->uid = getuid();
+ attr->gid = getgid();
+ attr->rdev = 0;
+}
+
+void
+f2timeout(double f, __u64 *s, __u32 *ns)
+{
+ *s = f;
+ *ns = (f - (int)f)*1e9;
+}
+
+void
+dir2attrout(Dir *d, struct fuse_attr_out *out)
+{
+ f2timeout(attrtimeout, &out->attr_valid, &out->attr_valid_nsec);
+ dir2attr(d, &out->attr);
+}
+
+/*
+ * Lookup. Walk to the name given as the argument.
+ * The response is a fuse_entry_out giving full stat info.
+ */
+void
+fuselookup(FuseMsg *m)
+{
+ char *name;
+ Fusefid *ff;
+ CFid *fid, *newfid;
+ Dir *d;
+ struct fuse_entry_out out;
+
+ name = m->tx;
+ if((fid = nodeid2fid(m->hdr->nodeid)) == nil){
+ replyfuseerrno(m, ESTALE);
+ return;
+ }
+ if(strchr(name, '/')){
+ replyfuseerrno(m, ENOENT);
+ return;
+ }
+ if((newfid = fswalk(fid, name)) == nil){
+ replyfuseerrstr(m);
+ return;
+ }
+ if((d = fsdirfstat(newfid)) == nil){
+ fsclose(newfid);
+ replyfuseerrstr(m);
+ return;
+ }
+ out.nodeid = allocnodeid(newfid);
+ ff = lookupfusefid(out.nodeid, 1);
+ out.generation = ff->gen;
+ f2timeout(attrtimeout, &out.attr_valid, &out.attr_valid_nsec);
+ f2timeout(entrytimeout, &out.entry_valid, &out.entry_valid_nsec);
+ dir2attr(d, &out.attr);
+ free(d);
+ replyfuse(m, &out, sizeof out);
+}
+
+/*
+ * Forget. Reference-counted clunk for nodeids.
+ * Does not send a reply.
+ * Each lookup response gives the kernel an additional reference
+ * to the returned nodeid. Forget says "drop this many references
+ * to this nodeid". Our fuselookup, when presented with the same query,
+ * does not return the same results (it allocates a new nodeid for each
+ * call), but if that ever changes, fuseforget already handles the ref
+ * counts properly.
+ */
+void
+fuseforget(FuseMsg *m)
+{
+ struct fuse_forget_in *in;
+ Fusefid *ff;
+
+ in = m->tx;
+ if((ff = lookupfusefid(m->hdr->nodeid, 1)) == nil)
+ return;
+ if(ff->ref > in->nlookup){
+ ff->ref -= in->nlookup;
+ return;
+ }
+ if(ff->ref < in->nlookup)
+ fprint(2, "bad count in forget\n");
+ ff->ref = 1;
+ freefusefid(ff);
+}
+
+/*
+ * Getattr.
+ * Replies with a fuse_attr_out structure giving the
+ * attr for the requested nodeid in out.attr.
+ * Out.attr_valid and out.attr_valid_nsec give
+ * the amount of time that the attributes can
+ * be cached.
+ *
+ * Empirically, though, if I run ls -ld on the root
+ * twice back to back, I still get two getattrs,
+ * even with a one second attribute timeout!
+ */
+void
+fusegetattr(FuseMsg *m)
+{
+ CFid *fid;
+ struct fuse_attr_out out;
+ Dir *d;
+
+ if((fid = nodeid2fid(m->hdr->nodeid)) == nil){
+ replyfuseerrno(m, ESTALE);
+ return;
+ }
+ if((d = fsdirfstat(fid)) == nil){
+ replyfuseerrstr(m);
+ return;
+ }
+ memset(&out, 0, sizeof out);
+ dir2attrout(d, &out);
+ free(d);
+ replyfuse(m, &out, sizeof out);
+}
+
+/*
+ * Setattr.
+ * FUSE treats the many Unix attribute setting routines
+ * more or less like 9P does, with a single message.
+ */
+void
+fusesetattr(FuseMsg *m)
+{
+ CFid *fid, *nfid;
+ Dir d, *dd;
+ struct fuse_setattr_in *in;
+ struct fuse_attr_out out;
+
+ in = m->tx;
+ if(in->valid&FATTR_FH){
+ if((fid = fh2fid(in->fh)) == nil){
+ replyfuseerrno(m, ESTALE);
+ return;
+ }
+ }else{
+ if((fid = nodeid2fid(m->hdr->nodeid)) == nil){
+ replyfuseerrno(m, ESTALE);
+ return;
+ }
+ /*
+ * Special case: Linux issues a size change to
+ * truncate a file before opening it OTRUNC.
+ * Synthetic file servers (e.g., plumber) honor
+ * open(OTRUNC) but not wstat.
+ */
+ if(in->valid == FATTR_SIZE && in->size == 0){
+ if((nfid = fswalk(fid, nil)) == nil){
+ replyfuseerrstr(m);
+ return;
+ }
+ if(fsfopen(nfid, OWRITE|OTRUNC) < 0){
+ replyfuseerrstr(m);
+ fsclose(nfid);
+ }
+ fsclose(nfid);
+ goto stat;
+ }
+ }
+
+ nulldir(&d);
+ if(in->valid&FATTR_SIZE)
+ d.length = in->size;
+ if(in->valid&FATTR_ATIME)
+ d.atime = in->atime;
+ if(in->valid&FATTR_MTIME)
+ d.mtime = in->mtime;
+ if(in->valid&FATTR_MODE)
+ d.mode = in->mode;
+ if((in->valid&FATTR_UID) || (in->valid&FATTR_GID)){
+ /*
+ * I can't be bothered with these yet.
+ */
+ replyfuseerrno(m, EPERM);
+ return;
+ }
+ if(fsdirfwstat(fid, &d) < 0){
+ replyfuseerrstr(m);
+ return;
+ }
+stat:
+ if((dd = fsdirfstat(fid)) == nil){
+ replyfuseerrstr(m);
+ return;
+ }
+ memset(&out, 0, sizeof out);
+ dir2attrout(dd, &out);
+ free(dd);
+ replyfuse(m, &out, sizeof out);
+}
+
+CFid*
+_fuseopenfid(uvlong nodeid, int isdir, int openmode, int *err)
+{
+ CFid *fid, *newfid;
+
+ if((fid = nodeid2fid(nodeid)) == nil){
+ *err = ESTALE;
+ return nil;
+ }
+ if(isdir && !(fsqid(fid).type&QTDIR)){
+ *err = ENOTDIR;
+ return nil;
+ }
+ if(openmode != OREAD && fsqid(fid).type&QTDIR){
+ *err = EISDIR;
+ return nil;
+ }
+
+ /* Clone fid to get one we can open. */
+ newfid = fswalk(fid, nil);
+ if(newfid == nil){
+ *err = errstr2errno();
+ // fsclose(fid);
+ return nil;
+ }
+ // fsputfid(fid);
+
+ if(fsfopen(newfid, openmode) < 0){
+ *err = errstr2errno();
+ fsclose(newfid);
+ return nil;
+ }
+
+ return newfid;
+}
+
+/*
+ * Open & Opendir.
+ * Argument is a struct fuse_open_in.
+ * The mode field is ignored (presumably permission bits)
+ * and flags is the open mode.
+ * Replies with a struct fuse_open_out.
+ */
+void
+_fuseopen(FuseMsg *m, int isdir)
+{
+ struct fuse_open_in *in;
+ struct fuse_open_out out;
+ CFid *fid;
+ int openmode, flags, err;
+
+ /* TODO: better job translating openmode - see lib9 open */
+ in = m->tx;
+ flags = in->flags;
+ openmode = flags&3;
+ flags &= ~3;
+ flags &= ~(O_DIRECTORY|O_NONBLOCK|O_LARGEFILE);
+ if(flags){
+ fprint(2, "unexpected open flags %#uo", (uint)in->flags);
+ replyfuseerrno(m, EACCES);
+ return;
+ }
+ if((fid = _fuseopenfid(m->hdr->nodeid, isdir, openmode, &err)) == nil){
+ replyfuseerrno(m, err);
+ return;
+ }
+ out.fh = allocfh(fid);
+ out.open_flags = FOPEN_DIRECT_IO; /* no page cache */
+ replyfuse(m, &out, sizeof out);
+}
+
+void
+fuseopen(FuseMsg *m)
+{
+ _fuseopen(m, 0);
+}
+
+void
+fuseopendir(FuseMsg *m)
+{
+ _fuseopen(m, 1);
+}
+
+/*
+ * Create & Mkdir.
+ */
+CFid*
+_fusecreate(uvlong nodeid, char *name, int perm, int ismkdir, int omode, struct fuse_entry_out *out, int *err)
+{
+ CFid *fid, *newfid, *newfid2;
+ Dir *d;
+ Fusefid *ff;
+
+ if((fid = nodeid2fid(nodeid)) == nil){
+ *err = ESTALE;
+ return nil;
+ }
+ perm &= 0777;
+ if(ismkdir)
+ perm |= DMDIR;
+ if(ismkdir && omode != OREAD){
+ *err = EPERM;
+ return nil;
+ }
+ if((newfid = fswalk(fid, nil)) == nil){
+ *err = errstr2errno();
+ return nil;
+ }
+ if(fsfcreate(newfid, name, perm, omode) < 0){
+ *err = errstr2errno();
+ fsclose(newfid);
+ return nil;
+ }
+ if((d = fsdirfstat(newfid)) == nil){
+ *err = errstr2errno();
+ fsfremove(newfid);
+ return nil;
+ }
+ /*
+ * This fid is no good, because it's open.
+ * We need an unopened fid. Sigh.
+ */
+ if((newfid2 = fswalk(fid, name)) == nil){
+ *err = errstr2errno();
+ free(d);
+ fsfremove(newfid);
+ return nil;
+ }
+ out->nodeid = allocnodeid(newfid2);
+ ff = lookupfusefid(out->nodeid, 1);
+ out->generation = ff->gen;
+ f2timeout(attrtimeout, &out->attr_valid, &out->attr_valid_nsec);
+ f2timeout(entrytimeout, &out->entry_valid, &out->entry_valid_nsec);
+ dir2attr(d, &out->attr);
+ free(d);
+ return newfid;
+}
+
+void
+fusemkdir(FuseMsg *m)
+{
+ struct fuse_mkdir_in *in;
+ struct fuse_entry_out out;
+ CFid *fid;
+ int err;
+ char *name;
+
+ in = m->tx;
+ name = (char*)(in+1);
+ if((fid = _fusecreate(m->hdr->nodeid, name, in->mode, 1, OREAD, &out, &err)) == nil){
+ replyfuseerrno(m, err);
+ return;
+ }
+ /* Toss the open fid. */
+ fsclose(fid);
+ replyfuse(m, &out, sizeof out);
+}
+
+void
+fusecreate(FuseMsg *m)
+{
+ struct fuse_open_in *in;
+ struct fuse_create_out out;
+ CFid *fid;
+ int err, openmode, flags;
+ char *name;
+
+ in = m->tx;
+ flags = in->flags;
+ openmode = in->flags&3;
+ flags &= ~(O_DIRECTORY|O_NONBLOCK|O_LARGEFILE);
+ if(flags){
+ fprint(2, "bad mode %#uo\n", in->flags);
+ replyfuseerrno(m, EACCES);
+ return;
+ }
+ name = (char*)(in+1);
+ if((fid = _fusecreate(m->hdr->nodeid, name, in->mode, 0, openmode, &out.e, &err)) == nil){
+ replyfuseerrno(m, err);
+ return;
+ }
+ out.o.fh = allocfh(fid);
+ out.o.open_flags = FOPEN_DIRECT_IO; /* no page cache */
+ replyfuse(m, &out, sizeof out);
+}
+
+/*
+ * Access.
+ * Lib9pclient implements this just as Plan 9 does,
+ * by opening the file (or not) and then closing it.
+ */
+void
+fuseaccess(FuseMsg *m)
+{
+ struct fuse_access_in *in;
+ CFid *fid;
+ int err;
+ static int a2o[] = {
+ 0,
+ OEXEC,
+ OWRITE,
+ ORDWR,
+ OREAD,
+ OEXEC,
+ ORDWR,
+ ORDWR
+ };
+
+ in = m->tx;
+ if(in->mask >= nelem(a2o)){
+ replyfuseerrno(m, EINVAL);
+ return;
+ }
+ if((fid = _fuseopenfid(m->hdr->nodeid, 0, a2o[in->mask], &err)) == nil){
+ replyfuseerrno(m, err);
+ return;
+ }
+ fsclose(fid);
+ replyfuse(m, nil, 0);
+}
+
+/*
+ * Release.
+ * Equivalent of clunk for file handles.
+ * in->flags is the open mode used in Open or Opendir.
+ */
+void
+fuserelease(FuseMsg *m)
+{
+ struct fuse_release_in *in;
+ Fusefid *ff;
+
+ in = m->tx;
+ if((ff = lookupfusefid(in->fh, 0)) != nil)
+ freefusefid(ff);
+ else
+ fprint(2, "fuserelease: fh not found\n");
+ replyfuse(m, nil, 0);
+}
+
+void
+fusereleasedir(FuseMsg *m)
+{
+ fuserelease(m);
+}
+
+/*
+ * Read.
+ * Read from file handle in->fh at offset in->offset for size in->size.
+ * We truncate size to maxwrite just to keep the buffer reasonable.
+ */
+void
+fuseread(FuseMsg *m)
+{
+ int n;
+ uchar *buf;
+ CFid *fid;
+ struct fuse_read_in *in;
+
+ in = m->tx;
+ if((fid = fh2fid(in->fh)) == nil){
+ replyfuseerrno(m, ESTALE);
+ return;
+ }
+ n = in->size;
+ if(n > fusemaxwrite)
+ n = fusemaxwrite;
+ buf = emalloc(n);
+ n = fsread(fid, buf, n);
+ if(n < 0){
+ free(buf);
+ replyfuseerrstr(m);
+ }
+ replyfuse(m, buf, n);
+ free(buf);
+}
+
+/*
+ * Readdir.
+ * Read from file handle in->fh at offset in->offset for size in->size.
+ * We truncate size to maxwrite just to keep the buffer reasonable.
+ * We assume 9P directory read semantics: a read at offset 0 rewinds
+ * and a read at any other offset starts where we left off.
+ * If it became necessary, we could implement a crude seek
+ * or cache the entire list of directory entries.
+ * Directory entries read from 9P but not yet handed to FUSE
+ * are stored in m->d,nd,d0.
+ */
+int canpack(Dir*, uvlong, uchar**, uchar*);
+void
+fusereaddir(FuseMsg *m)
+{
+ struct fuse_read_in *in;
+ uchar *buf, *p, *ep;
+ int n;
+ Fusefid *ff;
+
+ in = m->tx;
+ if((ff = lookupfusefid(in->fh, 0)) == nil){
+ replyfuseerrno(m, ESTALE);
+ return;
+ }
+ if(in->offset == 0){
+ fsseek(ff->fid, 0, 0);
+ free(ff->d0);
+ ff->d0 = nil;
+ ff->d = nil;
+ ff->nd = 0;
+ }
+ n = in->size;
+ if(n > fusemaxwrite)
+ n = fusemaxwrite;
+ buf = emalloc(n);
+ p = buf;
+ ep = buf + n;
+ for(;;){
+ if(ff->nd == 0){
+ free(ff->d0);
+ ff->d0 = nil;
+ ff->d = nil;
+ if((ff->nd = fsdirread(ff->fid, &ff->d0)) < 0){
+ replyfuseerrstr(m);
+ return;
+ }
+ if(ff->nd == 0)
+ break;
+ ff->d = ff->d0;
+ }
+ while(ff->nd > 0 && canpack(ff->d, ff->off, &p, ep)){
+ ff->off++;
+ ff->d++;
+ ff->nd--;
+ }
+ }
+ replyfuse(m, buf, p - buf);
+ free(buf);
+}
+
+int
+canpack(Dir *d, uvlong off, uchar **pp, uchar *ep)
+{
+ uchar *p;
+ struct fuse_dirent *de;
+ int pad, size;
+
+ p = *pp;
+ size = FUSE_NAME_OFFSET + strlen(d->name);
+ pad = 0;
+ if(size%8)
+ pad = 8 - size%8;
+ if(size+pad > ep - p)
+ return 0;
+ de = (struct fuse_dirent*)p;
+ de->ino = qid2inode(d->qid);
+ de->off = off;
+ de->namelen = strlen(d->name);
+ memmove(de->name, d->name, de->namelen);
+ if(pad > 0)
+ memset(de->name+de->namelen, 0, pad);
+ *pp = p+size+pad;
+ return 1;
+}
+
+/*
+ * Write.
+ * Write from file handle in->fh at offset in->offset for size in->size.
+ * Don't know what in->write_flags means.
+ *
+ * Apparently implementations are allowed to buffer these writes
+ * and wait until Flush is sent, but FUSE docs say flush may be
+ * called zero, one, or even more times per close. So better do the
+ * actual writing here. Also, errors that happen during Flush just
+ * show up in the close() return status, which no one checks anyway.
+ */
+void
+fusewrite(FuseMsg *m)
+{
+ struct fuse_write_in *in;
+ struct fuse_write_out out;
+ void *a;
+ CFid *fid;
+ int n;
+
+ in = m->tx;
+ a = in+1;
+ if((fid = fh2fid(in->fh)) == nil){
+ replyfuseerrno(m, ESTALE);
+ return;
+ }
+ if(in->size > fusemaxwrite){
+ replyfuseerrno(m, EINVAL);
+ return;
+ }
+ n = fswrite(fid, a, in->size);
+ if(n < 0){
+ replyfuseerrstr(m);
+ return;
+ }
+ out.size = n;
+ replyfuse(m, &out, sizeof out);
+}
+
+/*
+ * Flush. Supposed to flush any buffered writes. Don't use this.
+ *
+ * Flush is a total crock. It gets called on close() of a file descriptor
+ * associated with this open file. Some open files have multiple file
+ * descriptors and thus multiple closes of those file descriptors.
+ * In those cases, Flush is called multiple times. Some open files
+ * have file descriptors that are closed on process exit instead of
+ * closed explicitly. For those files, Flush is never called.
+ * Even more amusing, Flush gets called before close() of read-only
+ * file descriptors too!
+ *
+ * This is just a bad idea.
+ */
+void
+fuseflush(FuseMsg *m)
+{
+ replyfuse(m, nil, 0);
+}
+
+/*
+ * Unlink & Rmdir.
+ */
+void
+_fuseremove(FuseMsg *m, int isdir)
+{
+ char *name;
+ CFid *fid, *newfid;
+
+ name = m->tx;
+ if((fid = nodeid2fid(m->hdr->nodeid)) == nil){
+ replyfuseerrno(m, ESTALE);
+ return;
+ }
+ if(strchr(name, '/')){
+ replyfuseerrno(m, ENOENT);
+ return;
+ }
+ if((newfid = fswalk(fid, name)) == nil){
+ replyfuseerrstr(m);
+ return;
+ }
+ if(isdir && !(fsqid(newfid).type&QTDIR)){
+ replyfuseerrno(m, ENOTDIR);
+ fsclose(newfid);
+ return;
+ }
+ if(!isdir && (fsqid(newfid).type&QTDIR)){
+ replyfuseerrno(m, EISDIR);
+ fsclose(newfid);
+ return;
+ }
+ if(fsfremove(newfid) < 0){
+ replyfuseerrstr(m);
+ return;
+ }
+ replyfuse(m, nil, 0);
+}
+
+void
+fuseunlink(FuseMsg *m)
+{
+ _fuseremove(m, 0);
+}
+
+void
+fusermdir(FuseMsg *m)
+{
+ _fuseremove(m, 1);
+}
+
+/*
+ * Rename.
+ *
+ * FUSE sends the nodeid for the source and destination
+ * directory and then the before and after names as strings.
+ * 9P can only do the rename if the source and destination
+ * are the same. If the same nodeid is used for source and
+ * destination, we're fine, but if FUSE gives us different nodeids
+ * that happen to correspond to the same directory, we have
+ * no way of figuring that out. Let's hope it doesn't happen too often.
+ */
+void
+fuserename(FuseMsg *m)
+{
+ struct fuse_rename_in *in;
+ char *before, *after;
+ CFid *fid, *newfid;
+ Dir d;
+
+ in = m->tx;
+ if(in->newdir != m->hdr->nodeid){
+ replyfuseerrno(m, EXDEV);
+ return;
+ }
+ before = (char*)(in+1);
+ after = before + strlen(before) + 1;
+ if((fid = nodeid2fid(m->hdr->nodeid)) == nil){
+ replyfuseerrno(m, ESTALE);
+ return;
+ }
+ if(strchr(before, '/') || strchr(after, '/')){
+ replyfuseerrno(m, ENOENT);
+ return;
+ }
+ if((newfid = fswalk(fid, before)) == nil){
+ replyfuseerrstr(m);
+ return;
+ }
+ nulldir(&d);
+ d.name = after;
+ if(fsdirfwstat(newfid, &d) < 0){
+ replyfuseerrstr(m);
+ fsclose(newfid);
+ return;
+ }
+ fsclose(newfid);
+ replyfuse(m, nil, 0);
+}
+
+/*
+ * Fsync. Commit file info to stable storage.
+ * Not sure what in->fsync_flags are.
+ */
+void
+fusefsync(FuseMsg *m)
+{
+ struct fuse_fsync_in *in;
+ CFid *fid;
+ Dir d;
+
+ in = m->tx;
+ if((fid = fh2fid(in->fh)) == nil){
+ replyfuseerrno(m, ESTALE);
+ return;
+ }
+ nulldir(&d);
+ if(fsdirfwstat(fid, &d) < 0){
+ replyfuseerrstr(m);
+ return;
+ }
+ replyfuse(m, nil, 0);
+}
+
+/*
+ * Fsyncdir. Commit dir info to stable storage?
+ */
+void
+fusefsyncdir(FuseMsg *m)
+{
+ fusefsync(m);
+}
+
+/*
+ * Statfs. Send back information about file system.
+ * Not really worth implementing, except that if we
+ * reply with ENOSYS, programs like df print messages like
+ * df: `/tmp/z': Function not implemented
+ * and that gets annoying. Returning all zeros excludes
+ * us from df without appearing to cause any problems.
+ */
+void
+fusestatfs(FuseMsg *m)
+{
+ struct fuse_statfs_out out;
+
+ memset(&out, 0, sizeof out);
+ replyfuse(m, &out, sizeof out);
+}
+
+void (*fusehandlers[100])(FuseMsg*);
+
+struct {
+ int op;
+ void (*fn)(FuseMsg*);
+} fuselist[] = {
+ { FUSE_LOOKUP, fuselookup },
+ { FUSE_FORGET, fuseforget },
+ { FUSE_GETATTR, fusegetattr },
+ { FUSE_SETATTR, fusesetattr },
+ /*
+ * FUSE_READLINK, FUSE_SYMLINK, FUSE_MKNOD are unimplemented.
+ */
+ { FUSE_MKDIR, fusemkdir },
+ { FUSE_UNLINK, fuseunlink },
+ { FUSE_RMDIR, fusermdir },
+ { FUSE_RENAME, fuserename },
+ /*
+ * FUSE_LINK is unimplemented.
+ */
+ { FUSE_OPEN, fuseopen },
+ { FUSE_READ, fuseread },
+ { FUSE_WRITE, fusewrite },
+ { FUSE_STATFS, fusestatfs },
+ { FUSE_RELEASE, fuserelease },
+ { FUSE_FSYNC, fusefsync },
+ /*
+ * FUSE_SETXATTR, FUSE_GETXATTR, FUSE_LISTXATTR, and
+ * FUSE_REMOVEXATTR are unimplemented.
+ * FUSE will stop sending these requests after getting
+ * an -ENOSYS reply (see dispatch below).
+ */
+ { FUSE_FLUSH, fuseflush },
+ /*
+ * FUSE_INIT is handled in initfuse and should not be seen again.
+ */
+ { FUSE_OPENDIR, fuseopendir },
+ { FUSE_READDIR, fusereaddir },
+ { FUSE_RELEASEDIR, fusereleasedir },
+ { FUSE_FSYNCDIR, fusefsyncdir },
+ { FUSE_ACCESS, fuseaccess },
+ { FUSE_CREATE, fusecreate },
+};
+
+void
+fusethread(void *v)
+{
+ FuseMsg *m;
+
+ m = v;
+ if((uint)m->hdr->opcode >= nelem(fusehandlers)
+ || !fusehandlers[m->hdr->opcode]){
+ replyfuseerrno(m, ENOSYS);
+ return;
+ }
+ fusehandlers[m->hdr->opcode](m);
+}
+
+void
+fusedispatch(void *v)
+{
+ int i;
+ FuseMsg *m;
+
+ eofkill9pclient = 1; /* threadexitsall on 9P eof */
+ atexit(unmountatexit);
+
+ recvp(fusechan); /* sync */
+
+ for(i=0; i<nelem(fuselist); i++){
+ if(fuselist[i].op >= nelem(fusehandlers))
+ sysfatal("make fusehandlers bigger op=%d", fuselist[i].op);
+ fusehandlers[fuselist[i].op] = fuselist[i].fn;
+ }
+
+ while((m = recvp(fusechan)) != nil)
+ threadcreate(fusethread, m, STACK);
+}
+
+void*
+emalloc(uint n)
+{
+ void *p;
+
+ p = malloc(n);
+ if(p == nil)
+ sysfatal("malloc(%d): %r", n);
+ memset(p, 0, n);
+ return p;
+}
+
+void*
+erealloc(void *p, uint n)
+{
+ p = realloc(p, n);
+ if(p == nil)
+ sysfatal("realloc(..., %d): %r", n);
+ return p;
+}
+
+char*
+estrdup(char *p)
+{
+ char *pp;
+ pp = strdup(p);
+ if(pp == nil)
+ sysfatal("strdup(%.20s): %r", p);
+ return pp;
+}
+
+
diff --git a/src/cmd/9pfuse/mkfile b/src/cmd/9pfuse/mkfile
new file mode 100644
index 00000000..041a7834
--- /dev/null
+++ b/src/cmd/9pfuse/mkfile
@@ -0,0 +1,12 @@
+<$PLAN9/src/mkhdr
+
+TARG=9pfuse
+
+OFILES=\
+ fuse.$O\
+ main.$O\
+
+HFILES=a.h
+
+<$PLAN9/src/mkone
+