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

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


#include <u.h>
#include <libc.h>
#include <bio.h>
#include <auth.h>
#include <libsec.h>
#include <fcall.h>
#include <thread.h>
#include <9p.h>
#include "cvsfs.h"

static char *
Statestr[] = {		/* state names - for debug */
	[Sfile] 	"file",
	[Stags] 	"tags",
	[Sjunk]		"junk",
	[Sdesc]		"desc",
	[Srevision]	"revision",
	[Sbranches]	"branches",
};


static void
addview(Sess *s, long mtime, char *tag, int seq)
{
	Tm *tp;
	View *v;
	static long omtime = 0;
	static int alloc = 0;
	static int ver = 0;

	if (seq == 0){		/* first revision on this file */
		ver = 0;
		omtime = 0;
	}

	if (tag){
		for (v = s->views; v < s->views+s->nviews; v++)
			if (v->tag && strcmp(v->tag, tag) == 0)
				return;
	}
	else{
		if (mtime/DAY != omtime/DAY){
			ver = 0;
			for (v = s->views; v < s->views+s->nviews; v++)
				if (!v->tag && v->mtime/DAY == mtime/DAY)
					return;
		}
		else
			ver++;
	}

	if ((s->nviews+1) >= alloc){
		alloc += CHUNK;
		s->views = erealloc9p(s->views, alloc * sizeof(View));
	}
	v = s->views+s->nviews;
	s->nviews++;
	memset(v, 0, sizeof(View));

	v->tag = tag;
	v->mtime = mtime;

	tp = localtime(mtime);
	if (tag)
		v->path = smprint("tags/%s", tag);
	else
	if (ver)
		v->path = smprint("%d/%02d%02d.%d", 
			tp->year+1900, tp->mon+1,
			tp->mday, ver);
	else
		v->path = smprint("%d/%02d%02d", 
			tp->year+1900, tp->mon+1, tp->mday);
	omtime = mtime;
}

/*
 * FIXME: Not sure these are all relevant or correct
 */
static char *
keysub(char *k)
{
	static char buf[64];

	if (strcmp(k, "v") == 0)
		return "val";
	if (strcmp(k, "k") == 0)
		return "key";
	if (strcmp(k, "kv") == 0)
		return "key-val";
	if (strcmp(k, "kvl") == 0)
		return "key-val-lck";
	if (strcmp(k, "o") == 0)
		return "reuse";		/* reuse old values */
	if (strcmp(k, "b") == 0)
		return "none";		/* binary file, no key substution */

	snprint(buf, sizeof(buf), "unknown-keyword-mode='%s'", k);
	return buf;
}

static int
cvspipe(Sess *s, char *prog, char *uflag)
{
	char *p;
	int ph2t[2], pt2h[2];

	if(pipe(ph2t)==-1 || pipe(pt2h)==-1)
		return -1;

	switch(fork()){
	case 0:
		dup(ph2t[0], 0);
		dup(pt2h[1], 1);
		close(ph2t[0]);
		close(ph2t[1]);
		close(pt2h[0]);
		close(pt2h[1]);
		close(2);
		if (*s->keyp)
			execl(prog, prog, uflag, s->user, "-k", s->keyp, s->host, "cvs server", 0);
		else
			execl(prog, prog, uflag, s->user, s->host, "cvs server", 0);
		exits(0);
	case -1:
		return -1;
	}

	if (s->bin == nil)
		s->bin = emalloc9p(sizeof(Biobuf));
	s->fdin = pt2h[0];
	Binit(s->bin, s->fdin, OREAD);
	close(ph2t[0]);

	if (s->bout == nil)
		s->bout = emalloc9p(sizeof(Biobuf));
	s->fdout = ph2t[1];
	Binit(s->bout, s->fdout, OWRITE);
	close(pt2h[1]);

	s->epoch++;

	if (Verbose > 2){
		Bprnt(s->bout, "ver\n");
		Bflush(s->bout);
		if ((p = Bgetline(s->bin)) == nil)	/* version string */
			return -1;
		if (strlen(p) > 2)
			print("%s\n", p+2);
		if ((p = Bgetline(s->bin)) == nil)	/* ok */
			return -1;
		if (strcmp(p, "ok") != 0)		/* proves the session is good */
			return -1;
	}

	Bprnt(s->bout, "Root %s\n", s->root);
	Bprnt(s->bout, "UseUnchanged\n");
	Bprnt(s->bout, "Global_option -Q\n");
	Bflush(s->bout);

	return 0;
}

