/* main.c - cvsfs - CVS filesystem - Steve Simon - Feb 2005 */
#include <u.h>
#include <libc.h>
#include <bio.h>
#include <ctype.h>
#include <auth.h>
#include <fcall.h>
#include <mp.h>
#include <libsec.h>
#include <thread.h>
#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);
}
|