#include #include #include #include #include #include #include enum { Maxfdata = 8192, Namelen = 28, }; typedef struct Fid Fid; struct Fid { short busy; int fid; Fid *next; char *user; String *path; /* complete path */ int fd; /* set on open or create */ Qid qid; /* set on open or create */ int attach; /* this is an attach fd */ ulong diroff; /* directory offset */ Dir *dir; /* directory entries */ int ndir; /* number of directory entries */ }; Fid *fids; int mfd[2]; char *user; uchar mdata[IOHDRSZ+Maxfdata]; uchar rdata[Maxfdata]; /* buffer for data in reply */ uchar statbuf[STATMAX]; Fcall thdr; Fcall rhdr; int messagesize = sizeof mdata; int readonly; char *srvname; int debug; int namelen = Namelen; int frogs; Fid * newfid(int); void io(void); void *erealloc(void*, ulong); void *emalloc(usize); char *estrdup(char*); void usage(void); void fidqid(Fid*, Qid*); char* short2long(char*); char* long2short(char*, int); void readnames(void); void post(char*, int); char *rflush(Fid*), *rversion(Fid*), *rauth(Fid*), *rattach(Fid*), *rwalk(Fid*), *ropen(Fid*), *rcreate(Fid*), *rread(Fid*), *rwrite(Fid*), *rclunk(Fid*), *rremove(Fid*), *rstat(Fid*), *rwstat(Fid*); char *(*fcalls[])(Fid*) = { [Tversion] rversion, [Tflush] rflush, [Tauth] rauth, [Tattach] rattach, [Twalk] rwalk, [Topen] ropen, [Tcreate] rcreate, [Tread] rread, [Twrite] rwrite, [Tclunk] rclunk, [Tremove] rremove, [Tstat] rstat, [Twstat] rwstat, }; char Eperm[] = "permission denied"; char Enotdir[] = "not a directory"; char Enoauth[] = "lnfs: authentication not required"; char Enotexist[] = "file does not exist"; char Einuse[] = "file in use"; char Eexist[] = "file exists"; char Eisdir[] = "file is a directory"; char Enotowner[] = "not owner"; char Eisopen[] = "file already open for I/O"; char Excl[] = "exclusive use file already open"; char Ename[] = "illegal name"; char Eversion[] = "unknown 9P version"; void usage(void) { fprint(2, "usage: %s [-l len] [-r] [-s srvname] mountpoint\n", argv0); exits("usage"); } void notifyf(void *a, char *s) { USED(a); if(strncmp(s, "interrupt", 9) == 0) noted(NCONT); noted(NDFLT); } void main(int argc, char *argv[]) { char *defmnt; int p[2]; Dir *d; ARGBEGIN{ case 'f': frogs = 1; break; case 'l': namelen = atoi(EARGF(usage())); break; case 'r': readonly = 1; break; case 'd': debug = 1; break; case 's': srvname = EARGF(usage()); break; default: usage(); }ARGEND if(argc < 1) usage(); defmnt = argv[0]; d = dirstat(defmnt); if(d == nil || !(d->qid.type & QTDIR)) sysfatal("mountpoint must be an accessible directory"); free(d); if(pipe(p) < 0) sysfatal("pipe failed"); mfd[0] = p[0]; mfd[1] = p[0]; user = getuser(); notify(notifyf); if(srvname != nil) post(srvname, p[1]); if(debug) fmtinstall('F', fcallfmt); switch(rfork(RFFDG|RFPROC|RFNAMEG|RFNOTEG)){ case -1: sysfatal("fork: %r"); case 0: close(p[1]); chdir(defmnt); io(); break; default: close(p[0]); /* don't deadlock if child fails */ if(mount(p[1], -1, defmnt, MREPL|MCREATE, "") < 0) sysfatal("mount failed: %r"); } exits(0); } void post(char *srvname, int pfd) { char name[128]; int fd; snprint(name, sizeof name, "#s/%s", srvname); fd = create(name, OWRITE, 0666); if(fd < 0) sysfatal("create of %s failed: %r", srvname); sprint(name, "%d", pfd); if(write(fd, name, strlen(name)) < 0) sysfatal("writing %s: %r", srvname); } char* rversion(Fid*) { Fid *f; for(f = fids; f; f = f->next) if(f->busy) rclunk(f); if(thdr.msize > sizeof mdata) rhdr.msize = sizeof mdata; else rhdr.msize = thdr.msize; messagesize = rhdr.msize; if(strncmp(thdr.version, "9P2000", 6) != 0) return Eversion; rhdr.version = "9P2000"; return nil; } char* rauth(Fid*) { return Enoauth; } char* rflush(Fid *f) { USED(f); return nil; } char* rattach(Fid *f) { /* no authentication! */ f->busy = 1; if(thdr.uname[0]) f->user = estrdup(thdr.uname); else f->user = "none"; if(strcmp(user, "none") == 0) user = f->user; if(f->path) s_free(f->path); f->path = s_copy("."); fidqid(f, &rhdr.qid); f->attach = 1; return nil; } char* clone(Fid *f, Fid **nf) { if(f->fd >= 0) return Eisopen; *nf = newfid(thdr.newfid); (*nf)->busy = 1; if((*nf)->path) s_free((*nf)->path); (*nf)->path = s_clone(f->path); (*nf)->fd = -1; (*nf)->user = f->user; (*nf)->attach = 0; return nil; } char* rwalk(Fid *f) { char *name; Fid *nf; char *err; int i; String *npath; Dir *d; char *cp; Qid qid; err = nil; nf = nil; rhdr.nwqid = 0; if(rhdr.newfid != rhdr.fid){ err = clone(f, &nf); if(err) return err; f = nf; /* walk the new fid */ } readnames(); npath = s_clone(f->path); if(thdr.nwname > 0){ for(i=0; iptr = cp; } } else { s_append(npath, "/"); s_append(npath, name); } d = dirstat(s_to_c(npath)); if(d == nil) break; rhdr.nwqid++; qid = d->qid; rhdr.wqid[i] = qid; free(d); } if(i==0 && err == nil) err = Enotexist; } /* if there was an error and we cloned, get rid of the new fid */ if(nf != nil && (err!=nil || rhdr.nwqidbusy = 0; s_free(npath); } /* update the fid after a successful walk */ if(rhdr.nwqid == thdr.nwname){ s_free(f->path); f->path = npath; } return err; } static char* passerror(void) { static char err[256]; rerrstr(err, sizeof err); return err; } char* ropen(Fid *f) { if(readonly && (thdr.mode & 3)) return Eperm; if(f->fd >= 0) return Eisopen; f->fd = open(s_to_c(f->path), thdr.mode); if(f->fd < 0) return passerror(); fidqid(f, &rhdr.qid); f->qid = rhdr.qid; rhdr.iounit = messagesize-IOHDRSZ; return nil; } char* rcreate(Fid *f) { char *name; if(readonly) return Eperm; readnames(); name = long2short(thdr.name, 1); if(f->fd >= 0) return Eisopen; s_append(f->path, "/"); s_append(f->path, name); f->fd = create(s_to_c(f->path), thdr.mode, thdr.perm); if(f->fd < 0) return passerror(); fidqid(f, &rhdr.qid); f->qid = rhdr.qid; rhdr.iounit = messagesize-IOHDRSZ; return nil; } char* rreaddir(Fid *f) { int i; int n; /* reread the directory */ if(thdr.offset == 0){ if(f->dir) free(f->dir); f->dir = nil; if(f->diroff != 0) seek(f->fd, 0, 0); f->ndir = dirreadall(f->fd, &f->dir); f->diroff = 0; if(f->ndir < 0) return passerror(); readnames(); for(i = 0; i < f->ndir; i++) f->dir[i].name = short2long(f->dir[i].name); } /* copy in as many directory entries as possible */ for(n = 0; f->diroff < f->ndir; n += i){ i = convD2M(&f->dir[f->diroff], rdata+n, thdr.count - n); if(i <= BIT16SZ) break; f->diroff++; } rhdr.data = (char*)rdata; rhdr.count = n; return nil; } char* rread(Fid *f) { long n; if(f->fd < 0) return Enotexist; if(thdr.count > messagesize-IOHDRSZ) thdr.count = messagesize-IOHDRSZ; if(f->qid.type & QTDIR) return rreaddir(f); n = pread(f->fd, rdata, thdr.count, thdr.offset); if(n < 0) return passerror(); rhdr.data = (char*)rdata; rhdr.count = n; return nil; } char* rwrite(Fid *f) { long n; if(readonly || (f->qid.type & QTDIR)) return Eperm; if(f->fd < 0) return Enotexist; if(thdr.count > messagesize-IOHDRSZ) /* shouldn't happen, anyway */ thdr.count = messagesize-IOHDRSZ; n = pwrite(f->fd, thdr.data, thdr.count, thdr.offset); if(n < 0) return passerror(); rhdr.count = n; return nil; } char* rclunk(Fid *f) { f->busy = 0; close(f->fd); f->fd = -1; f->path = s_reset(f->path); if(f->attach){ free(f->user); f->user = nil; } f->attach = 0; if(f->dir != nil){ free(f->dir); f->dir = nil; } f->diroff = f->ndir = 0; return nil; } char* rremove(Fid *f) { if(remove(s_to_c(f->path)) < 0) return passerror(); return nil; } char* rstat(Fid *f) { int n; Dir *d; d = dirstat(s_to_c(f->path)); if(d == nil) return passerror(); readnames(); d->name = short2long(d->name); n = convD2M(d, statbuf, sizeof statbuf); free(d); if(n <= BIT16SZ) return passerror(); rhdr.nstat = n; rhdr.stat = statbuf; return nil; } char* rwstat(Fid *f) { int n; Dir d; if(readonly) return Eperm; convM2D(thdr.stat, thdr.nstat, &d, (char*)rdata); readnames(); d.name = long2short(d.name, 1); n = dirwstat(s_to_c(f->path), &d); if(n < 0) return passerror(); return nil; } Fid * newfid(int fid) { Fid *f, *ff; ff = 0; for(f = fids; f; f = f->next) if(f->fid == fid) return f; else if(!ff && !f->busy) ff = f; if(ff){ ff->fid = fid; return ff; } f = emalloc(sizeof *f); f->path = s_reset(f->path); f->fd = -1; f->fid = fid; f->next = fids; fids = f; return f; } void io(void) { char *err; int n, pid; pid = getpid(); for(;;){ /* * reading from a pipe or a network device * will give an error after a few eof reads. * however, we cannot tell the difference * between a zero-length read and an interrupt * on the processes writing to us, * so we wait for the error. */ n = read9pmsg(mfd[0], mdata, messagesize); if(n < 0) exits(""); if(n == 0) continue; if(convM2S(mdata, n, &thdr) == 0) continue; if(debug) fprint(2, "%s %d:<-%F\n", argv0, pid, &thdr); if(!fcalls[thdr.type]) err = "bad fcall type"; else err = (*fcalls[thdr.type])(newfid(thdr.fid)); if(err){ rhdr.type = Rerror; rhdr.ename = err; }else{ rhdr.type = thdr.type + 1; rhdr.fid = thdr.fid; } rhdr.tag = thdr.tag; if(debug) fprint(2, "%s %d:->%F\n", argv0, pid, &rhdr);/**/ n = convS2M(&rhdr, mdata, messagesize); if(n == 0) sysfatal("convS2M error on write"); if(write(mfd[1], mdata, n) != n) sysfatal("mount write"); } } void * emalloc(usize n) { void *p; p = malloc(n); if(p == nil) sysfatal("out of memory"); memset(p, 0, n); return p; } void * erealloc(void *p, ulong n) { p = realloc(p, n); if(p == nil) sysfatal("out of memory"); return p; } char * estrdup(char *q) { char *p; int n; n = strlen(q)+1; p = malloc(n); if(p == nil) sysfatal("out of memory"); memmove(p, q, n); return p; } void fidqid(Fid *f, Qid *q) { Dir *d; d = dirstat(s_to_c(f->path)); if(d == nil) *q = (Qid){0, 0, QTFILE}; else { *q = d->qid; free(d); } } /* * table of name translations * * the file ./.longnames contains all the known long names. * the short name is the first namelen bytes of the base64 * encoding of the MD5 hash of the longname. */ #include typedef struct Name Name; typedef struct Aname Aname; enum { Short, Long, Nname, }; struct Name{ int ref; char *s[Nname]; }; struct Aname{ Avl; int type; Name *name; }; void anamefree(void *v) { Aname *a; a = v; if(a->name->ref-- == 0) free(a->name); /* assume 1 allocation */ free(a); } int namecmp(Avl *va, Avl *vb) { int r, type; Name *a, *b; type = ((Aname*)va)->type; a = ((Aname*)va)->name; b = ((Aname*)vb)->name; r = strcmp(a->s[type], b->s[type]); if(r > 0) r = 1; if(r < 0) r = -1; return r; } static void insert(Avltree *t, Name *n, int type) { Avl *old; Aname *a; a = emalloc(sizeof *a); n->ref++; a->name = n; a->type = type; insertavl(t, a, &old); assert(old == nil); } static Name* lookup(Avltree *t, char *s, int type) { Name n; Aname a, *p; memset(&n, 0, sizeof n); n.s[type] = s; memset(&a, 0, sizeof a); a.name = &n; a.type = type; if(p = (Aname*)lookupavl(t, &a)) return p->name; return nil; } static void delete(Avltree *t, char *s, int type) { Name n; Aname a, *p; memset(&n, 0, sizeof n); n.s[type] = s; memset(&a, 0, sizeof a); a.name = &n; a.type = type; deleteavl(t, &a, (Avl**)&p); assert(p != nil); anamefree(p); } /* move to libavl */ struct Avltree { Avl *root; int (*cmp)(Avl*, Avl*); Avlwalk *walks; }; static void freeavl(Avl *a, void (freefn)(void*)) { if(a == nil) return; freeavl(a->n[0], freefn); freeavl(a->n[1], freefn); freefn(a); } static void freeavltree(Avltree *t, void (freefn)(void*)) { if(t == nil) return; freeavl(t->root, freefn); free(t); } Dir *dbstat; /* last stat of the name file */ char *namefile = "./.longnames"; static Avltree *shorttolong; static Avltree *longtoshort; Name* newname(char *lname, int writeflag) { int l, fd; Name *n; uchar digest[MD5dlen]; l = strlen(lname); md5((uchar*)lname, l, digest, nil); n = emalloc(sizeof *n + (l + 1) + namelen); n->s[Long] = ((char*)n) + sizeof *n; n->s[Short] = ((char*)n) + sizeof *n + (l + 1); memcpy(n->s[Long], lname, (l + 1)); enc32(n->s[Short], namelen, digest, MD5dlen); insert(shorttolong, n, Short); insert(longtoshort, n, Long); /* don't change namefile if we're read only */ if(!writeflag) return n; /* add to namefile */ fd = open(namefile, OWRITE); if(fd >= 0){ seek(fd, 0, 2); fprint(fd, "%s\n", lname); close(fd); } return n; } void newnames(void) { freeavltree(longtoshort, anamefree); freeavltree(shorttolong, anamefree); shorttolong = mkavltree(namecmp); longtoshort = mkavltree(namecmp); if(shorttolong == nil || longtoshort == nil) sysfatal("lnfs: malloc: %r"); } /* * reread the file if the qid.path has changed. * read any new entries if length has changed. * bug: we force ourselves to reread this. :-( */ void readnames(void) { char *p; int fd; vlong offset; Dir *d; Biobuf *b; static int once; /* not multi-user safe. alternative: lock files, etc. */ if(once != 0) return; newnames(); once = 1; d = dirstat(namefile); if(d == nil){ if(readonly) return; /* create file if it doesn't exist */ fd = create(namefile, OREAD, DMAPPEND|0666); if(fd < 0) return; if(dbstat != nil) free(dbstat); dbstat = nil; close(fd); return; } /* up to date? */ offset = 0; if(dbstat != nil){ if(d->qid.path == dbstat->qid.path){ if(d->length <= dbstat->length){ free(d); return; } offset = dbstat->length; } else { newnames(); } free(dbstat); dbstat = nil; } /* read file */ b = Bopen(namefile, OREAD); if(b == nil){ free(d); return; } Bseek(b, offset, 0); while((p = Brdline(b, '\n')) != nil){ p[Blinelen(b)-1] = 0; if(lookup(longtoshort, p, Long) == nil) newname(p, 0); } Bterm(b); dbstat = d; } /* * look up a long name, if it doesn't exist in the * file, add an entry to the file if the writeflag is * non-zero. Return a pointer to the short name. */ char* long2short(char *lname, int writeflag) { Name *n; if(strlen(lname) < namelen && (frogs || strpbrk(lname, " ")==nil)) return lname; if((n = lookup(longtoshort, lname, Long)) != nil) return n->s[Short]; if(!writeflag) return lname; return newname(lname, !readonly)->s[Short]; } /* * look up a short name, if it doesn't exist, return the * longname. */ char* short2long(char *sname) { Name *n; if((n = lookup(shorttolong, sname, Short)) != nil) return n->s[Long]; return sname; }