#include #include #include #include #include #include #include #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; } }