Plan 9 from Bell Labs’s /usr/web/sources/contrib/steve/root/sys/src/cmd/cvsfs/main.c

Copyright © 2021 Plan 9 Foundation.
Distributed under the MIT License.
Download the Plan 9 distribution.


/* 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);
}

Bell Labs OSI certified Powered by Plan 9

(Return to Plan 9 Home Page)

Copyright © 2021 Plan 9 Foundation. All Rights Reserved.
Comments to webmaster@9p.io.