static int
cvstcp(Sess *s)
{
	int fd;
	char *p;
	UserPasswd *up;

	if ((up = auth_getuserpasswd(auth_getkey,
	    "server=%s proto=pass user=%s servive=cvs %s", s->host, s->user, s->keyp)) == nil)
		return -1;

	if ((fd = dial(netmkaddr(s->host, "tcp", s->port), 0, 0, 0)) == -1)
		return -1;

	if (s->bin == nil)
		s->bin = emalloc9p(sizeof(Biobuf));
	if (s->bout == nil)
		s->bout = emalloc9p(sizeof(Biobuf));

	s->fdin = fd;
	Binit(s->bin, s->fdin, OREAD);

	s->fdout = dup(fd, -1);
	Binit(s->bout, s->fdout, OWRITE);
	
	s->epoch++;

	Bprnt(s->bout, "BEGIN AUTH REQUEST\n");
	Bprnt(s->bout, "%s\n", s->root);
	Bprnt(s->bout, "%s\n", up->user);
	Bprnt(s->bout, "A%s\n", mangle(up->passwd));
	Bprnt(s->bout, "END AUTH REQUEST\n");
	Bflush(s->bout);

	memset(up->passwd, 0, strlen(up->passwd));

	if ((p = Bgetline(s->bin)) == nil)
		return -1;
	if (strcmp(p, "I LOVE YOU") != 0){
		werrstr("authentication failure");
		return -1;
	}

	if (Verbose){
		Bprnt(s->bout, "ver\n");
		Bflush(s->bout);
		if ((p = Bgetline(s->bin)) == nil)	/* version string */
			return -1;
		if (strlen(p) > 2)
			print("%s\n", p+2);
		if ((p = Bgetline(s->bin)) == nil)	/* ok */
			return -1;
		if (strcmp(p, "ok") != 0)		/* proves the session is good */
			return -1;
	}

	Bprnt(s->bout, "Root %s\n", s->root);
	Bprnt(s->bout, "UseUnchanged\n");
	Bprnt(s->bout, "Global_option -Q\n");
	Bflush(s->bout);

	return 0;
}

char *
cvsopen(Sess *s)
{
	if (strcmp(s->method, "ext") == 0){
		if (cvspipe(s, "/bin/ssh", "-l") == 0)
			return nil;
		if (cvspipe(s, "/bin/rx", "-l") == 0)
			return nil;
		if (cvspipe(s, "/bin/cpu", "-u") == 0)  /* awaiting u9cpu */
			return nil;
		return "failed";
	}

	if (strcmp(s->method, "pserver") == 0){
		if (cvstcp(s) == 0)
			return nil;
		return "failed";
	}

	return "connect method not supported";

}

