#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:anoncvs@cvs-graphviz.research.att.com: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;
}
}
|