diff options
Diffstat (limited to 'src/cmd/9pfuse/main.c')
-rw-r--r-- | src/cmd/9pfuse/main.c | 1169 |
1 files changed, 1169 insertions, 0 deletions
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; +} + + |