]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/git/serve.c
16eb341894f49ad8b2edf2146a80c7d0803cada2
[plan9front.git] / sys / src / cmd / git / serve.c
1 #include <u.h>
2 #include <libc.h>
3 #include <ctype.h>
4 #include <auth.h>
5
6 #include "git.h"
7
8 char    *pathpfx = nil;
9 int     allowwrite;
10
11 int
12 fmtpkt(Conn *c, char *fmt, ...)
13 {
14         char pkt[Pktmax];
15         va_list ap;
16         int n;
17
18         va_start(ap, fmt);
19         n = vsnprint(pkt, sizeof(pkt), fmt, ap);
20         n = writepkt(c, pkt, n);
21         va_end(ap);
22         return n;
23 }
24
25 int
26 showrefs(Conn *c)
27 {
28         int i, ret, nrefs;
29         Hash head, *refs;
30         char **names;
31
32         ret = -1;
33         nrefs = 0;
34         refs = nil;
35         names = nil;
36         if(resolveref(&head, "HEAD") != -1)
37                 if(fmtpkt(c, "%H HEAD", head) == -1)
38                         goto error;
39
40         if((nrefs = listrefs(&refs, &names)) == -1)
41                 sysfatal("listrefs: %r");
42         for(i = 0; i < nrefs; i++){
43                 if(strncmp(names[i], "heads/", strlen("heads/")) != 0)
44                         continue;
45                 if(fmtpkt(c, "%H refs/%s\n", refs[i], names[i]) == -1)
46                         goto error;
47         }
48         if(flushpkt(c) == -1)
49                 goto error;
50         ret = 0;
51 error:
52         for(i = 0; i < nrefs; i++)
53                 free(names[i]);
54         free(names);
55         free(refs);
56         return ret;
57 }
58
59 int
60 servnegotiate(Conn *c, Hash **head, int *nhead, Hash **tail, int *ntail)
61 {
62         char pkt[Pktmax];
63         int n, acked;
64         Object *o;
65         Hash h;
66
67         if(showrefs(c) == -1)
68                 return -1;
69
70         *head = nil;
71         *tail = nil;
72         *nhead = 0;
73         *ntail = 0;
74         while(1){
75                 if((n = readpkt(c, pkt, sizeof(pkt))) == -1)
76                         goto error;
77                 if(n == 0)
78                         break;
79                 if(strncmp(pkt, "want ", 5) != 0){
80                         werrstr(" protocol garble %s", pkt);
81                         goto error;
82                 }
83                 if(hparse(&h, &pkt[5]) == -1){
84                         werrstr(" garbled want");
85                         goto error;
86                 }
87                 if((o = readobject(h)) == nil){
88                         werrstr("requested nonexistent object");
89                         goto error;
90                 }
91                 unref(o);
92                 *head = erealloc(*head, (*nhead + 1)*sizeof(Hash));
93                 (*head)[*nhead] = h;    
94                 *nhead += 1;
95         }
96
97         acked = 0;
98         while(1){
99                 if((n = readpkt(c, pkt, sizeof(pkt))) == -1)
100                         goto error;
101                 if(strncmp(pkt, "done", 4) == 0)
102                         break;
103                 if(n == 0){
104                         if(!acked && fmtpkt(c, "NAK") == -1)
105                                         goto error;
106                 }
107                 if(strncmp(pkt, "have ", 5) != 0){
108                         werrstr(" protocol garble %s", pkt);
109                         goto error;
110                 }
111                 if(hparse(&h, &pkt[5]) == -1){
112                         werrstr(" garbled have");
113                         goto error;
114                 }
115                 if((o = readobject(h)) == nil)
116                         continue;
117                 if(!acked){
118                         if(fmtpkt(c, "ACK %H", h) == -1)
119                                 goto error;
120                         acked = 1;
121                 }
122                 unref(o);
123                 *tail = erealloc(*tail, (*ntail + 1)*sizeof(Hash));
124                 (*tail)[*ntail] = h;    
125                 *ntail += 1;
126         }
127         if(!acked && fmtpkt(c, "NAK\n") == -1)
128                 goto error;
129         return 0;
130 error:
131         fmtpkt(c, "ERR %r\n");
132         free(*head);
133         free(*tail);
134         return -1;
135 }
136
137 int
138 servpack(Conn *c)
139 {
140         Hash *head, *tail, h;
141         int nhead, ntail;
142
143         dprint(1, "negotiating pack\n");
144         if(servnegotiate(c, &head, &nhead, &tail, &ntail) == -1)
145                 sysfatal("negotiate: %r");
146         dprint(1, "writing pack\n");
147         if(writepack(c->wfd, head, nhead, tail, ntail, &h) == -1)
148                 sysfatal("send: %r");
149         return 0;
150 }
151
152 int
153 validref(char *s)
154 {
155         if(strncmp(s, "refs/", 5) != 0)
156                 return 0;
157         for(; *s != '\0'; s++)
158                 if(!isalnum(*s) && strchr("/-_.", *s) == nil)
159                         return 0;
160         return 1;
161 }
162
163 int
164 recvnegotiate(Conn *c, Hash **cur, Hash **upd, char ***ref, int *nupd)
165 {
166         char pkt[Pktmax], *sp[4];
167         Hash old, new;
168         int n, i;
169
170         if(showrefs(c) == -1)
171                 return -1;
172         *cur = nil;
173         *upd = nil;
174         *ref = nil;
175         *nupd = 0;
176         while(1){
177                 if((n = readpkt(c, pkt, sizeof(pkt))) == -1)
178                         goto error;
179                 if(n == 0)
180                         break;
181                 if(getfields(pkt, sp, nelem(sp), 1, " \t\n\r") != 3){
182                         fmtpkt(c, "ERR  protocol garble %s\n", pkt);
183                         goto error;
184                 }
185                 if(hparse(&old, sp[0]) == -1){
186                         fmtpkt(c, "ERR bad old hash %s\n", sp[0]);
187                         goto error;
188                 }
189                 if(hparse(&new, sp[1]) == -1){
190                         fmtpkt(c, "ERR bad new hash %s\n", sp[1]);
191                         goto error;
192                 }
193                 if(!validref(sp[2])){
194                         fmtpkt(c, "ERR invalid ref %s\n", sp[2]);
195                         goto error;
196                 }
197                 *cur = erealloc(*cur, (*nupd + 1)*sizeof(Hash));
198                 *upd = erealloc(*upd, (*nupd + 1)*sizeof(Hash));
199                 *ref = erealloc(*ref, (*nupd + 1)*sizeof(Hash));
200                 (*cur)[*nupd] = old;
201                 (*upd)[*nupd] = new;
202                 (*ref)[*nupd] = estrdup(sp[2]);
203                 *nupd += 1;
204         }               
205         return 0;
206 error:
207         free(*cur);
208         free(*upd);
209         for(i = 0; i < *nupd; i++)
210                 free((*ref)[i]);
211         free(*ref);
212         return -1;
213 }
214
215 int
216 rename(char *pack, char *idx, Hash h)
217 {
218         char name[128], path[196];
219         Dir st;
220
221         nulldir(&st);
222         st.name = name;
223         snprint(name, sizeof(name), "%H.pack", h);
224         snprint(path, sizeof(path), ".git/objects/pack/%s", name);
225         if(access(path, AEXIST) == 0)
226                 fprint(2, "warning, pack %s already pushed\n", name);
227         else if(dirwstat(pack, &st) == -1)
228                 return -1;
229         snprint(name, sizeof(name), "%H.idx", h);
230         snprint(path, sizeof(path), ".git/objects/pack/%s", name);
231         if(access(path, AEXIST) == 0)
232                 fprint(2, "warning, pack %s already indexed\n", name);
233         else if(dirwstat(idx, &st) == -1)
234                 return -1;
235         return 0;
236 }
237
238 int
239 checkhash(int fd, vlong sz, Hash *hcomp)
240 {
241         DigestState *st;
242         Hash hexpect;
243         char buf[Pktmax];
244         vlong n, r;
245         int nr;
246         
247         if(sz < 28){
248                 werrstr("undersize packfile");
249                 return -1;
250         }
251
252         st = nil;
253         n = 0;
254         if(seek(fd, 0, 0) == -1)
255                 sysfatal("packfile seek: %r");
256         while(n != sz - 20){
257                 nr = sizeof(buf);
258                 if(sz - n - 20 < sizeof(buf))
259                         nr = sz - n - 20;
260                 r = readn(fd, buf, nr);
261                 if(r != nr){
262                         werrstr("short read");
263                         return -1;
264                 }
265                 st = sha1((uchar*)buf, nr, nil, st);
266                 n += r;
267         }
268         sha1(nil, 0, hcomp->h, st);
269         if(readn(fd, hexpect.h, sizeof(hexpect.h)) != sizeof(hexpect.h))
270                 sysfatal("truncated packfile");
271         if(!hasheq(hcomp, &hexpect)){
272                 werrstr("bad hash: %H != %H", *hcomp, hexpect);
273                 return -1;
274         }
275         return 0;
276 }
277
278 int
279 mkdir(char *dir)
280 {
281         char buf[ERRMAX];
282         int f;
283
284         if(access(dir, AEXIST) == 0)
285                 return 0;
286         if((f = create(dir, OREAD, DMDIR | 0755)) == -1){
287                 rerrstr(buf, sizeof(buf));
288                 if(strstr(buf, "exist") == nil)
289                         return -1;
290         }
291         close(f);
292         return 0;
293 }
294
295 int
296 updatepack(Conn *c)
297 {
298         char buf[Pktmax], packtmp[128], idxtmp[128], ebuf[ERRMAX];
299         int n, pfd, packsz;
300         Hash h;
301
302         /* make sure the needed dirs exist */
303         if(mkdir(".git/objects") == -1)
304                 return -1;
305         if(mkdir(".git/objects/pack") == -1)
306                 return -1;
307         if(mkdir(".git/refs") == -1)
308                 return -1;
309         if(mkdir(".git/refs/heads") == -1)
310                 return -1;
311         snprint(packtmp, sizeof(packtmp), ".git/objects/pack/recv-%d.pack.tmp", getpid());
312         snprint(idxtmp, sizeof(idxtmp), ".git/objects/pack/recv-%d.idx.tmp", getpid());
313         if((pfd = create(packtmp, ORDWR, 0644)) == -1)
314                 return -1;
315         packsz = 0;
316         while(1){
317                 n = read(c->rfd, buf, sizeof(buf));
318                 if(n == 0)
319                         break;
320                 if(n == -1){
321                         rerrstr(ebuf, sizeof(ebuf));
322                         if(strstr(ebuf, "hungup") == nil)
323                                 return -1;
324                         break;
325                 }
326                 if(write(pfd, buf, n) != n)
327                         return -1;
328                 packsz += n;
329         }
330         if(checkhash(pfd, packsz, &h) == -1){
331                 dprint(1, "hash mismatch\n");
332                 goto error1;
333         }
334         if(indexpack(packtmp, idxtmp, h) == -1){
335                 dprint(1, "indexing failed: %r\n");
336                 goto error1;
337         }
338         if(rename(packtmp, idxtmp, h) == -1){
339                 dprint(1, "rename failed: %r\n");
340                 goto error2;
341         }
342         return 0;
343
344 error2: remove(idxtmp);
345 error1: remove(packtmp);
346         return -1;
347 }       
348
349 int
350 lockrepo(void)
351 {
352         int fd, i;
353
354         for(i = 0; i < 10; i++) {
355                 if((fd = create(".git/_lock", ORCLOSE|ORDWR|OTRUNC|OEXCL, 0644))!= -1)
356                         return fd;
357                 sleep(250);
358         }
359         return -1;
360 }
361
362 int
363 updaterefs(Conn *c, Hash *cur, Hash *upd, char **ref, int nupd)
364 {
365         char refpath[512];
366         int i, newidx, hadref, fd, ret, lockfd;
367         vlong newtm;
368         Object *o;
369         Hash h;
370
371         ret = -1;
372         hadref = 0;
373         newidx = -1;
374         /*
375          * Date of Magna Carta.
376          * Wrong because it  was computed using
377          * the proleptic gregorian calendar.
378          */
379         newtm = -23811206400;   
380         if((lockfd = lockrepo()) == -1){
381                 werrstr("repo locked\n");
382                 return -1;
383         }
384         for(i = 0; i < nupd; i++){
385                 if(resolveref(&h, ref[i]) == 0){
386                         hadref = 1;
387                         if(!hasheq(&h, &cur[i])){
388                                 werrstr("old ref changed: %s", ref[i]);
389                                 goto error;
390                         }
391                 }
392                 if(snprint(refpath, sizeof(refpath), ".git/%s", ref[i]) == sizeof(refpath)){
393                         werrstr("ref path too long: %s", ref[i]);
394                         goto error;
395                 }
396                 if(hasheq(&upd[i], &Zhash)){
397                         remove(refpath);
398                         continue;
399                 }
400                 if((o = readobject(upd[i])) == nil){
401                         werrstr("update to nonexistent hash %H", upd[i]);
402                         goto error;
403                 }
404                 if(o->type != GCommit){
405                         werrstr("not commit: %H", upd[i]);
406                         goto error;
407                 }
408                 if(o->commit->mtime > newtm){
409                         newtm = o->commit->mtime;
410                         newidx = i;
411                 }
412                 unref(o);
413                 if((fd = create(refpath, OWRITE|OTRUNC, 0644)) == -1){
414                         werrstr("open ref: %r");
415                         goto error;
416                 }
417                 if(fprint(fd, "%H", upd[i]) == -1){
418                         werrstr("upate ref: %r");
419                         close(fd);
420                         goto error;
421                 }
422                 close(fd);
423         }
424         /*
425          * Heuristic:
426          * If there are no valid refs, and HEAD is invalid, then
427          * pick the ref with the newest commits as the default
428          * branch.
429          *
430          * Several people have been caught out by pushing to
431          * a repo where HEAD named differently from what got
432          * pushed, and this is going to be more of a footgun
433          * when 'master', 'main', and 'front' are all in active
434          * use. This should make us pick a useful default in
435          * those cases, instead of silently failing.
436          */
437         if(resolveref(&h, "HEAD") == -1 && hadref == 0 && newidx != -1){
438                 if((fd = create(".git/HEAD", OWRITE|OTRUNC, 0644)) == -1){
439                         werrstr("open HEAD: %r");
440                         goto error;
441                 }
442                 if(fprint(fd, "ref: %s", ref[0]) == -1){
443                         werrstr("write HEAD ref: %r");
444                         goto error;
445                 }
446                 close(fd);
447         }
448         ret = 0;
449 error:
450         fmtpkt(c, "ERR %r");
451         close(lockfd);
452         return ret;
453 }
454
455 int
456 recvpack(Conn *c)
457 {
458         Hash *cur, *upd;
459         char **ref;
460         int nupd;
461
462         if(recvnegotiate(c, &cur, &upd, &ref, &nupd) == -1)
463                 sysfatal("negotiate refs: %r");
464         if(nupd != 0 && updatepack(c) == -1)
465                 sysfatal("update pack: %r");
466         if(nupd != 0 && updaterefs(c, cur, upd, ref, nupd) == -1)
467                 sysfatal("update refs: %r");
468         return 0;
469 }
470
471 char*
472 parsecmd(char *buf, char *cmd, int ncmd)
473 {
474         int i;
475         char *p;
476
477         for(p = buf, i = 0; *p && i < ncmd - 1; i++, p++){
478                 if(*p == ' ' || *p == '\t'){
479                         cmd[i] = 0;
480                         break;
481                 }
482                 cmd[i] = *p;
483         }
484         while(*p == ' ' || *p == '\t')
485                 p++;
486         return p;
487 }
488
489 void
490 usage(void)
491 {
492         fprint(2, "usage: %s [-dw] [-r rel]\n", argv0);
493         exits("usage");
494 }
495
496 void
497 main(int argc, char **argv)
498 {
499         char *repo, cmd[32], buf[512];
500         Conn c;
501
502         ARGBEGIN{
503         case 'd':
504                 chattygit++;
505                 break;
506         case 'r':
507                 pathpfx = EARGF(usage());
508                 if(*pathpfx != '/')
509                         sysfatal("path prefix must begin with '/'");
510                 break;
511         case 'w':
512                 allowwrite++;
513                 break;
514         default:
515                 usage();
516                 break;
517         }ARGEND;
518
519         gitinit();
520         interactive = 0;
521         if(rfork(RFNAMEG) == -1)
522                 sysfatal("rfork: %r");
523         if(pathpfx != nil){
524                 if(bind(pathpfx, "/", MREPL) == -1)
525                         sysfatal("bind: %r");
526         }
527         if(rfork(RFNOMNT) == -1)
528                 sysfatal("rfork: %r");
529
530         initconn(&c, 0, 1);
531         if(readpkt(&c, buf, sizeof(buf)) == -1)
532                 sysfatal("readpkt: %r");
533         repo = parsecmd(buf, cmd, sizeof(cmd));
534         cleanname(repo);
535         if(strncmp(repo, "../", 3) == 0)
536                 sysfatal("invalid path %s\n", repo);
537         if(bind(repo, "/", MREPL) == -1){
538                 fmtpkt(&c, "ERR no repo %r\n");
539                 sysfatal("enter %s: %r", repo);
540         }
541         if(chdir("/") == -1)
542                 sysfatal("chdir: %r");
543         if(access(".git", AREAD) == -1)
544                 sysfatal("no git repository");
545         if(strcmp(cmd, "git-receive-pack") == 0 && allowwrite)
546                 recvpack(&c);
547         else if(strcmp(cmd, "git-upload-pack") == 0)
548                 servpack(&c);
549         else
550                 sysfatal("unsupported command '%s'", cmd);
551         exits(nil);
552 }