ref: 7d7e8ae31ac01434c7017afce4eb0a033281a757
dir: /sys/src/cmd/hgfs/fs.c/
#include <u.h> #include <libc.h> #include <thread.h> #include "dat.h" #include "fns.h" #include <ctype.h> #include <flate.h> #include <auth.h> #include <fcall.h> #include <9p.h> enum { Qroot, Qrev, Qrev0, Qrev1, Qrev2, Qlog, Qwho, Qwhy, Qfiles, Qchanges, Qtree, Qtreerev, }; static char *nametab[] = { "/", nil, "rev", "rev1", "rev2", "log", "who", "why", "files", "changes", nil, nil, }; static Revlog changelog; static Revlog manifest; static Revlog *revlogs; static int nfreerevlogs = 0; static char workdir[MAXPATH]; static int mangle = 0; static Revlog* getrevlog(Revnode *nd) { char buf[MAXPATH]; Revlog *rl, **link; int mang; mang = mangle; Again: nodepath(seprint(buf, buf+sizeof(buf), "%s/.hg/store/data", workdir), buf+sizeof(buf), nd, mang); link = &revlogs; while(rl = *link){ if(strcmp(buf, rl->path) == 0){ if(rl->ref == 0) nfreerevlogs--; break; } if(nfreerevlogs > 8 && rl->ref == 0){ *link = rl->next; nfreerevlogs--; revlogclose(rl); free(rl); continue; } link = &rl->next; } if(rl == nil){ rl = emalloc9p(sizeof(*rl)); memset(rl, 0, sizeof(*rl)); if(revlogopen(rl, buf, OREAD) < 0){ free(rl); if(mang++ == 0) goto Again; return nil; } rl->next = revlogs; revlogs = rl; if(mang) mangle = 1; } else revlogupdate(rl); incref(rl); return rl; } static void closerevlog(Revlog *rl) { if(rl != nil && decref(rl) == 0) nfreerevlogs++; } static Revinfo* getrevinfo(int rev) { Revinfo *ri; if(rev < 0 || rev >= changelog.nmap) return nil; if(ri = changelog.map[rev].aux) return ri; if(ri = loadrevinfo(&changelog, rev)) changelog.map[rev].aux = ri; return ri; } static Revtree* getrevtree(Revtree *(*fn)(Revlog *, Revlog *, Revinfo *), Revinfo *ri) { static ulong gen; static struct { ulong g; void *f; Revinfo *i; Revtree *t; } cache[4]; Revtree *rt; int i, j; for(i=j=0; i<nelem(cache); i++){ if(cache[i].t == nil){ j = i; continue; } if(cache[i].f == fn && cache[i].i == ri){ cache[i].g = ++gen; rt = cache[i].t; goto found; } if(cache[j].t && cache[i].g < cache[j].g) j = i; } if((rt = (*fn)(&changelog, &manifest, ri)) == nil) return nil; closerevtree(cache[j].t); cache[j].g = ++gen; cache[j].f = fn; cache[j].i = ri; cache[j].t = rt; found: incref(rt); return rt; } static char* fsmkuid(char *s) { if(s){ char *x; while(x = strchr(s, '<')) s = x+1; s = estrdup9p(s); if(x = strchr(s, '>')) *x = 0; if(x = strchr(s, '@')) *x = 0; if(x = strchr(s, '\n')) *x = 0; } if(s == nil || *s == 0){ free(s); s = estrdup9p("hgfs"); } return s; } static void fsmkqid(Qid *q, int level, void *aux) { Revnode *nd; Revinfo *ri; switch(level){ case Qroot: q->type = QTDIR; q->path = Qroot; q->vers = 0; break; case Qrev: case Qfiles: case Qchanges: q->type = QTDIR; if(0){ case Qrev0: case Qrev1: case Qrev2: case Qlog: case Qwho: case Qwhy: q->type = 0; } ri = aux; q->path = hash2qid(ri->chash) + (level - Qrev); q->vers = 0; break; case Qtree: case Qtreerev: nd = aux; if(level == Qtree && nd->down){ q->type = QTDIR; } else { q->type = 0; } q->path = nd->path + (level - Qtree); q->vers = 0; break; } } static char* fsmkrevname(char *buf, int nbuf, int rev) { if(rev < 0 || rev >= changelog.nmap) return nil; snprint(buf, nbuf, "%d.%H", rev, changelog.map[rev].hash); return buf; } static void fsmkdir(Dir *d, int level, void *aux) { char buf[64], *s; Revnode *nd; Revinfo *ri; int rev; memset(d, 0, sizeof(*d)); fsmkqid(&d->qid, level, aux); d->mode = 0444; if(d->qid.type & QTDIR) d->mode |= DMDIR | 0111; ri = nil; switch(level){ case Qroot: goto Namegen; case Qrev: case Qrev0: case Qrev1: case Qrev2: ri = aux; Revgen: rev = hashrev(&changelog, ri->chash); if(level == Qrev1) rev = changelog.map[rev].p1rev; else if(level == Qrev2) rev = changelog.map[rev].p2rev; s = fsmkrevname(buf, sizeof(buf), rev); if(level == Qrev){ d->name = estrdup9p(s); break; } goto Strgen; case Qlog: ri = aux; d->length = ri->loglen; goto Namegen; case Qwho: ri = aux; s = ri->who; goto Strgen; case Qwhy: ri = aux; s = ri->why; Strgen: d->length = s ? strlen(s)+1 : 0; if(level == Qtreerev) break; case Qfiles: case Qchanges: ri = aux; /* no break */ Namegen: d->name = estrdup9p(nametab[level]); break; case Qtree: case Qtreerev: nd = aux; if(level == Qtree && nd->mode == 'x') d->mode |= 0111; d->name = estrdup9p(nd->name); if(nd->hash){ Revlog *rl; if((rl = getrevlog(nd)) == nil) break; if((rev = hashrev(rl, nd->hash)) >= 0){ if(level == Qtree){ /* * BUG: this is not correct. mercurial might * prefix the data log with random \1\n escaped * metadata strings (see fmetaheader()) and the flen * *includes* the metadata part. we try to compensate * for this once the revision got extracted and * subtract the metadata header in fsstat(). */ d->length = rl->map[rev].flen; } ri = getrevinfo(rl->map[rev].linkrev); } closerevlog(rl); if(level == Qtreerev && ri) goto Revgen; } break; } if(ri){ d->atime = d->mtime = ri->when; d->muid = fsmkuid(ri->who); d->uid = fsmkuid(ri->who); } else d->atime = d->mtime = time(0); if(d->uid == nil) d->uid = fsmkuid(nil); if(d->gid == nil) d->gid = fsmkuid(nil); if(d->muid == nil) d->muid = fsmkuid(nil); } static void fsattach(Req *r) { Revfile *rf; if(r->ifcall.aname && r->ifcall.aname[0]){ respond(r, "invalid attach specifier"); return; } r->fid->qid.path = Qroot; r->fid->qid.type = QTDIR; r->fid->qid.vers = 0; r->ofcall.qid = r->fid->qid; rf = emalloc9p(sizeof(*rf)); rf->level = Qroot; rf->info = nil; rf->tree = nil; rf->node = nil; rf->rlog = nil; rf->fd = -1; rf->doff = 0; rf->buf = nil; r->fid->aux = rf; respond(r, nil); } static void fsstat(Req *r) { Revfile *rf; rf = r->fid->aux; if(rf->level < Qtree) fsmkdir(&r->d, rf->level, rf->info); else { fsmkdir(&r->d, rf->level, rf->node); if(rf->level == Qtree) r->d.length -= rf->doff; } respond(r, nil); } static int findrev(Revlog *rl, char *name) { uchar hash[HASHSZ]; int n, i, rev; char *s; if(strcmp(name, "tip") == 0) return rl->nmap-1; rev = strtol(name, &s, 10); if(s > name && (*s == 0 || ispunct(*s))) return rev; rev = -1; if(s = strchr(name, '.')) name = s+1; if((n = hex2hash(name, hash)) > 0){ for(i=0; i<rl->nmap; i++){ if(memcmp(rl->map[i].hash, hash, n) == 0){ if(rev < 0) rev = i; else { rev = -1; break; } } } } return rev; } static char* fswalk1(Fid *fid, char *name, Qid *qid) { Revtree* (*loadfn)(Revlog *, Revlog *, Revinfo *); char buf[MAXPATH], *sname; Revnode *nd; Revfile *rf; int i, level; if((fid->qid.type & QTDIR) == 0) return "walk in non-directory"; rf = fid->aux; if(strcmp(name, "..") == 0){ switch(rf->level){ case Qroot: break; case Qrev: rf->info = nil; rf->level = Qroot; break; case Qfiles: case Qchanges: closerevtree(rf->tree); rf->tree = nil; rf->level = Qrev; break; case Qtree: closerevlog(rf->rlog); rf->rlog = nil; if((rf->node = rf->node->up) == rf->tree->root) rf->level = rf->tree->level; break; } } else { switch(rf->level){ case Qroot: revlogupdate(&changelog); revlogupdate(&manifest); i = findrev(&changelog, name); if(rf->info = getrevinfo(i)){ rf->level = Qrev; break; } Notfound: return "directory entry not found"; break; case Qrev: for(i = Qrev+1; i < Qtree; i++){ if(nametab[i] == nil) continue; if(strcmp(name, nametab[i]) == 0) break; } loadfn = nil; switch(i){ case Qtree: goto Notfound; case Qfiles: loadfn = loadfilestree; break; case Qchanges: loadfn = loadchangestree; break; } if(loadfn){ if((rf->tree = getrevtree(loadfn, rf->info)) == nil) goto Notfound; rf->node = rf->tree->root; rf->tree->level = i; } rf->level = i; break; case Qtree: case Qfiles: case Qchanges: i = 0; level = Qtree; sname = name; Searchtree: for(nd = rf->node->down; nd; nd = nd->next) if(strcmp(nd->name, sname) == 0) break; if(nd == nil){ if(sname == name){ sname = strrchr(name, '.'); if(sname && (i = utfnlen(name, sname - name)) > 0){ snprint(buf, sizeof(buf), "%.*s", i, name); sname++; if(strncmp(sname, "rev", 3) == 0){ level = Qtreerev; sname += 3; } if(*sname == 0) i = 0; else { i = strtol(sname, &sname, 10); if(i < 0 || *sname != '\0') goto Notfound; } sname = buf; goto Searchtree; } } goto Notfound; } if(nd->hash){ Revnode *nb; int j; if((rf->rlog = getrevlog(nd)) == nil) goto Notfound; j = hashrev(rf->rlog, nd->hash) - i; if(i < 0 || j < 0 || j >= rf->rlog->nmap){ closerevlog(rf->rlog); rf->rlog = nil; goto Notfound; } for(nb = nd; nb; nb = nb->before) if(hashrev(rf->rlog, nb->hash) == j) break; if(nb == nil){ nb = mknode(nd->name, revhash(rf->rlog, j), nd->mode); nb->up = nd->up; nb->before = nd->before; nd->before = nb; } nd = nb; } else if(name != sname) goto Notfound; rf->node = nd; rf->level = level; break; } } if(rf->level < Qtree) fsmkqid(qid, rf->level, rf->info); else fsmkqid(qid, rf->level, rf->node); fid->qid = *qid; return nil; } static char* fsclone(Fid *oldfid, Fid *newfid) { Revfile *orf, *rf; rf = nil; if(orf = oldfid->aux){ rf = emalloc9p(sizeof(*rf)); *rf = *orf; if(rf->rlog) incref(rf->rlog); if(rf->tree) incref(rf->tree); if(rf->fd >= 0) rf->fd = dup(rf->fd, -1); if(rf->buf) rf->buf = estrdup9p(rf->buf); } newfid->aux = rf; return nil; } static void fsdestroyfid(Fid *fid) { Revfile *rf; if(rf = fid->aux){ closerevlog(rf->rlog); closerevtree(rf->tree); if(rf->fd >= 0) close(rf->fd); free(rf->buf); free(rf); } } static void fsopen(Req *r) { Revfile *rf; rf = r->fid->aux; switch(r->ifcall.mode & 3){ case OEXEC: if(rf->node == nil || rf->node->mode != 'x') break; case OREAD: if(rf->level == Qlog){ if((rf->fd = revlogopentemp(&changelog, hashrev(&changelog, rf->info->chash))) < 0){ responderror(r); return; } rf->doff = rf->info->logoff; } else if(rf->level == Qtree && rf->node->down == nil){ if((rf->fd = revlogopentemp(rf->rlog, hashrev(rf->rlog, rf->node->hash))) < 0){ responderror(r); return; } rf->doff = fmetaheader(rf->fd); } respond(r, nil); return; } respond(r, "permission denied"); } static int rootgen(int i, Dir *d, void *) { Revinfo *ri; if((ri = getrevinfo(i)) == nil) return -1; fsmkdir(d, Qrev, ri); return 0; } static int revgen(int i, Dir *d, void *aux) { i += Qrev+1; if(i >= Qtree) return -1; fsmkdir(d, i, aux); return 0; } static int treegen(int i, Dir *d, void *aux) { Revnode *nd = aux; while(i > 0 && nd){ nd = nd->next; i--; } if(i || nd == nil) return -1; fsmkdir(d, Qtree, nd); return 0; } static void fsread(Req *r) { char buf[MAXPATH], *s; Revfile *rf; int i, n; vlong off; int len; off = r->ifcall.offset; len = r->ifcall.count; rf = r->fid->aux; switch(rf->level){ case Qroot: if(off == 0){ revlogupdate(&changelog); revlogupdate(&manifest); } dirread9p(r, rootgen, nil); respond(r, nil); return; case Qrev: dirread9p(r, revgen, rf->info); respond(r, nil); return; case Qrev0: case Qrev1: case Qrev2: s = nil; if(rf->buf) goto Strgen; i = hashrev(&changelog, rf->info->chash); if(rf->level == Qrev1) i = changelog.map[i].p1rev; else if(rf->level == Qrev2) i = changelog.map[i].p2rev; Revgen: s = fsmkrevname(buf, sizeof(buf), i); goto Strgen; case Qtreerev: if((i = hashrev(rf->rlog, rf->node->hash)) >= 0) i = rf->rlog->map[i].linkrev; goto Revgen; case Qlog: if(off >= rf->info->loglen) len = 0; else if((off + len) >= rf->info->loglen) len = rf->info->loglen - off; goto Fdgen; case Qwho: s = rf->info->who; goto Strgen; case Qwhy: s = rf->info->why; Strgen: if(rf->buf == nil) rf->buf = s ? smprint("%s\n", s) : estrdup9p(""); readstr(r, rf->buf); respond(r, nil); return; case Qtree: if(rf->node->down){ case Qfiles: case Qchanges: dirread9p(r, treegen, rf->node->down); respond(r, nil); return; } Fdgen: if(rf->fd < 0) break; if((n = pread(rf->fd, r->ofcall.data, len, off + rf->doff)) < 0){ responderror(r); return; } r->ofcall.count = n; respond(r, nil); return; } respond(r, "bug in fsread"); } Srv fs = { .attach=fsattach, .stat=fsstat, .walk1=fswalk1, .clone=fsclone, .destroyfid=fsdestroyfid, .open=fsopen, .read=fsread, }; void usage(void) { fprint(2, "usage: %s [-D] [-m mtpt] [-s srv] [root]\n", argv0); exits("usage"); } void main(int argc, char *argv[]) { char *srv, *mtpt; char buf[MAXPATH]; inflateinit(); fmtinstall('H', Hfmt); srv = nil; mtpt = "/mnt/hg"; ARGBEGIN { case 'D': chatty9p++; break; case 'm': mtpt = EARGF(usage()); break; case 's': srv = EARGF(usage()); break; default: usage(); } ARGEND; if(getworkdir(workdir, *argv) < 0) sysfatal("can't find workdir: %r"); snprint(buf, sizeof(buf), "%s/.hg/store/00changelog", workdir); if(revlogopen(&changelog, buf, OREAD) < 0) sysfatal("can't open changelog: %r\n"); snprint(buf, sizeof(buf), "%s/.hg/store/00manifest", workdir); if(revlogopen(&manifest, buf, OREAD) < 0) sysfatal("can't open menifest: %r\n"); postmountsrv(&fs, srv, mtpt, MREPL); exits(0); }