int
cvscheckout(Sess *s, char **buf, uvlong *len, int *mode, char *path, char *rev)
{
	char *p;
	int try = 0;
again:
	Bprnt(s->bout, "Argument -N\n");
	Bprnt(s->bout, "Argument -P\n");
	Bprnt(s->bout, "Argument -r\n");
	Bprnt(s->bout, "Argument %s\n", rev);
	Bprnt(s->bout, "Argument %s\n", path);
	Bprnt(s->bout, "Directory .\n");
	Bprnt(s->bout, "%s\n", s->root);
	Bprnt(s->bout, "co\n");
	Bflush(s->bout);

	if ((p = Bgetline(s->bin)) == nil){	/* 'Updated' or "error  errmsg" */
		if (try++ == 0){
			cvsclose(s);
			cvsopen(s);
			goto again;
		}
		werrstr("cvs: short read, cannot reconnect to server");
		return -1;
	}
	if (strncmp(p, "error ", 6) == 0){
		werrstr("cvs: %s", p+6);
		return -1;
	}
	if (strncmp(p, "Remove-entry", 12) == 0){
		werrstr("cvs: file removed");
		return -1;
	}
	if (strncmp(p, "Updated", 7) != 0){
		werrstr("cvs: '%s' unknown reply from server", p);
		while ((p = Bgetline(s->bin)) == nil)
			print("ERR '%s'\n", p);
		return -1;
	}

	if ((p = Bgetline(s->bin)) == nil)	/* abs path of file in repository */
		return -1;
	USED(p);
	if ((p = Bgetline(s->bin)) == nil)	/* 'Entries' line */
		return -1;
	USED(p);
	if ((p = Bgetline(s->bin)) == nil)
		return -1;
	*mode = str2mode(p);

	if ((p = Bgetline(s->bin)) == nil)	/* length of file */
		return -1;
	*len = strtoull(p, nil, 10);

	*buf = emalloc9p(*len);
	if (Bread(s->bin, *buf, *len) != *len){
		free(*buf);
		*buf = nil;
		return -1;
	}
	if ((p = Bgetline(s->bin)) == nil){
		free(*buf);
		*buf = nil;
		return -1;
	}
	if (strcmp(p, "ok") != 0){
		free(*buf);
		*buf = nil;
		werrstr("cvs: '%s' not 'ok'", p);
		return -1;
	}
	return 0;
}


/*
 * If we get two checkins on the same day of the same file,
 * then we bump the files sequence number.
 * Files with non zero sequence numbers always generate a new
 * view in addview().
 */
