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