/* main.c - cvsfs - CVS filesystem - Steve Simon - Feb 2005 */ #include #include #include #include #include #include #include #include #include #include <9p.h> #include "cvsfs.h" int Debug; int Verbose; Sess *Session; typedef struct Node Node; struct Node { char *name; /* node name */ Node *parent; /* parent node */ Node *child; /* dir: lower directory */ Node *next; /* dir: next entry, if a dir */ View *v; /* dir: only in top level dirs */ Ent *e; /* file: cvs file info */ }; typedef struct Aux Aux; struct Aux { Node *n; /* position in heirarchy */ Node *join; /* link from cvs tree to parent view */ Node *dirpos; /* current slot in directory */ View *v; /* view (tag or mtime) */ Rev *r; /* file: this revision (filled in on demand) */ }; static Rev CLrev = { nil, 0, "cvs", "cvs", "", "", 0, 0, 0644 } ; static Ent CLent = { "/ChangeLog", "", "", nil, &CLrev }; static View CLview = { "/ChangeLog" }; static Node CLnode = { "ChangeLog", nil, nil, nil, &CLview, &CLent }; static Node Root = { "root", &Root, &CLnode, nil, nil, nil }; static int Sweeppid = 0; static void dumptree(Node *n, int in) { for (; n; n = n->next){ print("%*s %s\n", in, "", n->name); if (n->child) dumptree(n->child, in+2); } } static void sweep(Sess *s) { Ent *e; Rev *r; long now; close(s->fdin); close(s->fdout); while(1){ now = time(nil); for (e = s->ents; e < s->ents +s->nents; e++) for (r = e->revs; r; r = r->next){ lock(&r->lock); if (r->data && (now - r->atime) > EXPIRY) if (r->ref == 0){ free(r->data); r->data = nil; } unlock(&r->lock); } sleep(60); } } static Node * addnode(Node **n, char *name, Node *parent) { Node *t; t = emalloc9p(sizeof(Node)); memset(t, 0, sizeof(Node)); t->next = *n; *n = t; t->name = name; t->parent = parent; return t; } static Node * addpath(Node **n, char *path, Node *parent) { Node *t; char *p, *name; if (*path == '/') path++; if ((p = strchr(path, '/')) == nil) return addnode(n, path, parent); // leaf node name = emalloc9p((p-path)+1); memset(name, 0, (p-path)+1); strncpy(name, path, p-path); if (! *n){ // first new dir t = addnode(n, name, parent); return addpath(&t->child, p, t); } for(t = *n; t; t = t->next){ if (strcmp(t->name, name) == 0){ // dir exists free(name); return addpath(&t->child, p, t); } } t = addnode(n, name, parent); // n'th new dir return addpath(&t->child, p, t); } static void mknodes(Sess *s) { Ent *e; View *v; Node *n, *r, *t; r = nil; for (e = s->ents; e < s->ents +s->nents; e++){ t = addpath(&r, e->path, nil); t->e = e; } for (v = s->views; v < s->views +s->nviews; v++){ t = addpath(&Root.child, v->path, nil); t->child = r; t->v = v; } for (n = Root.child; n; n = n->next) n->parent = &Root; } static void ding(void *u, char *msg) { USED(u); if(strstr(msg, "alarm")) noted(NCONT); noted(NDFLT); } static void walkup(DigestState *s, Node *n) { if(! n) return; md5((uchar *)&n->name, strlen(n->name), nil, s); if(n->parent != n) walkup(s, n->parent); } static void aux2Q(Qid *q, Aux *a, Node *n) { DigestState *s; uchar d[MD5dlen]; s = md5((uchar *)n->name, strlen(n->name), nil, nil); if(a->v) md5((uchar *)&a->v->path, strlen(a->v->path), nil, s); walkup(s, n->parent); md5(nil, 0, d, s); q->path = *(uvlong *)d; q->type = (n->child)? QTDIR: QTFILE; if (a->v) q->vers = a->v->mtime; else q->vers = time(nil); } static void aux2D(Dir *d, Aux *a, Node *n, Rev *r) { View *v = a->v; memset(d, 0, sizeof(Dir)); d->type = 'C'; d->dev = 0; d->name = estrdup9p(n->name); aux2Q(&(d->qid), a, n); if (n->child){ /* directory */ d->mode = DMDIR|0755; d->uid = estrdup9p("cvs"); d->muid = estrdup9p("cvs"); d->gid = estrdup9p("cvs"); d->atime = time(nil); d->mtime = (v)? v->mtime -(v->mtime %DAY)+DAY+SNAPTIME: time(nil); return; } if (! r){ /* file, never checked out */ d->mode = 0644; d->uid = estrdup9p("?"); d->muid = estrdup9p("?"); d->gid = estrdup9p("?"); d->atime = time(nil); d->mtime = (v)? v->mtime -(v->mtime %DAY)+DAY+SNAPTIME: time(nil); return; } d->mode = r->mode; d->atime = r->atime; d->mtime = (v && v->mtime)? v->mtime: time(nil); d->length = r->length; d->uid = estrdup9p(r->author); d->muid = estrdup9p((r && r->rev)? r->rev: ""); d->gid = estrdup9p((r->locker)? r->locker: "unlocked"); } static Rev * getrev(View *v, Ent *e) /* find the relevant revision */ { Tag *t; Rev *r, *or; if (v->tag){ if (strcmp(v->tag, "HEAD") == 0 && !e->deleted) for (r = e->revs; r; r = r->next) if(r && r->next == nil) return r; for (t = e->tags; t; t = t->next) if (strcmp(t->tag, v->tag) == 0) break; if (t == nil) return nil; for (r = e->revs; r; r = r->next) if (strcmp(r->rev, t->rev) == 0) break; return r; } if (e->deleted && v->mtime > e->deleted) return nil; if (v->mtime < e->revs->mtime) return nil; for (or = r = e->revs; r; or = r, r = r->next){ if (v->mtime < r->mtime) break; } return or; } static void responderrstr(Req *r) { char e[ERRMAX]; *e = 0; rerrstr(e, sizeof e); respond(r, e); } static void fsattach(Req *r) { Aux *a; char *spec = r->ifcall.aname; if(spec && *spec){ respond(r, "invalid attach specifier"); return; } a = emalloc9p(sizeof(Aux)); memset(a, 0, sizeof(Aux)); r->fid->aux = a; a->n = &Root; aux2Q(&r->ofcall.qid, a, a->n); r->fid->qid = r->ofcall.qid; respond(r, nil); } static char* fsclone(Fid *ofid, Fid *fid) { Aux *a; a = emalloc9p(sizeof(*a)); *a = *(Aux*)ofid->aux; fid->aux = a; return nil; } static int dirgen(int slot, Dir *d, void *aux) { Node *n; Aux *a = aux; Rev *rev = nil; if (! a->n->child) return -1; if (slot == 0) n = a->n->child; else if (! a->dirpos) return -1; else n = a->dirpos->next; if (a->v) while (n && n->e && (rev = getrev(a->v, n->e)) == nil) n = n->next; if (! n) return -1; aux2D(d, a, n, rev); a->dirpos = n; return 0; } static char* fswalk1(Fid *fid, char *name, Qid *qid) { Aux *a = fid->aux; Node *n = a->n; Node *m; if (strcmp(name, "..") == 0){ m = (n->parent)? n->parent: a->join; if (m == nil) sysfatal("walk(..) - nil node=%s parent=%p join=%p", n->name, n->parent, a->join); if (m->v) a->v = m->v; aux2Q(qid, a, m); fid->qid = *qid; a->n = m; return nil; } if (! n->child) return "not a directory"; for (n = n->child; n; n = n->next){ if (n && a->v && n->e && (a->r = getrev(a->v, n->e)) == nil) continue; if (strcmp(n->name, name) == 0){ if (! n->parent) a->join = a->n; a->n = n; if (a->v == nil) a->v = n->v; aux2Q(qid, a, n); fid->qid = *qid; return nil; } } return "not found"; } static void fsstat(Req *r) { Rev *rev = nil; Aux *a = r->fid->aux; if (a->v && a->n->e) rev = getrev(a->v, a->n->e); aux2D(&(r->d), a, a->n, rev); respond(r, nil); } static void fsopen(Req *r) { Aux *a = r->fid->aux; Ent *e = a->n->e; if (r->fid->qid.type & QTDIR){ /* open dir is a noop */ respond(r, nil); return; } if (! a->v){ respond(r, "internal error - no view"); return; } if (! e){ respond(r, "internal error - no entry"); return; } a->r = getrev(a->v, e); lock(&a->r->lock); if (a->n == &CLnode && a->r->data == nil){ a->r->data = changelog(Session); a->r->length = strlen(a->r->data); a->r->mode = 644; a->r->atime = time(nil); } else if (a->r->data == nil){ if (a->r->epoch && a->r->epoch == Session->epoch){ cvsclose(Session); if (cvsopen(Session) != nil){ unlock(&a->r->lock); responderrstr(r); return; } } if (cvscheckout(Session, &a->r->data, &a->r->length, &a->r->mode, e->path, a->r->rev) == -1){ unlock(&a->r->lock); responderrstr(r); return; } a->r->epoch = Session->epoch; a->r->atime = time(nil); } a->r->ref++; unlock(&a->r->lock); respond(r, nil); } static void fsread(Req *r) { Aux *a = r->fid->aux; if (r->fid->qid.type & QTDIR){ dirread9p(r, dirgen, a); respond(r, nil); return; } if (a->r->length <= r->ifcall.offset) r->ifcall.count = 0; else if (a->r->length <= r->ifcall.offset +r->ifcall.count) r->ifcall.count = a->r->length - r->ifcall.offset; memcpy(r->ofcall.data, a->r->data +r->ifcall.offset, r->ifcall.count); r->ofcall.count = r->ifcall.count; a->r->atime = time(nil); respond(r, nil); } static void fsdestroyfid(Fid *f) { Aux *a = f->aux; if (a && a->r){ lock(&a->r->lock); a->r->ref--; unlock(&a->r->lock); } free(a); } static void fsend(Srv *srv) { USED(srv); cvsclose(Session); postnote(PNPROC, Sweeppid, "die"); } Srv fs = { .destroyfid = fsdestroyfid, .attach= fsattach, .open= fsopen, .read= fsread, .stat= fsstat, .clone= fsclone, .walk1= fswalk1, .end= fsend, }; static int cvsroot(Sess *s, char *str) { char *p, *q; // [:ext:]anoncvs@fred.cornell.edu:[nnnn]/cvsroot if ((p = strrchr(str, ':')) == nil) return -1; *p++ = 0; if(*p != '/'){ s->port = strheap(p); if((q = strchr(s->port, '/')) == nil) return -1; *q = 0; if((p = strchr(p, '/')) == nil) return -1; } else s->port = strheap("2401"); s->root = p; s->logroot = p; if ((p = strrchr(str, '@')) == nil){ s->user = getuser(); s->method = "pserver"; s->host = str; return 0; } *p++ = 0; s->host = p; if ((p = strrchr(str, ':')) == nil){ s->method = "pserver"; s->user = str; return 0; } *p++ = 0; s->user = p; if ((p = strrchr(str, ':')) == nil){ s->method = "pserver"; return 0; } s->method = p+1; if(Verbose){ print("root: %s\n", s->root); print("user: %s\n", s->user); print("host: %s\n", s->host); print("port: %s\n", s->port); print("method: %s\n", s->method); } return 0; } static void usage(void) { fprint(2, "usage: [-Ddabv] [-s srvname] [-k keyparam] [-m mountpoint] [:method:[user@]]host:/root module\n"); exits("usage"); } static char * trim(char *s) { char *p; while(*s == '/') s++; p = strchr(s, 0) -1; while(*p == '/' && p >= s) *p-- = 0; return s; } void main(int argc, char *argv[]) { Sess *s; int odebug, flag; char *root, *err, *keyp, *module, *mtpt, *svs; flag = 0; svs = nil; keyp = ""; mtpt = "/n/cvs"; ARGBEGIN{ case 'D': chatty9p++; break; case 'v': Verbose++; break; case 'd': Debug++; break; case 'k': keyp = EARGF(usage()); break; case 'm': mtpt = EARGF(usage()); break; case 's': svs = EARGF(usage()); break; case 'a': flag |= MAFTER; break; case 'b': flag |= MBEFORE; break; case '?': case 'h': usage(); break; default: fprint(2, "unrecognized option\n"); usage(); }ARGEND if (argc < 2) usage(); s = emalloc9p(sizeof(Sess)); memset(s, 0, sizeof(Sess)); root = trim(argv[argc -2]); if (cvsroot(s, root) == -1) sysfatal("%s - badly formed cvsroot\n", root); s->keyp = keyp; notify(ding); if ((err = cvsopen(s)) != nil) sysfatal("%s,%s - cannot connect - %s - %r\n", s->host, root, err); odebug = Debug; if (Debug) Debug--; module = trim(argv[argc -1]); if (cvsrlog(s, module) == -1) sysfatal("%s - cannot get rlog - %r\n", module); Debug = odebug; if (Verbose) print("%d views on %d files\n", s->nviews, s->nents); mknodes(s); // dumptree(Root.child, 0); /* dump internal tree reprisentation */ if(!Debug && !chatty9p){ close(0); close(1); close(2); } Session = s; postmountsrv(&fs, svs, mtpt, flag); if ((Sweeppid = rfork(RFPROC|RFMEM|RFNOTEG|RFCFDG|RFCENVG|RFNOWAIT|RFCNAMEG)) == 0){ sweep(Session); exits(nil); } exits(0); }