xref: /plan9-contrib/sys/src/cmd/replica/applylog.c (revision ec59a3ddbfceee0efe34584c2c9981a5e5ff1ec4)
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 char **conf;
22 int verbose;
23 char **match;
24 int nmatch;
25 int tempspool = 1;
26 int safeinstall = 1;
27 char *lroot;
28 char *rroot;
29 Db *clientdb;
30 int skip;
31 int douid;
32 char *mkname(char*, int, char*, char*);
33 char localbuf[10240];
34 char remotebuf[10240];
35 int copyfile(char*, char*, char*, Dir*, int, int*);
36 ulong maxnow;
37 int maxn;
38 char *timefile;
39 int timefd;
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(!ismatch(name)){
292 				if(!skip)
293 					fprint(2, "stopped updating log apply time because of %s\n", name);
294 				skip = 1;
295 				continue;
296 			}
297 			SET(checkedmatch1);
298 			if(!havedb){
299 				if(resolve1 == 's')
300 					goto DoRemove;
301 				else if(resolve1 == 'c')
302 					goto DoRemoveDb;
303 				conflict(name, "locally created; will not remove");
304 				skip = 1;
305 				continue;
306 			}
307 			assert(havelocal && havedb);
308 			if(dbd.mtime > rd.mtime)		/* we have a newer file than what was deleted */
309 				break;
310 			if(!(dbd.mode&DMDIR) && (dbd.mtime != ld.mtime || dbd.length != ld.length)){	/* locally modified since we downloaded it */
311 				if(resolve1 == 's')
312 					goto DoRemove;
313 				else if(resolve1 == 'c')
314 					break;
315 				conflict(name, "locally modified; will not remove");
316 				skip = 1;
317 				continue;
318 			}
319 		    DoRemove:
320 			USED(checkedmatch1);
321 			assert(ismatch(name));
322 			chat("d %q\n", name);
323 			if(donothing)
324 				break;
325 			if(remove(local) < 0){
326 				error("removing %q", name);
327 				skip = 1;
328 				continue;
329 			}
330 		    DoRemoveDb:
331 			USED(checkedmatch1);
332 			assert(ismatch(name));
333 			removedb(clientdb, name);
334 			break;
335 
336 		case 'a':	/* add file */
337 			if(!havedb){
338 				if(!ismatch(name)){
339 					if(!skip)
340 						fprint(2, "stopped updating log apply time because of %s\n", name);
341 					skip = 1;
342 					continue;
343 				}
344 				SET(checkedmatch2);
345 				if(!havelocal)
346 					goto DoCreate;
347 				if((ld.mode&DMDIR) && (rd.mode&DMDIR))
348 					break;
349 				if(resolve1 == 's')
350 					goto DoCreate;
351 				else if(resolve1 == 'c')
352 					goto DoCreateDb;
353 				conflict(name, "locally created; will not overwrite");
354 				skip = 1;
355 				continue;
356 			}
357 			assert(havedb);
358 			if(dbd.mtime >= rd.mtime)	/* already created this file; ignore */
359 				break;
360 			if(havelocal){
361 				if((ld.mode&DMDIR) && (rd.mode&DMDIR))
362 					break;
363 				if(!ismatch(name)){
364 					if(!skip)
365 						fprint(2, "stopped updating log apply time because of %s\n", name);
366 					skip = 1;
367 					continue;
368 				}
369 				SET(checkedmatch2);
370 				if(dbd.mtime==ld.mtime && dbd.length==ld.length)
371 					goto DoCreate;
372 				if(resolve1=='s')
373 					goto DoCreate;
374 				else if(resolve1 == 'c')
375 					break;
376 				conflict(name, "locally modified; will not overwrite");
377 				skip = 1;
378 				continue;
379 			}
380 			if(!ismatch(name)){
381 				if(!skip)
382 					fprint(2, "stopped updating log apply time because of %s\n", name);
383 				skip = 1;
384 				continue;
385 			}
386 			SET(checkedmatch2);
387 		    DoCreate:
388 			USED(checkedmatch2);
389 			assert(ismatch(name));
390 			if(notexists(remote)){
391 				addce(local);
392 				/* no skip=1 */
393 				break;;
394 			}
395 			chat("a %q %luo %q %q %lud\n", name, rd.mode, rd.uid, rd.gid, rd.mtime);
396 			if(donothing)
397 				break;
398 			if(rd.mode&DMDIR){
399 				if((fd = create(local, OREAD, DMDIR)) < 0){
400 					error("mkdir %q: %r", name);
401 					skip = 1;
402 					continue;
403 				}
404 				nulldir(&nd);
405 				nd.mode = rd.mode;
406 				if(dirfwstat(fd, &nd) < 0)
407 					fprint(2, "warning: cannot set mode on %q\n", local);
408 				nulldir(&nd);
409 				nd.gid = rd.gid;
410 				if(dirfwstat(fd, &nd) < 0)
411 					fprint(2, "warning: cannot set gid on %q\n", local);
412 				if(douid){
413 					nulldir(&nd);
414 					nd.uid = rd.uid;
415 					if(dirfwstat(fd, &nd) < 0)
416 						fprint(2, "warning: cannot set uid on %q\n", local);
417 				}
418 				close(fd);
419 				rd.mtime = now;
420 			}else{
421 				if(copyfile(local, remote, name, &rd, 1, &k) < 0){
422 					if(k)
423 						addce(local);
424 					skip = 1;
425 					continue;
426 				}
427 			}
428 		    DoCreateDb:
429 			USED(checkedmatch2);
430 			assert(ismatch(name));
431 			insertdb(clientdb, name, &rd);
432 			break;
433 
434 		case 'c':	/* change contents */
435 			if(!havedb){
436 				if(notexists(remote)){
437 					addce(local);
438 					/* no skip=1 */
439 					break;
440 				}
441 				if(!ismatch(name)){
442 					if(!skip)
443 						fprint(2, "stopped updating log apply time because of %s\n", name);
444 					skip = 1;
445 					continue;
446 				}
447 				SET(checkedmatch3);
448 				if(resolve1 == 's')
449 					goto DoCopy;
450 				else if(resolve1=='c')
451 					goto DoCopyDb;
452 				if(havelocal)
453 					conflict(name, "locally created; will not update");
454 				else
455 					conflict(name, "not replicated; will not update");
456 				skip = 1;
457 				continue;
458 			}
459 			if(dbd.mtime >= rd.mtime)		/* already have/had this version; ignore */
460 				break;
461 			if(!ismatch(name)){
462 				if(!skip)
463 					fprint(2, "stopped updating log apply time because of %s\n", name);
464 				skip = 1;
465 				continue;
466 			}
467 			SET(checkedmatch3);
468 			if(!havelocal){
469 				if(notexists(remote)){
470 					addce(local);
471 					/* no skip=1 */
472 					break;
473 				}
474 				if(resolve1 == 's')
475 					goto DoCopy;
476 				else if(resolve1 == 'c')
477 					break;
478 				conflict(name, "locally removed; will not update");
479 				skip = 1;
480 				continue;
481 			}
482 			assert(havedb && havelocal);
483 			if(dbd.mtime != ld.mtime || dbd.length != ld.length){
484 				if(notexists(remote)){
485 					addce(local);
486 					/* no skip=1 */
487 					break;
488 				}
489 				if(resolve1 == 's')
490 					goto DoCopy;
491 				else if(resolve1 == 'c')
492 					break;
493 				conflict(name, "locally modified; will not update");
494 				skip = 1;
495 				continue;
496 			}
497 		    DoCopy:
498 			USED(checkedmatch3);
499 			assert(ismatch(name));
500 			if(notexists(remote)){
501 				addce(local);
502 				/* no skip=1 */
503 				break;
504 			}
505 			chat("c %q\n", name);
506 			if(donothing)
507 				break;
508 			if(copyfile(local, remote, name, &rd, 0, &k) < 0){
509 				if(k)
510 					addce(local);
511 				skip = 1;
512 				continue;
513 			}
514 		    DoCopyDb:
515 			USED(checkedmatch3);
516 			assert(ismatch(name));
517 			if(!havedb){
518 				if(havelocal)
519 					dbd = ld;
520 				else
521 					dbd = rd;
522 			}
523 			dbd.mtime = rd.mtime;
524 			dbd.length = rd.length;
525 			insertdb(clientdb, name, &dbd);
526 			break;
527 
528 		case 'm':	/* change metadata */
529 			if(!havedb){
530 				if(notexists(remote)){
531 					addce(local);
532 					/* no skip=1 */
533 					break;
534 				}
535 				if(!ismatch(name)){
536 					if(!skip)
537 						fprint(2, "stopped updating log apply time because of %s\n", name);
538 					skip = 1;
539 					continue;
540 				}
541 				SET(checkedmatch4);
542 				if(resolve1 == 's'){
543 					USED(checkedmatch4);
544 					SET(checkedmatch2);
545 					goto DoCreate;
546 				}
547 				else if(resolve1 == 'c')
548 					goto DoMetaDb;
549 				if(havelocal)
550 					conflict(name, "locally created; will not update metadata");
551 				else
552 					conflict(name, "not replicated; will not update metadata");
553 				skip = 1;
554 				continue;
555 			}
556 			if(!(dbd.mode&DMDIR) && dbd.mtime > rd.mtime)		/* have newer version; ignore */
557 				break;
558 			if((dbd.mode&DMDIR) && dbd.mtime > now)
559 				break;
560 			if(havelocal && (!douid || strcmp(ld.uid, rd.uid)==0) && strcmp(ld.gid, rd.gid)==0 && ld.mode==rd.mode)
561 				break;
562 			if(!havelocal){
563 				if(notexists(remote)){
564 					addce(local);
565 					/* no skip=1 */
566 					break;
567 				}
568 				if(!ismatch(name)){
569 					if(!skip)
570 						fprint(2, "stopped updating log apply time because of %s\n", name);
571 					skip = 1;
572 					continue;
573 				}
574 				SET(checkedmatch4);
575 				if(resolve1 == 's'){
576 					USED(checkedmatch4);
577 					SET(checkedmatch2);
578 					goto DoCreate;
579 				}
580 				else if(resolve1 == 'c')
581 					break;
582 				conflict(name, "locally removed; will not update metadata");
583 				skip = 1;
584 				continue;
585 			}
586 			if(!(dbd.mode&DMDIR) && (dbd.mtime != ld.mtime || dbd.length != ld.length)){	/* this check might be overkill */
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 					goto DoMeta;
601 				else if(resolve1 == 'c')
602 					break;
603 				conflict(name, "contents locally modified (%s); will not update metadata to %s %s %luo",
604 					dbd.mtime != ld.mtime ? "mtime" :
605 					dbd.length != ld.length ? "length" :
606 					"unknown",
607 					rd.uid, rd.gid, rd.mode);
608 				skip = 1;
609 				continue;
610 			}
611 			if((douid && strcmp(ld.uid, dbd.uid)!=0) || strcmp(ld.gid, dbd.gid)!=0 || ld.mode!=dbd.mode){
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')
625 					goto DoMeta;
626 				else if(resolve1 == 'c')
627 					break;
628 				conflict(name, "metadata locally changed; will not update metadata to %s %s %luo", rd.uid, rd.gid, rd.mode);
629 				skip = 1;
630 				continue;
631 			}
632 			if(!ismatch(name)){
633 				if(!skip)
634 					fprint(2, "stopped updating log apply time because of %s\n", name);
635 				skip = 1;
636 				continue;
637 			}
638 			SET(checkedmatch4);
639 		    DoMeta:
640 			USED(checkedmatch4);
641 			assert(ismatch(name));
642 			if(notexists(remote)){
643 				addce(local);
644 				/* no skip=1 */
645 				break;
646 			}
647 			chat("m %q %luo %q %q %lud\n", name, rd.mode, rd.uid, rd.gid, rd.mtime);
648 			if(donothing)
649 				break;
650 			nulldir(&nd);
651 			nd.gid = rd.gid;
652 			nd.mode = rd.mode;
653 			if(douid)
654 				nd.uid = rd.uid;
655 			if(dirwstat(local, &nd) < 0){
656 				error("dirwstat %q: %r", name);
657 				skip = 1;
658 				continue;
659 			}
660 		    DoMetaDb:
661 			USED(checkedmatch4);
662 			assert(ismatch(name));
663 			if(!havedb){
664 				if(havelocal)
665 					dbd = ld;
666 				else
667 					dbd = rd;
668 			}
669 			if(dbd.mode&DMDIR)
670 				dbd.mtime = now;
671 			dbd.gid = rd.gid;
672 			dbd.mode = rd.mode;
673 			if(douid)
674 				dbd.uid = rd.uid;
675 			insertdb(clientdb, name, &dbd);
676 			break;
677 		}
678 		if(!skip && !donothing){
679 			maxnow = now;
680 			maxn = n;
681 		}
682 	}
683 
684 	w = avlwalk(copyerr->avl);
685 	while(e = (Entry*)avlnext(w))
686 		error("copying %q: %s\n", e->name, e->d.name);
687 
688 	if(timefile)
689 		writetimefile();
690 	if(nconf)
691 		exits("conflicts");
692 
693 	if(errors)
694 		exits("errors");
695 	exits(nil);
696 }
697 
698 
699 char*
700 mkname(char *buf, int nbuf, char *a, char *b)
701 {
702 	if(strlen(a)+strlen(b)+2 > nbuf)
703 		sysfatal("name too long");
704 
705 	strcpy(buf, a);
706 	if(a[strlen(a)-1] != '/')
707 		strcat(buf, "/");
708 	strcat(buf, b);
709 	return buf;
710 }
711 
712 int
713 isdir(char *s)
714 {
715 	ulong m;
716 	Dir *d;
717 
718 	if((d = dirstat(s)) == nil)
719 		return 0;
720 	m = d->mode;
721 	free(d);
722 	return (m&DMDIR) != 0;
723 }
724 
725 void
726 conflict(char *name, char *f, ...)
727 {
728 	char *s;
729 	va_list arg;
730 
731 	va_start(arg, f);
732 	s = vsmprint(f, arg);
733 	va_end(arg);
734 
735 	fprint(2, "%s: %s\n", name, s);
736 	free(s);
737 
738 	nconf++;
739 //	if(nconf%16 == 0)
740 //		conf = erealloc(conf, (nconf+16)*sizeof(conf[0]));
741 //	conf[nconf++] = estrdup(name);
742 }
743 
744 void
745 error(char *f, ...)
746 {
747 	char *s;
748 	va_list arg;
749 
750 	va_start(arg, f);
751 	s = vsmprint(f, arg);
752 	va_end(arg);
753 	fprint(2, "error: %s\n", s);
754 	free(s);
755 	errors = 1;
756 }
757 
758 int
759 ismatch(char *s)
760 {
761 	int i, len;
762 
763 	if(nmatch == 0)
764 		return 1;
765 	for(i=0; i<nmatch; i++){
766 		len = strlen(match[i]);
767 		if(len == 0)
768 			return 1;
769 		if(strncmp(s, match[i], len) == 0 && (s[len]=='/' || s[len] == 0))
770 			return 1;
771 	}
772 	return 0;
773 }
774 
775 int
776 localdirstat(char *name, Dir *d)
777 {
778 	static Dir *d2;
779 
780 	free(d2);
781 	if((d2 = dirstat(name)) == nil)
782 		return -1;
783 	*d = *d2;
784 	return 0;
785 }
786 
787 enum { DEFB = 8192 };
788 
789 static int
790 copy1(int fdf, int fdt, char *from, char *to)
791 {
792 	int i, n, rv, pid[Nwork];
793 	Waitmsg *w;
794 
795 	n = 0;
796 	off = 0;
797 	for(i=0; i<Nwork; i++){
798 		switch(pid[n] = rfork(RFPROC|RFMEM)){
799 		case 0:
800 			notify(failure);
801 			worker(fdf, fdt, from, to);
802 		case -1:
803 			break;
804 		default:
805 			n++;
806 			break;
807 		}
808 	}
809 	if(n == 0){
810 		fprint(2, "cp: rfork: %r\n");
811 		return -1;
812 	}
813 
814 	rv = 0;
815 	while((w = wait()) != nil){
816 		if(w->msg[0]){
817 			rv = -1;
818 			for(i=0; i<n; i++)
819 				if(pid[i] > 0)
820 					postnote(PNPROC, pid[i], "failure");
821 		}
822 		free(w);
823 	}
824 	return rv;
825 }
826 
827 void
828 worker(int fdf, int fdt, char *from, char *to)
829 {
830 	char buf[DEFB], *bp;
831 	long len, n;
832 	vlong o;
833 
834 	len = sizeof(buf);
835 	bp = buf;
836 	o = nextoff();
837 
838 	while(n = pread(fdf, bp, len, o)){
839 		if(n < 0){
840 			fprint(2, "reading %s: %r\n", from);
841 			_exits("bad");
842 		}
843 		if(pwrite(fdt, buf, n, o) != n){
844 			fprint(2, "writing %s: %r\n", to);
845 			_exits("bad");
846 		}
847 		bp += n;
848 		o += n;
849 		len -= n;
850 		if(len == 0){
851 			len = sizeof buf;
852 			bp = buf;
853 			o = nextoff();
854 		}
855 	}
856 	_exits(nil);
857 }
858 
859 vlong
860 nextoff(void)
861 {
862 	vlong o;
863 
864 	qlock(&lk);
865 	o = off;
866 	off += DEFB;
867 	qunlock(&lk);
868 
869 	return o;
870 }
871 
872 void
873 failure(void*, char *note)
874 {
875 	if(strcmp(note, "failure") == 0)
876 		_exits(nil);
877 	noted(NDFLT);
878 }
879 
880 
881 static int
882 opentemp(char *template)
883 {
884 	int fd, i;
885 	char *p;
886 
887 	p = estrdup(template);
888 	fd = -1;
889 	for(i=0; i<10; i++){
890 		mktemp(p);
891 		if((fd=create(p, ORDWR|OEXCL|ORCLOSE, 0000)) >= 0)
892 			break;
893 		strcpy(p, template);
894 	}
895 	if(fd < 0)
896 		return -1;
897 	strcpy(template, p);
898 	free(p);
899 	return fd;
900 }
901 
902 int
903 copyfile(char *local, char *remote, char *name, Dir *d, int dowstat, int *printerror)
904 {
905 	Dir *d0, *d1, *dl;
906 	Dir nd;
907 	int rfd, tfd, wfd, didcreate;
908 	char tmp[32], *p, *safe;
909 	char err[ERRMAX];
910 
911 Again:
912 	*printerror = 0;
913 	if((rfd = open(remote, OREAD)) < 0)
914 		return -1;
915 
916 	d0 = dirfstat(rfd);
917 	if(d0 == nil){
918 		close(rfd);
919 		return -1;
920 	}
921 	*printerror = 1;
922 	if(!tempspool){
923 		tfd = rfd;
924 		goto DoCopy;
925 	}
926 	strcpy(tmp, "/tmp/replicaXXXXXXXX");
927 	tfd = opentemp(tmp);
928 	if(tfd < 0){
929 		close(rfd);
930 		free(d0);
931 		return -1;
932 	}
933 	if(copy1(rfd, tfd, remote, tmp) < 0 || (d1 = dirfstat(rfd)) == nil){
934 		close(rfd);
935 		close(tfd);
936 		free(d0);
937 		return -1;
938 	}
939 	close(rfd);
940 	if(d0->qid.path != d1->qid.path
941 	|| d0->qid.vers != d1->qid.vers
942 	|| d0->mtime != d1->mtime
943 	|| d0->length != d1->length){
944 		/* file changed underfoot; go around again */
945 		close(tfd);
946 		free(d0);
947 		free(d1);
948 		goto Again;
949 	}
950 	free(d1);
951 	if(seek(tfd, 0, 0) != 0){
952 		close(tfd);
953 		free(d0);
954 		return -1;
955 	}
956 
957 DoCopy:
958 	/*
959 	 * clumsy but important hack to do safeinstall-like installs.
960 	 */
961 	p = strchr(name, '/');
962 	if(safeinstall && p && strncmp(p, "/bin/", 5) == 0 && access(local, AEXIST) >= 0){
963 		/*
964 		 * remove bin/_targ
965 		 */
966 		safe = emalloc(strlen(local)+2);
967 		strcpy(safe, local);
968 		p = strrchr(safe, '/')+1;
969 		memmove(p+1, p, strlen(p)+1);
970 		p[0] = '_';
971 		remove(safe);	/* ignore failure */
972 
973 		/*
974 		 * rename bin/targ to bin/_targ
975 		 */
976 		nulldir(&nd);
977 		nd.name = p;
978 		if(dirwstat(local, &nd) < 0)
979 			fprint(2, "warning: rename %s to %s: %r\n", local, p);
980 	}
981 
982 	didcreate = 0;
983 	if((dl = dirstat(local)) == nil){
984 		if((wfd = create(local, OWRITE, 0)) >= 0){
985 			didcreate = 1;
986 			goto okay;
987 		}
988 		goto err;
989 	}else{
990 		if((wfd = open(local, OTRUNC|OWRITE)) >= 0)
991 			goto okay;
992 		rerrstr(err, sizeof err);
993 		if(strstr(err, "permission") == nil)
994 			goto err;
995 		nulldir(&nd);
996 		/*
997 		 * Assume the person running pull is in the appropriate
998 		 * groups.  We could set 0666 instead, but I'm worried
999 		 * about leaving the file world-readable or world-writable
1000 		 * when it shouldn't be.
1001 		 */
1002 		nd.mode = dl->mode | 0660;
1003 		if(nd.mode == dl->mode)
1004 			goto err;
1005 		if(dirwstat(local, &nd) < 0)
1006 			goto err;
1007 		if((wfd = open(local, OTRUNC|OWRITE)) >= 0){
1008 			nd.mode = dl->mode;
1009 			if(dirfwstat(wfd, &nd) < 0)
1010 				fprint(2, "warning: set mode on %s to 0660 to open; cannot set back to %luo: %r\n", local, nd.mode);
1011 			goto okay;
1012 		}
1013 		nd.mode = dl->mode;
1014 		if(dirwstat(local, &nd) < 0)
1015 			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);
1016 		goto err;
1017 	}
1018 
1019 err:
1020 	close(tfd);
1021 	free(d0);
1022 	free(dl);
1023 	return -1;
1024 
1025 okay:
1026 	free(dl);
1027 	if(copy1(tfd, wfd, tmp, local) < 0){
1028 		close(tfd);
1029 		close(wfd);
1030 		free(d0);
1031 		return -1;
1032 	}
1033 	close(tfd);
1034 	if(didcreate || dowstat){
1035 		nulldir(&nd);
1036 		nd.mode = d->mode;
1037 		if(dirfwstat(wfd, &nd) < 0)
1038 			fprint(2, "warning: cannot set mode on %s\n", local);
1039 		nulldir(&nd);
1040 		nd.gid = d->gid;
1041 		if(dirfwstat(wfd, &nd) < 0)
1042 			fprint(2, "warning: cannot set gid on %s\n", local);
1043 		if(douid){
1044 			nulldir(&nd);
1045 			nd.uid = d->uid;
1046 			if(dirfwstat(wfd, &nd) < 0)
1047 				fprint(2, "warning: cannot set uid on %s\n", local);
1048 		}
1049 	}
1050 	d->mtime = d0->mtime;
1051 	d->length = d0->length;
1052 	nulldir(&nd);
1053 	nd.mtime = d->mtime;
1054 	if(dirfwstat(wfd, &nd) < 0)
1055 		fprint(2, "warning: cannot set mtime on %s\n", local);
1056 	free(d0);
1057 
1058 	close(wfd);
1059 	return 0;
1060 }
1061 
1062 /*
1063  * Applylog might try to overwrite itself.
1064  * To avoid problems with this, we copy ourselves
1065  * into /tmp and then re-exec.
1066  */
1067 char *rmargv0;
1068 
1069 static void
1070 rmself(void)
1071 {
1072 	remove(rmargv0);
1073 }
1074 
1075 static int
1076 genopentemp(char *template, int mode, int perm)
1077 {
1078 	int fd, i;
1079 	char *p;
1080 
1081 	p = estrdup(template);
1082 	fd = -1;
1083 	for(i=0; i<10; i++){
1084 		mktemp(p);
1085 		if(access(p, 0) < 0 && (fd=create(p, mode, perm)) >= 0)
1086 			break;
1087 		strcpy(p, template);
1088 	}
1089 	if(fd < 0)
1090 		sysfatal("could not create temporary file");
1091 
1092 	strcpy(template, p);
1093 	free(p);
1094 
1095 	return fd;
1096 }
1097 
1098 static void
1099 membogus(char **argv)
1100 {
1101 	int n, fd, wfd;
1102 	char template[50], buf[1024];
1103 
1104 	if(strncmp(argv[0], "/tmp/_applylog_", 1+3+1+1+8+1)==0) {
1105 		rmargv0 = argv[0];
1106 		atexit(rmself);
1107 		return;
1108 	}
1109 
1110 	if((fd = open(argv[0], OREAD)) < 0)
1111 		return;
1112 
1113 	strcpy(template, "/tmp/_applylog_XXXXXX");
1114 	if((wfd = genopentemp(template, OWRITE, 0700)) < 0)
1115 		return;
1116 
1117 	while((n = read(fd, buf, sizeof buf)) > 0)
1118 		if(write(wfd, buf, n) != n)
1119 			goto Error;
1120 
1121 	if(n != 0)
1122 		goto Error;
1123 
1124 	close(fd);
1125 	close(wfd);
1126 
1127 	argv[0] = template;
1128 	exec(template, argv);
1129 	fprint(2, "exec error %r\n");
1130 
1131 Error:
1132 	close(fd);
1133 	close(wfd);
1134 	remove(template);
1135 	return;
1136 }
1137