int
cvsrlog(Sess *s, char *module)
{
	Rev *r;
	Tag *t;
	Ent *e;
	int i, n, seq, deleted, atticlen, state, alloc;
	char lasterr[ERRMAX], *a[16], *buf, *p, *q;
	
	char *minus = "----------------------------";
	char *equals =  "======================================"
			"=======================================";

	Bprnt(s->bout, "Argument %s\n", module);
	Bprnt(s->bout, "rlog\n");
	Bflush(s->bout);
	
	e = nil;
	r = nil;
	seq = 0;
	alloc = 0;
	deleted = 0;
	state = Sfile;
	s->nents = 0;
	s->ents = nil;
	atticlen = strlen("/Attic");

	addview(s, time(nil), "HEAD", 0);

	while (1){
		if (Debug)
			fprint(2, "%-10s ", Statestr[state]);
		if ((buf = Bgetline(s->bin)) == nil){
			werrstr("%r (%s)", lasterr);
			return -1;
		}
		if (strcmp(buf, "ok") == 0)			/* finished */
			break;
		if (strncmp(buf, "E ", 2) == 0){
			if(strncmp(buf, "E cvs rlog: ", 12) == 0)
				snprint(lasterr, sizeof(lasterr), buf+12);
			else
				snprint(lasterr, sizeof(lasterr), buf+2);
			continue;
		}
		if (strncmp(buf, "M ", 2) != 0)
			continue;
		buf += 2;

		switch(state){
		case Sfile:
			if ((p = strpfx(buf, "RCS file: ")) != nil){
				if ((s->nents+1) >= alloc){
					alloc += CHUNK;
					s->ents = erealloc9p(s->ents, alloc * sizeof(Ent));
				}
				e = s->ents+s->nents;
				s->nents++;
				memset(e, 0, sizeof(Ent));
				assert((q = strrchr(p, ',')) != nil);
				assert(q[1] == 'v' && q[2] == 0);
				*q = 0;
				/*
				 * some servers reply with paths in their rlog command which differ from
				 * the path requested. This may be because they run chroot'ed or perhaps
				 * they have a non-cvs backend (SQL?),
				 * :pserver:[email protected]:2401/home/cvsroot graphviz2
				 * is an example of this.
				 *
				 * to workaround we try to addapt by guessing what the root might be.
				 * this is probably doomed to failure elsewhere, but works for graphviz.
				 */
				if((q = strpfx(p, s->logroot)) == nil){
					if((q = strrchr(p, '/')) == nil)
						sysfatal("cannot parse log root '%s' giving up\n", p);
					*q = 0;
					s->logroot = strheap(p);
				}
				e->path = strheap(q+1);
				deleted = 0;
				if ((p = strstr(e->path, "/Attic/")) != nil){
					memmove(p, p+atticlen, (strlen(p)-atticlen)+1);
					deleted = 1;
				}
				seq = 0;
			}
			else
			if ((p = strpfx(buf, "head: ")) != nil)
				e->head = strheap(p);
			else
			if (strpfx(buf, "symbolic names:" /* NB. no space after colon */ ) != nil)
				state = Stags;
			break;
		case Stags:
			if ((p = strpfx(buf, "keyword substitution: ")) != nil){
				e->keysub = keysub(p);
				state = Sjunk;
			}
			else
			if (*buf == '\t'){
				t = emalloc9p(sizeof(Tag));		/* new tag */
				t->next = e->tags;
				e->tags = t;
				if (getfields(buf, a, nelem(a), 1, " \t\n\r:") == 2){
					t->tag = strheap(a[0]);
					t->rev = strheap(a[1]);
					addview(s, 0, t->tag, seq++); 	/* NB: pass the heap'ed copy */
				}
			}
			break;
		case Sjunk:
			if (strcmp(buf, minus) == 0)
				state = Srevision;
			break;
		case Srevision:
			if ((p = strpfx(buf, "revision ")) != nil){
				r = emalloc9p(sizeof(Rev));		/* new rev */
				r->next = e->revs;
				e->revs = r;

				if (getfields(p, a, nelem(a), 1, " \t\n\r;") == 4){
					r->locker = strheap(a[3]);
				}
				r->rev = strheap(a[0]);
				r->mode = 0644;			/* guess for now */
			}
			else
			if ((p = strpfx(buf, "date: ")) != nil){
				if ((n = getfields(p, a, nelem(a), 1, "\n\r;")) > 1){

					for (i = 0; i < n; i++)
						while(*a[i] == ' ')
							a[i]++;

					r->mtime = str2date(a[0]);
					if (deleted)
						e->deleted = r->mtime;
					for (i = 1; i < n; i++){
						if ((p = strpfx(a[i], "author: ")) != nil)
							r->author = strheap(p);
						if ((p = strpfx(a[i], "lines: ")) != nil){
							r->added = strtol(p, &p, 0);
							r->removed = -strtol(p, nil, 0);
						}
					}
				}
				addview(s, r->mtime, nil, seq++);
				state = Sbranches;
			}
			break;
		case Sbranches:
			if (strpfx(buf, "branches: ") == nil)		/* optional, ugh! */
				r->desc = strgrow(r->desc, buf);
			state = Sdesc;
			break;
		case Sdesc:
			if (strcmp(buf, minus) == 0)
				state = Srevision;
			else
			if (strcmp(buf, equals) == 0)
				state = Sfile;
			else
				r->desc = strgrow(r->desc, buf);
			break;

		}
	}

	return 0;
}

void
cvsclose(Sess *s)
{
	if (s->fdin){
		close(s->fdin);
		Bterm(s->bin);
		free(s->bin);
		s->bin = nil;
	}
	if (s->fdout){
		close(s->fdout);
		Bterm(s->bout);
		free(s->bout);
		s->bout = nil;
	}
}

Bell Labs OSI certified Powered by Plan 9

(Return to Plan 9 Home Page)

Copyright © 2021 Plan 9 Foundation. All Rights Reserved.
Comments to [email protected].