]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/replica/applylog.c
grep: error if sbrk fails
[plan9front.git] / sys / src / cmd / replica / applylog.c
1 #include "all.h"
2
3 #define Nwork   16
4
5 int localdirstat(char*, Dir*);
6 int ismatch(char*);
7 void conflict(char*, char*, ...);
8 void error(char*, ...);
9 int isdir(char*);
10
11 void worker(int fdf, int fdt, char *from, char *to);
12 vlong   nextoff(void);
13 void    failure(void *, char *note);
14
15 QLock   lk;
16 vlong   off;
17
18 int errors;
19 int nconf;
20 int donothing;
21 int verbose;
22 char **match;
23 int nmatch;
24 int tempspool = 1;
25 int safeinstall = 1;
26 char *lroot;
27 char *rroot;
28 Db *clientdb;
29 int skip;
30 int douid;
31 char *mkname(char*, int, char*, char*);
32 char localbuf[10240];
33 char remotebuf[10240];
34 int copyfile(char*, char*, char*, Dir*, int, int*);
35 ulong maxnow;
36 int maxn;
37 char *timefile;
38 int timefd;
39 int samecontents(char*, char*);
40
41 Db *copyerr;
42
43 typedef struct Res Res;
44 struct Res
45 {
46         char c;
47         char *name;
48 };
49
50 Res *res;
51 int nres;
52
53 void 
54 addresolve(int c, char *name)
55 {
56         if(name[0] == '/')
57                 name++;
58         res = erealloc(res, (nres+1)*sizeof res[0]);
59         res[nres].c = c;
60         res[nres].name = name;
61         nres++;
62 }
63
64 int
65 resolve(char *name)
66 {
67         int i, len;
68
69         for(i=0; i<nres; i++){
70                 len = strlen(res[i].name);
71                 if(len == 0)
72                         return res[i].c;
73                 if(strncmp(name, res[i].name, len) == 0 && (name[len]=='/' || name[len] == 0))
74                         return res[i].c;
75         }
76         return '?';
77 }
78
79 void
80 readtimefile(void)
81 {
82         int n;
83         char buf[24];
84
85         if((timefd = open(timefile, ORDWR)) < 0
86         && (timefd = create(timefile, ORDWR|OEXCL, 0666)) < 0)
87                 return;
88
89         n = readn(timefd, buf, sizeof buf);
90         if(n < sizeof buf)
91                 return;
92
93         maxnow = atoi(buf);
94         maxn = atoi(buf+12);
95 }
96
97 void
98 writetimefile(void)
99 {
100         char buf[24+1];
101
102         snprint(buf, sizeof buf, "%11lud %11d ", maxnow, maxn);
103         pwrite(timefd, buf, 24, 0);
104 }
105
106 static void membogus(char**);
107
108 void
109 addce(char *local)
110 {
111         char e[ERRMAX];
112         Dir d;
113
114         memset(&d, 0, sizeof d);
115         rerrstr(e, sizeof e);
116         d.name = atom(e);
117         d.uid = "";
118         d.gid = "";
119         insertdb(copyerr, atom(local), &d);
120 }
121
122 void
123 delce(char *local)
124 {
125         removedb(copyerr, local);
126 }
127
128 void
129 chat(char *f, ...)
130 {
131         Fmt fmt;
132         char buf[256];
133         va_list arg;
134
135         if(!verbose)
136                 return;
137
138         fmtfdinit(&fmt, 1, buf, sizeof buf);
139         va_start(arg, f);
140         fmtvprint(&fmt, f, arg);
141         va_end(arg);
142         fmtfdflush(&fmt);
143 }
144
145 void
146 usage(void)
147 {
148         fprint(2, "usage: replica/applylog [-cnSstuv] [-T timefile] clientdb clientroot serverroot [path ...]\n");
149         exits("usage");
150 }
151
152 int
153 notexists(char *path)
154 {
155         char buf[ERRMAX];
156
157         if(access(path, AEXIST) >= 0)
158                 return 0;
159         
160         rerrstr(buf, sizeof buf);
161         if(strstr(buf, "entry not found") || strstr(buf, "not exist"))
162                 return 1;
163
164         /* some other error, like network hangup */
165         return 0;
166 }
167
168 void
169 main(int argc, char **argv)
170
171         char *f[10], *local, *name, *remote, *s, *t, verb;
172         int fd, havedb, havelocal, i, k, n, nf, resolve1, skip;
173         int checkedmatch1, checkedmatch2, 
174                 checkedmatch3, checkedmatch4;
175         ulong now;
176         Biobuf bin;
177         Dir dbd, ld, nd, rd;
178         Entry *e;
179
180         membogus(argv);
181         quotefmtinstall();
182         ARGBEGIN{
183         case 's':
184         case 'c':
185                 i = ARGC();
186                 addresolve(i, EARGF(usage()));
187                 break;
188         case 'n':
189                 donothing = 1;
190                 verbose = 1;
191                 break;
192         case 'S':
193                 safeinstall = 0;
194                 break;
195         case 'T':
196                 timefile = EARGF(usage());
197                 break;
198         case 't':
199                 tempspool = 0;
200                 break;
201         case 'u':
202                 douid = 1;
203                 break;
204         case 'v':
205                 verbose++;
206                 break;
207         default:
208                 usage();
209         }ARGEND
210
211         if(argc < 3)
212                 usage();
213
214         if(timefile)
215                 readtimefile();
216
217         lroot = argv[1];
218         if(!isdir(lroot))
219                 sysfatal("bad local root directory");
220         rroot = argv[2];
221         if(!isdir(rroot))
222                 sysfatal("bad remote root directory");
223
224         match = argv+3;
225         nmatch = argc-3;
226         for(i=0; i<nmatch; i++)
227                 if(match[i][0] == '/')
228                         match[i]++;
229
230         if((clientdb = opendb(argv[0])) == nil)
231                 sysfatal("opendb %q: %r", argv[2]);
232         
233         copyerr = opendb(nil);
234
235         skip = 0;
236         Binit(&bin, 0, OREAD);
237         for(; s=Brdstr(&bin, '\n', 1); free(s)){
238                 t = estrdup(s);
239                 nf = tokenize(s, f, nelem(f));
240                 if(nf != 10 || strlen(f[2]) != 1){
241                         skip = 1;
242                         fprint(2, "warning: skipping bad log entry <%s>\n", t);
243                         free(t);
244                         continue;
245                 }
246                 free(t);
247                 now = strtoul(f[0], 0, 0);
248                 n = atoi(f[1]);
249                 verb = f[2][0];
250                 name = f[3];
251                 if(now < maxnow || (now==maxnow && n <= maxn))
252                         continue;
253                 local = mkname(localbuf, sizeof localbuf, lroot, name);
254                 if(strcmp(f[4], "-") == 0)
255                         f[4] = f[3];
256                 remote = mkname(remotebuf, sizeof remotebuf, rroot, f[4]);
257                 rd.name = f[4];
258                 rd.mode = strtoul(f[5], 0, 8);
259                 rd.uid = f[6];
260                 rd.gid = f[7];
261                 rd.mtime = strtoul(f[8], 0, 10);
262                 rd.length = strtoll(f[9], 0, 10);
263                 havedb = finddb(clientdb, name, &dbd)>=0;
264                 havelocal = localdirstat(local, &ld)>=0;
265
266                 resolve1 = resolve(name);
267
268                 /*
269                  * if(!ismatch(name)){
270                  *      skip = 1;
271                  *      continue;
272                  * }
273                  * 
274                  * This check used to be right here, but we want
275                  * the time to be able to move forward past entries
276                  * that don't match and have already been applied.
277                  * So now every path below must checked !ismatch(name)
278                  * before making any changes to the local file
279                  * system.  The fake variable checkedmatch
280                  * tracks whether !ismatch(name) has been checked.
281                  * If the compiler doesn't produce any used/set
282                  * warnings, then all the paths should be okay.
283                  * Even so, we have the asserts to fall back on.
284                  */
285                 switch(verb){
286                 case 'd':       /* delete file */
287                         delce(local);
288                         if(!havelocal)  /* doesn't exist; who cares? */
289                                 break;
290                         if(access(remote, AEXIST) >= 0) /* got recreated! */
291                                 break;
292                         if(!ismatch(name)){
293                                 if(!skip)
294                                         fprint(2, "stopped updating log apply time because of %s\n", name);
295                                 skip = 1;
296                                 continue;
297                         }
298                         SET(checkedmatch1);
299                         if(!havedb){
300                                 if(resolve1 == 's')
301                                         goto DoRemove;
302                                 else if(resolve1 == 'c')
303                                         goto DoRemoveDb;
304                                 conflict(name, "locally created; will not remove");
305                                 skip = 1;
306                                 continue;
307                         }
308                         assert(havelocal && havedb);
309                         if(dbd.mtime > rd.mtime)                /* we have a newer file than what was deleted */
310                                 break;
311                         if(samecontents(local, remote) > 0){    /* going to get recreated */
312                                 chat("= %q %luo %q %q %lud\n", name, rd.mode, rd.uid, rd.gid, rd.mtime);
313                                 break;
314                         }
315                         if(!(dbd.mode&DMDIR) && (dbd.mtime != ld.mtime || dbd.length != ld.length)){    /* locally modified since we downloaded it */
316                                 if(resolve1 == 's')
317                                         goto DoRemove;
318                                 else if(resolve1 == 'c')
319                                         break;
320                                 conflict(name, "locally modified; will not remove");
321                                 skip = 1;
322                                 continue;
323                         }
324                     DoRemove:
325                         USED(checkedmatch1);
326                         assert(ismatch(name));
327                         chat("d %q %luo %q %q %lud\n", name, rd.mode, rd.uid, rd.gid, rd.mtime);
328                         if(donothing)
329                                 break;
330                         if(remove(local) < 0){
331                                 error("removing %q: %r", name);
332                                 skip = 1;
333                                 continue;
334                         }
335                     DoRemoveDb:
336                         USED(checkedmatch1);
337                         assert(ismatch(name));
338                         removedb(clientdb, name);
339                         break;
340
341                 case 'a':       /* add file */
342                         if(!havedb){
343                                 if(!ismatch(name)){
344                                         if(!skip)
345                                                 fprint(2, "stopped updating log apply time because of %s\n", name);
346                                         skip = 1;
347                                         continue;
348                                 }
349                                 SET(checkedmatch2);
350                                 if(!havelocal)
351                                         goto DoCreate;
352                                 if((ld.mode&DMDIR) && (rd.mode&DMDIR))
353                                         break;
354                                 if(samecontents(local, remote) > 0){
355                                         chat("= %q %luo %q %q %lud\n", name, rd.mode, rd.uid, rd.gid, rd.mtime);
356                                         goto DoCreateDb;
357                                 }
358                                 if(resolve1 == 's')
359                                         goto DoCreate;
360                                 else if(resolve1 == 'c')
361                                         goto DoCreateDb;
362                                 conflict(name, "locally created; will not overwrite");
363                                 skip = 1;
364                                 continue;
365                         }
366                         assert(havedb);
367                         if(dbd.mtime >= rd.mtime)       /* already created this file; ignore */
368                                 break;
369                         if(havelocal){
370                                 if((ld.mode&DMDIR) && (rd.mode&DMDIR))
371                                         break;
372                                 if(!ismatch(name)){
373                                         if(!skip)
374                                                 fprint(2, "stopped updating log apply time because of %s\n", name);
375                                         skip = 1;
376                                         continue;
377                                 }
378                                 SET(checkedmatch2);
379                                 if(samecontents(local, remote) > 0){
380                                         chat("= %q %luo %q %q %lud\n", name, rd.mode, rd.uid, rd.gid, rd.mtime);
381                                         goto DoCreateDb;
382                                 }
383                                 if(dbd.mtime==ld.mtime && dbd.length==ld.length)
384                                         goto DoCreate;
385                                 if(resolve1=='s')
386                                         goto DoCreate;
387                                 else if(resolve1 == 'c')
388                                         break;
389                                 conflict(name, "locally modified; will not overwrite");
390                                 skip = 1;
391                                 continue;
392                         }
393                         if(!ismatch(name)){
394                                 if(!skip)
395                                         fprint(2, "stopped updating log apply time because of %s\n", name);
396                                 skip = 1;
397                                 continue;
398                         }
399                         SET(checkedmatch2);
400                     DoCreate:
401                         USED(checkedmatch2);
402                         assert(ismatch(name));
403                         if(notexists(remote)){
404                                 addce(local);
405                                 /* no skip=1 */
406                                 break;;
407                         }
408                         chat("a %q %luo %q %q %lud\n", name, rd.mode, rd.uid, rd.gid, rd.mtime);
409                         if(donothing)
410                                 break;
411                         if(rd.mode&DMDIR){
412                                 fd = create(local, OREAD, DMDIR);
413                                 if(fd < 0 && isdir(local))
414                                         fd = open(local, OREAD);
415                                 if(fd  < 0){
416                                         error("mkdir %q: %r", name);
417                                         skip = 1;
418                                         continue;
419                                 }
420                                 nulldir(&nd);
421                                 nd.mode = rd.mode;
422                                 if(dirfwstat(fd, &nd) < 0)
423                                         fprint(2, "warning: cannot set mode on %q\n", local);
424                                 nulldir(&nd);
425                                 nd.gid = rd.gid;
426                                 if(dirfwstat(fd, &nd) < 0)
427                                         fprint(2, "warning: cannot set gid on %q\n", local);
428                                 if(douid){
429                                         nulldir(&nd);
430                                         nd.uid = rd.uid;
431                                         if(dirfwstat(fd, &nd) < 0)
432                                                 fprint(2, "warning: cannot set uid on %q\n", local);
433                                 }
434                                 close(fd);
435                                 rd.mtime = now;
436                         }else{
437                                 if(copyfile(local, remote, name, &rd, 1, &k) < 0){
438                                         if(k)
439                                                 addce(local);
440                                         skip = 1;
441                                         continue;
442                                 }
443                         }
444                     DoCreateDb:
445                         USED(checkedmatch2);
446                         assert(ismatch(name));
447                         insertdb(clientdb, name, &rd);
448                         break;
449                         
450                 case 'c':       /* change contents */
451                         if(!havedb){
452                                 if(notexists(remote)){
453                                         addce(local);
454                                         /* no skip=1 */
455                                         break;
456                                 }
457                                 if(!ismatch(name)){
458                                         if(!skip)
459                                                 fprint(2, "stopped updating log apply time because of %s\n", name);
460                                         skip = 1;
461                                         continue;
462                                 }
463                                 SET(checkedmatch3);
464                                 if(resolve1 == 's')
465                                         goto DoCopy;
466                                 else if(resolve1=='c')
467                                         goto DoCopyDb;
468                                 if(samecontents(local, remote) > 0){
469                                         chat("= %q %luo %q %q %lud\n", name, rd.mode, rd.uid, rd.gid, rd.mtime);
470                                         goto DoCopyDb;
471                                 }
472                                 if(havelocal)
473                                         conflict(name, "locally created; will not update");
474                                 else
475                                         conflict(name, "not replicated; will not update");
476                                 skip = 1;
477                                 continue;
478                         }
479                         if(dbd.mtime >= rd.mtime)               /* already have/had this version; ignore */
480                                 break;
481                         if(!ismatch(name)){
482                                 if(!skip)
483                                         fprint(2, "stopped updating log apply time because of %s\n", name);
484                                 skip = 1;
485                                 continue;
486                         }
487                         SET(checkedmatch3);
488                         if(!havelocal){
489                                 if(notexists(remote)){
490                                         addce(local);
491                                         /* no skip=1 */
492                                         break;
493                                 }
494                                 if(resolve1 == 's')
495                                         goto DoCopy;
496                                 else if(resolve1 == 'c')
497                                         break;
498                                 conflict(name, "locally removed; will not update");
499                                 skip = 1;
500                                 continue;
501                         }
502                         assert(havedb && havelocal);
503                         if(dbd.mtime != ld.mtime || dbd.length != ld.length){
504                                 if(notexists(remote)){
505                                         addce(local);
506                                         /* no skip=1 */
507                                         break;
508                                 }
509                                 if(samecontents(local, remote) > 0){
510                                         chat("= %q %luo %q %q %lud\n", name, rd.mode, rd.uid, rd.gid, rd.mtime);
511                                         goto DoCopyDb;
512                                 }
513                                 if(resolve1 == 's')
514                                         goto DoCopy;
515                                 else if(resolve1 == 'c')
516                                         break;
517                                 conflict(name, "locally modified; will not update [%llud %lud -> %llud %lud]", dbd.length, dbd.mtime, ld.length, ld.mtime);
518                                 skip = 1;
519                                 continue;
520                         }
521                     DoCopy:
522                         USED(checkedmatch3);
523                         assert(ismatch(name));
524                         if(notexists(remote)){
525                                 addce(local);
526                                 /* no skip=1 */
527                                 break;
528                         }
529                         chat("c %q\n", name);
530                         if(donothing)
531                                 break;
532                         if(copyfile(local, remote, name, &rd, 0, &k) < 0){
533                                 if(k)
534                                         addce(local);
535                                 skip = 1;
536                                 continue;
537                         }
538                     DoCopyDb:
539                         USED(checkedmatch3);
540                         assert(ismatch(name));
541                         if(!havedb){
542                                 if(havelocal)
543                                         dbd = ld;
544                                 else
545                                         dbd = rd;
546                         }
547                         dbd.mtime = rd.mtime;
548                         dbd.length = rd.length;
549                         insertdb(clientdb, name, &dbd);
550                         break;                  
551
552                 case 'm':       /* change metadata */
553                         if(!havedb){
554                                 if(notexists(remote)){
555                                         addce(local);
556                                         /* no skip=1 */
557                                         break;
558                                 }
559                                 if(!ismatch(name)){
560                                         if(!skip)
561                                                 fprint(2, "stopped updating log apply time because of %s\n", name);
562                                         skip = 1;
563                                         continue;
564                                 }
565                                 SET(checkedmatch4);
566                                 if(resolve1 == 's'){
567                                         USED(checkedmatch4);
568                                         SET(checkedmatch2);
569                                         goto DoCreate;
570                                 }
571                                 else if(resolve1 == 'c')
572                                         goto DoMetaDb;
573                                 if(havelocal)
574                                         conflict(name, "locally created; will not update metadata");
575                                 else
576                                         conflict(name, "not replicated; will not update metadata");
577                                 skip = 1;
578                                 continue;
579                         }
580                         if(!(dbd.mode&DMDIR) && dbd.mtime > rd.mtime)           /* have newer version; ignore */
581                                 break;
582                         if((dbd.mode&DMDIR) && dbd.mtime > now)
583                                 break;
584                         if(havelocal && (!douid || strcmp(ld.uid, rd.uid)==0) && strcmp(ld.gid, rd.gid)==0 && ld.mode==rd.mode)
585                                 break;
586                         if(!havelocal){
587                                 if(notexists(remote)){
588                                         addce(local);
589                                         /* no skip=1 */
590                                         break;
591                                 }
592                                 if(!ismatch(name)){
593                                         if(!skip)
594                                                 fprint(2, "stopped updating log apply time because of %s\n", name);
595                                         skip = 1;
596                                         continue;
597                                 }
598                                 SET(checkedmatch4);
599                                 if(resolve1 == 's'){
600                                         USED(checkedmatch4);
601                                         SET(checkedmatch2);
602                                         goto DoCreate;
603                                 }
604                                 else if(resolve1 == 'c')
605                                         break;
606                                 conflict(name, "locally removed; will not update metadata");
607                                 skip = 1;
608                                 continue;
609                         }
610                         if(!(dbd.mode&DMDIR) && (dbd.mtime != ld.mtime || dbd.length != ld.length)){    /* this check might be overkill */
611                                 if(notexists(remote)){
612                                         addce(local);
613                                         /* no skip=1 */
614                                         break;
615                                 }
616                                 if(!ismatch(name)){
617                                         if(!skip)
618                                                 fprint(2, "stopped updating log apply time because of %s\n", name);
619                                         skip = 1;
620                                         continue;
621                                 }
622                                 SET(checkedmatch4);
623                                 if(resolve1 == 's' || samecontents(local, remote) > 0)
624                                         goto DoMeta;
625                                 else if(resolve1 == 'c')
626                                         break;
627                                 conflict(name, "contents locally modified (%s); will not update metadata to %s %s %luo",
628                                         dbd.mtime != ld.mtime ? "mtime" :
629                                         dbd.length != ld.length ? "length" : 
630                                         "unknown",
631                                         rd.uid, rd.gid, rd.mode);
632                                 skip = 1;
633                                 continue;
634                         }
635                         if((douid && strcmp(ld.uid, dbd.uid)!=0) || strcmp(ld.gid, dbd.gid)!=0 || ld.mode!=dbd.mode){
636                                 if(notexists(remote)){
637                                         addce(local);
638                                         /* no skip=1 */
639                                         break;
640                                 }
641                                 if(!ismatch(name)){
642                                         if(!skip)
643                                                 fprint(2, "stopped updating log apply time because of %s\n", name);
644                                         skip = 1;
645                                         continue;
646                                 }
647                                 SET(checkedmatch4);
648                                 if(resolve1 == 's')
649                                         goto DoMeta;
650                                 else if(resolve1 == 'c')
651                                         break;
652                                 conflict(name, "metadata locally changed; will not update metadata to %s %s %luo", rd.uid, rd.gid, rd.mode);
653                                 skip = 1;
654                                 continue;
655                         }
656                         if(!ismatch(name)){
657                                 if(!skip)
658                                         fprint(2, "stopped updating log apply time because of %s\n", name);
659                                 skip = 1;
660                                 continue;
661                         }
662                         SET(checkedmatch4);
663                     DoMeta:
664                         USED(checkedmatch4);
665                         assert(ismatch(name));
666                         if(notexists(remote)){
667                                 addce(local);
668                                 /* no skip=1 */
669                                 break;
670                         }
671                         chat("m %q %luo %q %q %lud\n", name, rd.mode, rd.uid, rd.gid, rd.mtime);
672                         if(donothing)
673                                 break;
674                         nulldir(&nd);
675                         nd.gid = rd.gid;
676                         nd.mode = rd.mode;
677                         if(douid)
678                                 nd.uid = rd.uid;
679                         if(dirwstat(local, &nd) < 0){
680                                 error("dirwstat %q: %r", name);
681                                 skip = 1;
682                                 continue;
683                         }
684                     DoMetaDb:
685                         USED(checkedmatch4);
686                         assert(ismatch(name));
687                         if(!havedb){
688                                 if(havelocal)
689                                         dbd = ld;
690                                 else
691                                         dbd = rd;
692                         }
693                         if(dbd.mode&DMDIR)
694                                 dbd.mtime = now;
695                         dbd.gid = rd.gid;
696                         dbd.mode = rd.mode;
697                         if(douid)
698                                 dbd.uid = rd.uid;
699                         insertdb(clientdb, name, &dbd);
700                         break;
701                 }
702                 if(!skip && !donothing){
703                         maxnow = now;
704                         maxn = n;
705                 }
706         }
707
708         for(e = (Entry*)avlmin(copyerr->avl); e != nil; e = (Entry*)avlnext(e))
709                 error("copying %q: %s\n", e->name, e->d.name);
710
711         if(timefile)
712                 writetimefile();
713         if(nconf)
714                 exits("conflicts");
715
716         if(errors)
717                 exits("errors");
718         exits(nil);
719 }
720
721
722 char*
723 mkname(char *buf, int nbuf, char *a, char *b)
724 {
725         if(strlen(a)+strlen(b)+2 > nbuf)
726                 sysfatal("name too long");
727
728         strcpy(buf, a);
729         if(a[strlen(a)-1] != '/')
730                 strcat(buf, "/");
731         strcat(buf, b);
732         return buf;
733 }
734
735 int
736 isdir(char *s)
737 {
738         ulong m;
739         Dir *d;
740
741         if((d = dirstat(s)) == nil)
742                 return 0;
743         m = d->mode;
744         free(d);
745         return (m&DMDIR) != 0;
746 }
747
748 void
749 conflict(char *name, char *f, ...)
750 {
751         char *s;
752         va_list arg;
753
754         va_start(arg, f);
755         s = vsmprint(f, arg);
756         va_end(arg);
757
758         fprint(2, "! %s: %s\n", name, s);
759         free(s);
760
761         nconf++;
762 }
763
764 void
765 error(char *f, ...)
766 {
767         char *s;
768         va_list arg;
769
770         va_start(arg, f);
771         s = vsmprint(f, arg);
772         va_end(arg);
773         fprint(2, "error: %s\n", s);
774         free(s);
775         errors = 1;
776 }
777
778 int
779 ismatch(char *s)
780 {
781         int i, len;
782
783         if(nmatch == 0)
784                 return 1;
785         for(i=0; i<nmatch; i++){
786                 len = strlen(match[i]);
787                 if(len == 0)
788                         return 1;
789                 if(strncmp(s, match[i], len) == 0 && (s[len]=='/' || s[len] == 0))
790                         return 1;
791         }
792         return 0;
793 }
794
795 int
796 localdirstat(char *name, Dir *d)
797 {
798         static Dir *d2;
799
800         free(d2);
801         if((d2 = dirstat(name)) == nil)
802                 return -1;
803         *d = *d2;
804         return 0;
805 }
806
807 enum { DEFB = 8192 };
808
809 static int
810 cmp1(int fd1, int fd2)
811 {
812         char buf1[DEFB];
813         char buf2[DEFB];
814         int n1, n2;
815         
816         for(;;){
817                 n1 = readn(fd1, buf1, DEFB);
818                 n2 = readn(fd2, buf2, DEFB);
819                 if(n1 < 0 || n2 < 0)
820                         return -1;
821                 if(n1 != n2)
822                         return 0;
823                 if(n1 == 0)
824                         return 1;
825                 if(memcmp(buf1, buf2, n1) != 0)
826                         return 0;
827         }
828 }
829
830 static int
831 copy1(int fdf, int fdt, char *from, char *to)
832 {
833         int i, n, rv, pid[Nwork];
834         Waitmsg *w;
835
836         n = 0;
837         off = 0;
838         for(i=0; i<Nwork; i++){
839                 switch(pid[n] = rfork(RFPROC|RFMEM)){
840                 case 0:
841                         notify(failure);
842                         worker(fdf, fdt, from, to);
843                 case -1:
844                         break;
845                 default:
846                         n++;
847                         break;
848                 }
849         }
850         if(n == 0){
851                 fprint(2, "cp: rfork: %r\n");
852                 return -1;
853         }
854
855         rv = 0;
856         while((w = wait()) != nil){
857                 if(w->msg[0]){
858                         rv = -1;
859                         for(i=0; i<n; i++)
860                                 if(pid[i] > 0)
861                                         postnote(PNPROC, pid[i], "failure");
862                 }
863                 free(w);
864         }
865         return rv;
866 }
867
868 void
869 worker(int fdf, int fdt, char *from, char *to)
870 {
871         char buf[DEFB], *bp;
872         long len, n;
873         vlong o;
874
875         len = sizeof(buf);
876         bp = buf;
877         o = nextoff();
878
879         while(n = pread(fdf, bp, len, o)){
880                 if(n < 0){
881                         fprint(2, "reading %s: %r\n", from);
882                         _exits("bad");
883                 }
884                 if(pwrite(fdt, buf, n, o) != n){
885                         fprint(2, "writing %s: %r\n", to);
886                         _exits("bad");
887                 }
888                 bp += n;
889                 o += n;
890                 len -= n;
891                 if(len == 0){
892                         len = sizeof buf;
893                         bp = buf;
894                         o = nextoff();
895                 }
896         }
897         _exits(nil);
898 }
899
900 vlong
901 nextoff(void)
902 {
903         vlong o;
904
905         qlock(&lk);
906         o = off;
907         off += DEFB;
908         qunlock(&lk);
909
910         return o;
911 }
912
913 void
914 failure(void*, char *note)
915 {
916         if(strcmp(note, "failure") == 0)
917                 _exits(nil);
918         noted(NDFLT);
919 }
920
921
922 static int
923 opentemp(char *template)
924 {
925         int fd, i;
926         char *p;
927
928         p = estrdup(template);
929         fd = -1;
930         for(i=0; i<10; i++){
931                 mktemp(p);
932                 if((fd=create(p, ORDWR|OEXCL|ORCLOSE, 0000)) >= 0)
933                         break;
934                 strcpy(p, template);
935         }
936         if(fd < 0)
937                 return -1;
938         strcpy(template, p);
939         free(p);
940         return fd;
941 }
942
943 int
944 copyfile(char *local, char *remote, char *name, Dir *d, int dowstat, int *printerror)
945 {
946         Dir *d0, *d1, *dl;
947         Dir nd;
948         int rfd, tfd, wfd, didcreate;
949         char tmp[32], *p, *safe;
950         char err[ERRMAX];
951
952 Again:
953         *printerror = 0;
954         if((rfd = open(remote, OREAD)) < 0)
955                 return -1;
956
957         d0 = dirfstat(rfd);
958         if(d0 == nil){
959                 close(rfd);
960                 return -1;
961         }
962         *printerror = 1;
963         if(!tempspool){
964                 tfd = rfd;
965                 goto DoCopy;
966         }
967         strcpy(tmp, "/tmp/replicaXXXXXXXX");
968         tfd = opentemp(tmp);
969         if(tfd < 0){
970                 close(rfd);
971                 free(d0);
972                 return -1;
973         }
974         if(copy1(rfd, tfd, remote, tmp) < 0 || (d1 = dirfstat(rfd)) == nil){
975                 close(rfd);
976                 close(tfd);
977                 free(d0);
978                 return -1;
979         }
980         close(rfd);
981         if(d0->qid.path != d1->qid.path
982         || d0->qid.vers != d1->qid.vers
983         || d0->mtime != d1->mtime
984         || d0->length != d1->length){
985                 /* file changed underfoot; go around again */
986                 close(tfd);
987                 free(d0);
988                 free(d1);
989                 goto Again;
990         }
991         free(d1);
992         if(seek(tfd, 0, 0) != 0){
993                 close(tfd);
994                 free(d0);
995                 return -1;
996         }
997
998 DoCopy:
999         /*
1000          * clumsy but important hack to do safeinstall-like installs.
1001          */
1002         p = strchr(name, '/');
1003         if(safeinstall && p && strncmp(p, "/bin/", 5) == 0 && access(local, AEXIST) >= 0){
1004                 /* 
1005                  * remove bin/_targ
1006                  */
1007                 safe = emalloc(strlen(local)+2);
1008                 strcpy(safe, local);
1009                 p = strrchr(safe, '/')+1;
1010                 memmove(p+1, p, strlen(p)+1);
1011                 p[0] = '_';
1012                 remove(safe);   /* ignore failure */
1013
1014                 /*
1015                  * rename bin/targ to bin/_targ
1016                  */
1017                 nulldir(&nd);
1018                 nd.name = p;
1019                 if(dirwstat(local, &nd) < 0)
1020                         fprint(2, "warning: rename %s to %s: %r\n", local, p);
1021         }
1022
1023         didcreate = 0;
1024         if((dl = dirstat(local)) == nil){
1025                 if((wfd = create(local, OWRITE, 0)) >= 0){
1026                         didcreate = 1;
1027                         goto okay;
1028                 }
1029                 goto err;
1030         }else{
1031                 if((wfd = open(local, OTRUNC|OWRITE)) >= 0)
1032                         goto okay;
1033                 rerrstr(err, sizeof err);
1034                 if(strstr(err, "permission") == nil)
1035                         goto err;
1036                 nulldir(&nd);
1037                 /*
1038                  * Assume the person running pull is in the appropriate
1039                  * groups.  We could set 0666 instead, but I'm worried
1040                  * about leaving the file world-readable or world-writable
1041                  * when it shouldn't be.
1042                  */
1043                 nd.mode = dl->mode | 0660;
1044                 if(nd.mode == dl->mode)
1045                         goto err;
1046                 if(dirwstat(local, &nd) < 0)
1047                         goto err;
1048                 if((wfd = open(local, OTRUNC|OWRITE)) >= 0){
1049                         nd.mode = dl->mode;
1050                         if(dirfwstat(wfd, &nd) < 0)
1051                                 fprint(2, "warning: set mode on %s to 0660 to open; cannot set back to %luo: %r\n", local, nd.mode);
1052                         goto okay;
1053                 }
1054                 nd.mode = dl->mode;
1055                 if(dirwstat(local, &nd) < 0)
1056                         fprint(2, "warning: set mode on %s to %luo to open; open failed; cannot set mode back to %luo: %r\n", local, nd.mode|0660, nd.mode);
1057                 goto err;
1058         }
1059                 
1060 err:
1061         close(tfd);
1062         free(d0);
1063         free(dl);
1064         return -1;
1065
1066 okay:
1067         free(dl);
1068         if(copy1(tfd, wfd, tmp, local) < 0){
1069                 close(tfd);
1070                 close(wfd);
1071                 free(d0);
1072                 return -1;
1073         }
1074         close(tfd);
1075         if(didcreate || dowstat){
1076                 nulldir(&nd);
1077                 nd.mode = d->mode;
1078                 if(dirfwstat(wfd, &nd) < 0)
1079                         fprint(2, "warning: cannot set mode on %s\n", local);
1080                 nulldir(&nd);
1081                 nd.gid = d->gid;
1082                 if(dirfwstat(wfd, &nd) < 0)
1083                         fprint(2, "warning: cannot set gid on %s\n", local);
1084                 if(douid){
1085                         nulldir(&nd);
1086                         nd.uid = d->uid;
1087                         if(dirfwstat(wfd, &nd) < 0)
1088                                 fprint(2, "warning: cannot set uid on %s\n", local);
1089                 }
1090         }
1091         d->mtime = d0->mtime;
1092         d->length = d0->length;
1093         nulldir(&nd);
1094         nd.mtime = d->mtime;
1095         if(dirfwstat(wfd, &nd) < 0)
1096                 fprint(2, "warning: cannot set mtime on %s\n", local);
1097         free(d0);
1098
1099         close(wfd);
1100         return 0;
1101 }
1102
1103 int
1104 samecontents(char *local, char *remote)
1105 {
1106         Dir *d0, *d1;
1107         int rfd, tfd, lfd, ret;
1108         char tmp[32];
1109
1110         /* quick check: sizes must match */
1111         d1 = nil;
1112         if((d0 = dirstat(local)) == nil || (d1 = dirstat(remote)) == nil){
1113                 free(d0);
1114                 free(d1);
1115                 return -1;
1116         }
1117         if(d0->length != d1->length){
1118                 free(d0);
1119                 free(d1);
1120                 return 0;
1121         }
1122
1123 Again:
1124         if((rfd = open(remote, OREAD)) < 0)
1125                 return -1;
1126         d0 = dirfstat(rfd);
1127         if(d0 == nil){
1128                 close(rfd);
1129                 return -1;
1130         }
1131
1132         strcpy(tmp, "/tmp/replicaXXXXXXXX");
1133         tfd = opentemp(tmp);
1134         if(tfd < 0){
1135                 close(rfd);
1136                 free(d0);
1137                 return -1;
1138         }
1139         if(copy1(rfd, tfd, remote, tmp) < 0 || (d1 = dirfstat(rfd)) == nil){
1140                 close(rfd);
1141                 close(tfd);
1142                 free(d0);
1143                 return -1;
1144         }
1145         close(rfd);
1146         if(d0->qid.path != d1->qid.path
1147         || d0->qid.vers != d1->qid.vers
1148         || d0->mtime != d1->mtime
1149         || d0->length != d1->length){
1150                 /* file changed underfoot; go around again */
1151                 close(tfd);
1152                 free(d0);
1153                 free(d1);
1154                 goto Again;
1155         }
1156         free(d1);
1157         free(d0);
1158         if(seek(tfd, 0, 0) != 0){
1159                 close(tfd);
1160                 return -1;
1161         }
1162
1163         /*
1164          * now compare
1165          */
1166         if((lfd = open(local, OREAD)) < 0){
1167                 close(tfd);
1168                 return -1;
1169         }
1170         
1171         ret = cmp1(lfd, tfd);
1172         close(lfd);
1173         close(tfd);
1174         return ret;
1175 }
1176
1177 /*
1178  * Applylog might try to overwrite itself.
1179  * To avoid problems with this, we copy ourselves
1180  * into /tmp and then re-exec.
1181  */
1182 char *rmargv0;
1183
1184 static void
1185 rmself(void)
1186 {
1187         remove(rmargv0);
1188 }
1189
1190 static int
1191 genopentemp(char *template, int mode, int perm)
1192 {
1193         int fd, i;
1194         char *p;        
1195
1196         p = estrdup(template);
1197         fd = -1;
1198         for(i=0; i<10; i++){
1199                 mktemp(p);
1200                 if(access(p, 0) < 0 && (fd=create(p, mode, perm)) >= 0)
1201                         break;
1202                 strcpy(p, template);
1203         }
1204         if(fd < 0)
1205                 sysfatal("could not create temporary file");
1206
1207         strcpy(template, p);
1208         free(p);
1209
1210         return fd;
1211 }
1212
1213 static void
1214 membogus(char **argv)
1215 {
1216         int n, fd, wfd;
1217         char template[50], buf[1024];
1218
1219         if(strncmp(argv[0], "/tmp/_applylog_", 1+3+1+1+8+1)==0) {
1220                 rmargv0 = argv[0];
1221                 atexit(rmself);
1222                 return;
1223         }
1224
1225         if((fd = open(argv[0], OREAD)) < 0)
1226                 return;
1227
1228         strcpy(template, "/tmp/_applylog_XXXXXX");
1229         if((wfd = genopentemp(template, OWRITE, 0700)) < 0)
1230                 return;
1231
1232         while((n = read(fd, buf, sizeof buf)) > 0)
1233                 if(write(wfd, buf, n) != n)
1234                         goto Error;
1235
1236         if(n != 0)
1237                 goto Error;
1238
1239         close(fd);
1240         close(wfd);
1241
1242         argv[0] = template;
1243         exec(template, argv);
1244         fprint(2, "exec error %r\n");
1245
1246 Error:
1247         close(fd);
1248         close(wfd);
1249         remove(template);
1250         return;
1251 }