ref: 99c05d231cb6f47987777d8f6e0b434a0262871d
dir: /serve.c/
#include <u.h>
#include <libc.h>
#include <pool.h>
#include <ctype.h>
#include "git.h"
#define Packtmp ".git/objects/pack/recv.pack.tmp"
#define Idxtmp ".git/objects/pack/recv.idx.tmp"
char *pathpfx = "";
int allowwrite;
int
fmtpkt(Conn *c, char *fmt, ...)
{
char pkt[Pktmax];
va_list ap;
int n;
va_start(ap, fmt);
n = vsnprint(pkt, sizeof(pkt), fmt, ap);
n = writepkt(c, pkt, n);
va_end(ap);
return n;
}
int
showrefs(Conn *c)
{
int i, ret, nrefs;
Hash head, *refs;
char **names;
ret = -1;
nrefs = 0;
if(resolveref(&head, "HEAD") != -1) {
if(fmtpkt(c, "%H HEAD", head) == -1)
goto error;
}
if((nrefs = listrefs(&refs, &names)) == -1)
sysfatal("listrefs: %r");
for(i = 0; i < nrefs; i++)
if(fmtpkt(c, "%H refs/%s\n", refs[i], names[i]) == -1)
goto error;
if(flushpkt(c) == -1)
goto error;
ret = 0;
error:
for(i = 0; i < nrefs; i++)
free(names[i]);
free(names);
free(refs);
return ret;
}
int
servnegotiate(Conn *c, Hash **head, int *nhead, Hash **tail, int *ntail)
{
char pkt[Pktmax];
int n, acked;
Object *o;
Hash h;
if(showrefs(c) == -1)
return -1;
*head = nil;
*tail = nil;
*nhead = 0;
*ntail = 0;
while(1){
if((n = readpkt(c, pkt, sizeof(pkt))) == -1)
goto error;
if(n == 0)
break;
if(strncmp(pkt, "want ", 5) != 0){
fmtpkt(c, "ERR protocol garble %s\n", pkt);
goto error;
}
if(hparse(&h, &pkt[5]) == -1){
fmtpkt(c, "ERR garbled want\n");
goto error;
}
if((o = readobject(h)) == nil){
fmtpkt(c, "ERR requested nonexistent object");
goto error;
}
unref(o);
*head = erealloc(*head, (*nhead + 1)*sizeof(Hash));
(*head)[*nhead] = h;
*nhead += 1;
}
acked = 0;
while(1){
if((n = readpkt(c, pkt, sizeof(pkt))) == -1)
goto error;
if(strcmp(pkt, "done") == 0 || strcmp(pkt, "done\n") == 0)
break;
if(n == 0){
if(!acked && fmtpkt(c, "NAK\n") == -1)
goto error;
}
if(strncmp(pkt, "have ", 5) != 0){
fmtpkt(c, "ERR protocol garble %s\n", pkt);
goto error;
}
if(hparse(&h, &pkt[5]) == -1){
fmtpkt(c, "ERR garbled have\n");
goto error;
}
if((o = readobject(h)) == nil)
continue;
if(!acked){
if(fmtpkt(c, "ACK %H\n", h) == -1)
goto error;
acked = 1;
}
unref(o);
*tail = erealloc(*tail, (*ntail + 1)*sizeof(Hash));
(*tail)[*ntail] = h;
*ntail += 1;
}
if(!acked && fmtpkt(c, "NAK\n") == -1)
goto error;
return 0;
error:
free(*head);
free(*tail);
return -1;
}
int
servpack(Conn *c)
{
Hash *head, *tail, h;
Object **obj;
int nhead, ntail, nobj;
dprint(1, "negotiating pack\n");
if(servnegotiate(c, &head, &nhead, &tail, &ntail) == -1)
sysfatal("negotiate: %r");
dprint(1, "finding twixt\n");
if(findtwixt(head, nhead, tail, ntail, &obj, &nobj) == -1)
sysfatal("twixt: %r");
fprint(2, "DPRINT!\n");
dprint(1, "writing pack\n");
if(nobj > 0 && writepack(c->wfd, obj, nobj, &h) == -1)
sysfatal("send: %r");
return 0;
}
int
validref(char *s)
{
if(strncmp(s, "refs/", 5) != 0)
return 0;
for(; *s != '\0'; s++)
if(!isalnum(*s) && strchr("/-_.", *s) == nil)
return 0;
return 1;
}
int
recvnegotiate(Conn *c, Hash **cur, Hash **upd, char ***ref, int *nupd)
{
char pkt[Pktmax], *sp[4];
Hash old, new;
int n, i;
if(showrefs(c) == -1)
return -1;
*cur = nil;
*upd = nil;
*ref = nil;
*nupd = 0;
while(1){
if((n = readpkt(c, pkt, sizeof(pkt))) == -1)
goto error;
if(n == 0)
break;
if(getfields(pkt, sp, nelem(sp), 1, " \t\n\r") != 3){
fmtpkt(c, "ERR protocol garble %s\n", pkt);
goto error;
}
if(hparse(&old, sp[0]) == -1){
fmtpkt(c, "ERR bad old hash %s\n", sp[0]);
goto error;
}
if(hparse(&new, sp[1]) == -1){
fmtpkt(c, "ERR bad new hash %s\n", sp[1]);
goto error;
}
if(!validref(sp[2])){
fmtpkt(c, "ERR invalid ref %s\n", sp[2]);
goto error;
}
*cur = erealloc(*cur, (*nupd + 1)*sizeof(Hash));
*upd = erealloc(*upd, (*nupd + 1)*sizeof(Hash));
*ref = erealloc(*ref, (*nupd + 1)*sizeof(Hash));
(*cur)[*nupd] = old;
(*upd)[*nupd] = new;
(*ref)[*nupd] = estrdup(sp[2]);
*nupd += 1;
}
return 0;
error:
free(*cur);
free(*upd);
for(i = 0; i < *nupd; i++)
free((*ref)[i]);
free(*ref);
return -1;
}
int
rename(char *pack, char *idx, Hash h)
{
char name[128], path[196];
Dir st;
nulldir(&st);
st.name = name;
snprint(name, sizeof(name), "%H.pack", h);
snprint(path, sizeof(path), ".git/objects/pack/%s", name);
if(access(path, AEXIST) == 0)
fprint(2, "warning, pack %s already pushed\n", name);
else if(dirwstat(pack, &st) == -1)
return -1;
snprint(name, sizeof(name), "%H.idx", h);
snprint(path, sizeof(path), ".git/objects/pack/%s", name);
if(access(path, AEXIST) == 0)
fprint(2, "warning, pack %s already indexed\n", name);
else if(dirwstat(idx, &st) == -1)
return -1;
return 0;
}
int
checkhash(int fd, vlong sz, Hash *hcomp)
{
DigestState *st;
Hash hexpect;
char buf[Pktmax];
vlong n, r;
int nr;
if(sz < 28){
werrstr("undersize packfile");
return -1;
}
st = nil;
n = 0;
if(seek(fd, 0, 0) == -1)
sysfatal("packfile seek: %r");
while(n != sz - 20){
nr = sizeof(buf);
if(sz - n - 20 < sizeof(buf))
nr = sz - n - 20;
r = readn(fd, buf, nr);
if(r != nr){
werrstr("short read");
return -1;
}
st = sha1((uchar*)buf, nr, nil, st);
n += r;
}
sha1(nil, 0, hcomp->h, st);
if(readn(fd, hexpect.h, sizeof(hexpect.h)) != sizeof(hexpect.h))
sysfatal("truncated packfile");
if(!hasheq(hcomp, &hexpect)){
werrstr("bad hash: %H != %H", *hcomp, hexpect);
return -1;
}
return 0;
}
int
updatepack(Conn *c)
{
char buf[Pktmax];
int n, pfd, packsz;
Hash h;
if((pfd = create(Packtmp, ORDWR, 0644)) == -1)
return -1;
packsz = 0;
while(1){
n = read(c->rfd, buf, sizeof(buf));
if(n == 0)
break;
if(n == -1 || write(pfd, buf, n) != n)
return -1;
packsz += n;
}
if(checkhash(pfd, packsz, &h) == -1){
dprint(1, "hash mismatch\n");
goto error1;
}
if(indexpack(Packtmp, Idxtmp, h) == -1){
dprint(1, "indexing failed\n");
goto error1;
}
if(rename(Packtmp, Idxtmp, h) == -1){
dprint(1, "rename failed: %r\n");
goto error2;
}
return 0;
error2: remove(Idxtmp);
error1: remove(Packtmp);
return -1;
}
int
lockrepo(void)
{
int fd, i;
for(i = 0; i < 10; i++) {
if((fd = create(".git/index9/lock", ORDWR|OEXCL, 0644))!= -1)
return fd;
sleep(250);
}
return -1;
}
int
updaterefs(Conn *c, Hash *cur, Hash *upd, char **ref, int nupd)
{
char refpath[512];
int i, fd, ret, lockfd;
Object *o;
Hash h;
ret = -1;
if((lockfd = lockrepo()) == -1){
fmtpkt(c, "ERR repo locked\n");
return -1;
}
for(i = 0; i < nupd; i++){
if(resolveref(&h, ref[i]) == 0 && !hasheq(&h, &cur[i])){
fmtpkt(c, "ERR old ref changed: %s", ref[i]);
goto error;
}
if((o = readobject(upd[i])) == nil){
fmtpkt(c, "ERR update to nonexistent hash %H", upd[i]);
goto error;
}
unref(o);
if(o->type != GCommit){
fmtpkt(c, "ERR not commit: %H", upd[i]);
goto error;
}
if(snprint(refpath, sizeof(refpath), ".git/%s", ref[i]) == sizeof(refpath)){
fmtpkt(c, "ERR ref path too long: %s", ref[i]);
goto error;
}
if((fd = create(refpath, OWRITE, 0644)) == -1){
fmtpkt(c, "ERR open ref: %r");
goto error;
}
if(fprint(fd, "%H", upd[i]) == -1){
close(fd);
fmtpkt(c, "ERR upate ref: %r");
goto error;
}
close(fd);
}
ret = 0;
error:
close(lockfd);
remove(".git/index9/lock");
return ret;
}
int
recvpack(Conn *c)
{
Hash *cur, *upd;
char **ref;
int nupd;
if(recvnegotiate(c, &cur, &upd, &ref, &nupd) == -1)
sysfatal("negotiate refs: %r");
if(nupd != 0 && updatepack(c) == 0)
sysfatal("update pack: %r");
if(nupd != 0 && updaterefs(c, cur, upd, ref, nupd) == -1)
sysfatal("update refs: %r");
return 0;
}
char*
parsecmd(char *buf, char *cmd, int ncmd)
{
int i;
char *p;
for(p = buf, i = 0; *p && i < ncmd - 1; i++, p++){
if(*p == ' ' || *p == '\t'){
cmd[i] = 0;
break;
}
cmd[i] = *p;
}
while(*p == ' ' || *p == '\t')
p++;
return p;
}
void
usage(void)
{
fprint(2, "usage: %s [-dw]\n", argv0);
exits("usage");
}
void
main(int argc, char **argv)
{
char *p, cmd[32], buf[512], path[512];
Conn c;
ARGBEGIN{
case 'd':
debug++;
chattygit++;
break;
case 'r':
pathpfx = EARGF(usage());
if(*pathpfx != '/')
sysfatal("path prefix must begin with '/'");
break;
case 'w':
allowwrite++;
break;
default:
usage();
break;
}ARGEND;
gitinit();
initconn(&c, 0, 1);
if(readpkt(&c, buf, sizeof(buf)) == -1)
sysfatal("readpkt: %r");
p = parsecmd(buf, cmd, sizeof(cmd));
if(snprint(path, sizeof(path), "%s/%s", pathpfx, p) == sizeof(path))
sysfatal("%s: path too long\n", p);
if(chdir(path) == -1)
sysfatal("cd %s: %r", p);
if(access(".git", AREAD) == -1)
sysfatal("no git repository");
if(strcmp(cmd, "git-receive-pack") == 0 && allowwrite)
recvpack(&c);
else if(strcmp(cmd, "git-upload-pack") == 0)
servpack(&c);
else
sysfatal("unsupported command '%s'", cmd);
exits(nil);
}