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