diff options
Diffstat (limited to 'src/cmd/9pfuse')
-rw-r--r-- | src/cmd/9pfuse/COPYRIGHT | 17 | ||||
-rw-r--r-- | src/cmd/9pfuse/a.h | 51 | ||||
-rw-r--r-- | src/cmd/9pfuse/fuse.c | 772 | ||||
-rw-r--r-- | src/cmd/9pfuse/fuse_kernel.h | 312 | ||||
-rw-r--r-- | src/cmd/9pfuse/main.c | 1169 | ||||
-rw-r--r-- | src/cmd/9pfuse/mkfile | 12 |
6 files changed, 2333 insertions, 0 deletions
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 + |