xref: /plan9-contrib/sys/src/cmd/replica/applylog.c (revision d46c239f8612929b7dbade67d0d071633df3a15d)
1 #include "all.h"
2 
3 int localdirstat(char*, Dir*);
4 int ismatch(char*);
5 void conflict(char*, char*, ...);
6 void error(char*, ...);
7 int isdir(char*);
8 
9 int errors;
10 int nconf;
11 int donothing;
12 char **conf;
13 int verbose;
14 char **match;
15 int nmatch;
16 int resolve;
17 int notempspool;
18 char *lroot;
19 char *rroot;
20 Db *clientdb;
21 int skip;
22 int douid;
23 char *mkname(char*, int, char*, char*);
24 char localbuf[10240];
25 char remotebuf[10240];
26 int copyfile(char*, char*, Dir*, int, int*);
27 ulong maxnow;
28 int maxn;
29 char *timefile;
30 int timefd;
31 
32 Db *copyerr;
33 
34 void
35 readtimefile(void)
36 {
37 	int n;
38 	char buf[24];
39 
40 	if((timefd = open(timefile, ORDWR)) < 0
41 	&& (timefd = create(timefile, ORDWR|OEXCL, 0666)) < 0)
42 		return;
43 
44 	n = readn(timefd, buf, sizeof buf);
45 	if(n < sizeof buf)
46 		return;
47 
48 	maxnow = atoi(buf);
49 	maxn = atoi(buf+12);
50 }
51 
52 void
53 writetimefile(void)
54 {
55 	char buf[24+1];
56 
57 	snprint(buf, sizeof buf, "%11lud %11d ", maxnow, maxn);
58 	pwrite(timefd, buf, 24, 0);
59 }
60 
61 static void membogus(char**);
62 
63 void
64 addce(char *local)
65 {
66 	char e[ERRMAX];
67 	Dir d;
68 
69 	memset(&d, 0, sizeof d);
70 	rerrstr(e, sizeof e);
71 	d.name = atom(e);
72 	d.uid = "";
73 	d.gid = "";
74 	insertdb(copyerr, atom(local), &d);
75 }
76 
77 void
78 delce(char *local)
79 {
80 	removedb(copyerr, local);
81 }
82 
83 void
84 chat(char *f, ...)
85 {
86 	Fmt fmt;
87 	char buf[256];
88 	va_list arg;
89 
90 	if(!verbose)
91 		return;
92 
93 	fmtfdinit(&fmt, 1, buf, sizeof buf);
94 	va_start(arg, f);
95 	fmtvprint(&fmt, f, arg);
96 	va_end(arg);
97 	fmtfdflush(&fmt);
98 }
99 
100 void
101 usage(void)
102 {
103 	fprint(2, "usage: replica/applylog [-cnsuv] [-T timefile] clientdb clientroot serverroot [path ...]\n");
104 	exits("usage");
105 }
106 
107 int
108 notexists(char *path)
109 {
110 	char buf[ERRMAX];
111 
112 	if(access(path, AEXIST) >= 0)
113 		return 0;
114 
115 	rerrstr(buf, sizeof buf);
116 	if(strstr(buf, "entry not found") || strstr(buf, "not exist"))
117 		return 1;
118 
119 	/* some other error, like network hangup */
120 	return 0;
121 }
122 
123 void
124 main(int argc, char **argv)
125 {
126 	char *f[10], *local, *name, *remote, *s, *t, verb;
127 	int fd, havedb, havelocal, k, n, nf, skip;
128 	ulong now;
129 	Biobuf bin;
130 	Dir dbd, ld, nd, rd;
131 	Avlwalk *w;
132 	Entry *e;
133 
134 	membogus(argv);
135 	quotefmtinstall();
136 	ARGBEGIN{
137 	case 's':
138 	case 'c':
139 		resolve = ARGC();
140 		break;
141 	case 'n':
142 		donothing = 1;
143 		verbose = 1;
144 		break;
145 	case 'T':
146 		timefile = EARGF(usage());
147 		break;
148 	case 't':
149 		notempspool = 1;
150 		break;
151 	case 'u':
152 		douid = 1;
153 		break;
154 	case 'v':
155 		verbose = 1;
156 		break;
157 	default:
158 		usage();
159 	}ARGEND
160 
161 	if(argc < 3)
162 		usage();
163 
164 	if(timefile)
165 		readtimefile();
166 
167 	lroot = argv[1];
168 	if(!isdir(lroot))
169 		sysfatal("bad local root directory");
170 	rroot = argv[2];
171 	if(!isdir(rroot))
172 		sysfatal("bad remote root directory");
173 
174 	if((clientdb = opendb(argv[0])) == nil)
175 		sysfatal("opendb %q: %r", argv[2]);
176 	match = argv+3;
177 	nmatch = argc-3;
178 
179 	copyerr = opendb(nil);
180 
181 	skip = 0;
182 	Binit(&bin, 0, OREAD);
183 	for(; s=Brdstr(&bin, '\n', 1); free(s)){
184 		t = estrdup(s);
185 		nf = tokenize(s, f, nelem(f));
186 		if(nf != 10 || strlen(f[2]) != 1){
187 			skip = 1;
188 			fprint(2, "warning: skipping bad log entry <%s>\n", t);
189 			free(t);
190 			continue;
191 		}
192 		free(t);
193 		now = strtoul(f[0], 0, 0);
194 		n = atoi(f[1]);
195 		verb = f[2][0];
196 		name = f[3];
197 		if(now < maxnow || (now==maxnow && n <= maxn))
198 			continue;
199 		if(!ismatch(name)){
200 			skip = 1;
201 			continue;
202 		}
203 		local = mkname(localbuf, sizeof localbuf, lroot, name);
204 		if(strcmp(f[4], "-") == 0)
205 			f[4] = f[3];
206 		remote = mkname(remotebuf, sizeof remotebuf, rroot, f[4]);
207 		rd.name = f[4];
208 		rd.mode = strtoul(f[5], 0, 8);
209 		rd.uid = f[6];
210 		rd.gid = f[7];
211 		rd.mtime = strtoul(f[8], 0, 10);
212 		rd.length = strtoll(f[9], 0, 10);
213 		havedb = finddb(clientdb, name, &dbd)>=0;
214 		havelocal = localdirstat(local, &ld)>=0;
215 
216 		switch(verb){
217 		case 'd':	/* delete file */
218 			delce(local);
219 			if(!havelocal)	/* doesn't exist; who cares? */
220 				break;
221 			if(!havedb){
222 				if(resolve == 's')
223 					goto DoRemove;
224 				else if(resolve == 'c')
225 					goto DoRemoveDb;
226 				conflict(name, "locally created; will not remove");
227 				skip = 1;
228 				continue;
229 			}
230 			assert(havelocal && havedb);
231 			if(dbd.mtime > rd.mtime)		/* we have a newer file than what was deleted */
232 				break;
233 			if(!(dbd.mode&DMDIR) && (dbd.mtime != ld.mtime || dbd.length != ld.length)){	/* locally modified since we downloaded it */
234 				if(resolve == 's')
235 					goto DoRemove;
236 				else if(resolve == 'c')
237 					break;
238 				conflict(name, "locally modified; will not remove");
239 				skip = 1;
240 				continue;
241 			}
242 		    DoRemove:
243 			chat("d %q\n", name);
244 			if(donothing)
245 				break;
246 			if(remove(local) < 0){
247 				error("removing %q", name);
248 				skip = 1;
249 				continue;
250 			}
251 		    DoRemoveDb:
252 			removedb(clientdb, name);
253 			break;
254 
255 		case 'a':	/* add file */
256 			if(!havedb){
257 				if(!havelocal)
258 					goto DoCreate;
259 				if((ld.mode&DMDIR) && (rd.mode&DMDIR))
260 					break;
261 				if(resolve == 's')
262 					goto DoCreate;
263 				else if(resolve == 'c')
264 					goto DoCreateDb;
265 				conflict(name, "locally created; will not overwrite");
266 				skip = 1;
267 				continue;
268 			}
269 			assert(havedb);
270 			if(dbd.mtime >= rd.mtime)	/* already created this file; ignore */
271 				break;
272 			if(havelocal){
273 				if(dbd.mtime==ld.mtime && dbd.length==ld.length)
274 					goto DoCreate;
275 				if(resolve=='s')
276 					goto DoCreate;
277 				else if(resolve == 'c')
278 					break;
279 				conflict(name, "locally modified; will not overwrite");
280 				skip = 1;
281 				continue;
282 			}
283 		    DoCreate:
284 			if(notexists(remote)){
285 				addce(local);
286 				/* no skip=1 */
287 				break;;
288 			}
289 			chat("a %q %luo %q %q %lud\n", name, rd.mode, rd.uid, rd.gid, rd.mtime);
290 			if(donothing)
291 				break;
292 			if(rd.mode&DMDIR){
293 				if((fd = create(local, OREAD, DMDIR)) < 0){
294 					error("mkdir %q: %r", name);
295 					skip = 1;
296 					continue;
297 				}
298 				nulldir(&nd);
299 				nd.mode = rd.mode;
300 				if(dirfwstat(fd, &nd) < 0)
301 					fprint(2, "warning: cannot set mode on %q\n", local);
302 				nulldir(&nd);
303 				nd.gid = rd.gid;
304 				if(dirfwstat(fd, &nd) < 0)
305 					fprint(2, "warning: cannot set gid on %q\n", local);
306 				if(douid){
307 					nulldir(&nd);
308 					nd.uid = rd.uid;
309 					if(dirfwstat(fd, &nd) < 0)
310 						fprint(2, "warning: cannot set uid on %q\n", local);
311 				}
312 				close(fd);
313 				rd.mtime = now;
314 			}else{
315 				if(copyfile(local, remote, &rd, 1, &k) < 0){
316 					if(k)
317 						addce(local);
318 					skip = 1;
319 					continue;
320 				}
321 			}
322 		    DoCreateDb:
323 			insertdb(clientdb, name, &rd);
324 			break;
325 
326 		case 'c':	/* change contents */
327 			if(!havedb){
328 				if(notexists(remote)){
329 					addce(local);
330 					/* no skip=1 */
331 					break;
332 				}
333 				if(resolve == 's')
334 					goto DoCopy;
335 				else if(resolve=='c')
336 					goto DoCopyDb;
337 				if(havelocal)
338 					conflict(name, "locally created; will not update");
339 				else
340 					conflict(name, "not replicated; will not update");
341 				skip = 1;
342 				continue;
343 			}
344 			if(dbd.mtime >= rd.mtime)		/* already have/had this version; ignore */
345 				break;
346 			if(!havelocal){
347 				if(notexists(remote)){
348 					addce(local);
349 					/* no skip=1 */
350 					break;
351 				}
352 				if(resolve == 's')
353 					goto DoCopy;
354 				else if(resolve == 'c')
355 					break;
356 				conflict(name, "locally removed; will not update");
357 				skip = 1;
358 				continue;
359 			}
360 			assert(havedb && havelocal);
361 			if(dbd.mtime != ld.mtime || dbd.length != ld.length){
362 				if(notexists(remote)){
363 					addce(local);
364 					/* no skip=1 */
365 					break;
366 				}
367 				if(resolve == 's')
368 					goto DoCopy;
369 				else if(resolve == 'c')
370 					break;
371 				conflict(name, "locally modified; will not update");
372 				skip = 1;
373 				continue;
374 			}
375 		    DoCopy:
376 			if(notexists(remote)){
377 				addce(local);
378 				/* no skip=1 */
379 				break;
380 			}
381 			chat("c %q\n", name);
382 			if(donothing)
383 				break;
384 			if(copyfile(local, remote, &rd, 0, &k) < 0){
385 				if(k)
386 					addce(local);
387 				skip = 1;
388 				continue;
389 			}
390 		    DoCopyDb:
391 			if(!havedb){
392 				if(havelocal)
393 					dbd = ld;
394 				else
395 					dbd = rd;
396 			}
397 			dbd.mtime = rd.mtime;
398 			dbd.length = rd.length;
399 			insertdb(clientdb, name, &dbd);
400 			break;
401 
402 		case 'm':	/* change metadata */
403 			if(!havedb){
404 				if(notexists(remote)){
405 					addce(local);
406 					/* no skip=1 */
407 					break;
408 				}
409 				if(resolve == 's')
410 					goto DoCreate;
411 				else if(resolve == 'c')
412 					goto DoMetaDb;
413 				if(havelocal)
414 					conflict(name, "locally created; will not update metadata");
415 				else
416 					conflict(name, "not replicated; will not update metadata");
417 				skip = 1;
418 				continue;
419 			}
420 			if(!(dbd.mode&DMDIR) && dbd.mtime > rd.mtime)		/* have newer version; ignore */
421 				break;
422 			if((dbd.mode&DMDIR) && dbd.mtime > now)
423 				break;
424 			if(havelocal && (!douid || strcmp(ld.uid, rd.uid)==0) && strcmp(ld.gid, rd.gid)==0 && ld.mode==rd.mode)	/* nothing to do */
425 				goto DoMetaDb;
426 			if(!(dbd.mode&DMDIR) && dbd.mtime < rd.mtime){	/* this check might be overkill */
427 				if(notexists(remote)){
428 					addce(local);
429 					/* no skip=1 */
430 					break;
431 				}
432 				if(resolve == 's')
433 					goto DoMeta;
434 				else if(resolve == 'c')
435 					break;
436 				conflict(name, "contents out of date; will not update metadata to %s %s %luo",
437 					rd.uid, rd.gid, rd.mode);
438 				skip = 1;
439 				continue;
440 			}
441 			if(!havelocal){
442 				if(notexists(remote)){
443 					addce(local);
444 					/* no skip=1 */
445 					break;
446 				}
447 				if(resolve == 's')
448 					goto DoCreate;
449 				else if(resolve == 'c')
450 					break;
451 				conflict(name, "locally removed; will not update metadata");
452 				skip = 1;
453 				continue;
454 			}
455 			if(!(dbd.mode&DMDIR) && (dbd.mtime != ld.mtime || dbd.length != ld.length)){	/* this check might be overkill */
456 				if(notexists(remote)){
457 					addce(local);
458 					/* no skip=1 */
459 					break;
460 				}
461 				if(resolve == 's')
462 					goto DoMeta;
463 				else if(resolve == 'c')
464 					break;
465 				conflict(name, "contents locally modified; will not update metadata to %s %s %luo",
466 					rd.uid, rd.gid, rd.mode);
467 				skip = 1;
468 				continue;
469 			}
470 			if((douid && strcmp(ld.uid, dbd.uid)!=0) || strcmp(ld.gid, dbd.gid)!=0 || ld.mode!=dbd.mode){
471 				if(notexists(remote)){
472 					addce(local);
473 					/* no skip=1 */
474 					break;
475 				}
476 				if(resolve == 's')
477 					goto DoMeta;
478 				else if(resolve == 'c')
479 					break;
480 				conflict(name, "metadata locally changed; will not update metadata to %s %s %luo", rd.uid, rd.gid, rd.mode);
481 				skip = 1;
482 				continue;
483 			}
484 		    DoMeta:
485 			if(notexists(remote)){
486 				addce(local);
487 				/* no skip=1 */
488 				break;
489 			}
490 			chat("m %q %luo %q %q %lud\n", name, rd.mode, rd.uid, rd.gid, rd.mtime);
491 			if(donothing)
492 				break;
493 			nulldir(&nd);
494 			nd.gid = rd.gid;
495 			nd.mode = rd.mode;
496 			if(douid)
497 				nd.uid = rd.uid;
498 			if(dirwstat(local, &nd) < 0){
499 				error("dirwstat %q: %r", name);
500 				skip = 1;
501 				continue;
502 			}
503 		    DoMetaDb:
504 			if(dbd.mode&DMDIR)
505 				dbd.mtime = now;
506 			dbd.gid = rd.gid;
507 			dbd.mode = rd.mode;
508 			if(douid)
509 				dbd.uid = rd.uid;
510 			insertdb(clientdb, name, &dbd);
511 			break;
512 		}
513 		if(!skip && !donothing){
514 			maxnow = now;
515 			maxn = n;
516 		}
517 	}
518 
519 	w = avlwalk(copyerr->avl);
520 	while(e = (Entry*)avlnext(w))
521 		error("copying %q: %s\n", e->name, e->d.name);
522 
523 	if(timefile)
524 		writetimefile();
525 	if(nconf)
526 		exits("conflicts");
527 
528 	if(errors)
529 		exits("errors");
530 	exits(nil);
531 }
532 
533 
534 char*
535 mkname(char *buf, int nbuf, char *a, char *b)
536 {
537 	if(strlen(a)+strlen(b)+2 > nbuf)
538 		sysfatal("name too long");
539 
540 	strcpy(buf, a);
541 	if(a[strlen(a)-1] != '/')
542 		strcat(buf, "/");
543 	strcat(buf, b);
544 	return buf;
545 }
546 
547 int
548 isdir(char *s)
549 {
550 	ulong m;
551 	Dir *d;
552 
553 	if((d = dirstat(s)) == nil)
554 		return 0;
555 	m = d->mode;
556 	free(d);
557 	return (m&DMDIR) != 0;
558 }
559 
560 void
561 conflict(char *name, char *f, ...)
562 {
563 	char *s;
564 	va_list arg;
565 
566 	va_start(arg, f);
567 	s = vsmprint(f, arg);
568 	va_end(arg);
569 
570 	fprint(2, "%s: %s\n", name, s);
571 	free(s);
572 
573 	nconf++;
574 //	if(nconf%16 == 0)
575 //		conf = erealloc(conf, (nconf+16)*sizeof(conf[0]));
576 //	conf[nconf++] = estrdup(name);
577 }
578 
579 void
580 error(char *f, ...)
581 {
582 	char *s;
583 	va_list arg;
584 
585 	va_start(arg, f);
586 	s = vsmprint(f, arg);
587 	va_end(arg);
588 	fprint(2, "error: %s\n", s);
589 	free(s);
590 	errors = 1;
591 }
592 
593 int
594 ismatch(char *s)
595 {
596 	int i, len;
597 
598 	if(nmatch == 0)
599 		return 1;
600 	for(i=0; i<nmatch; i++){
601 		if(strcmp(s, match[i]) == 0)
602 			return 1;
603 		len = strlen(match[i]);
604 		if(strncmp(s, match[i], len) == 0 && s[len]=='/')
605 			return 1;
606 	}
607 	return 0;
608 }
609 
610 int
611 localdirstat(char *name, Dir *d)
612 {
613 	static Dir *d2;
614 
615 	free(d2);
616 	if((d2 = dirstat(name)) == nil)
617 		return -1;
618 	*d = *d2;
619 	return 0;
620 }
621 
622 enum { DEFB = 8192 };
623 
624 static int
625 copy1(int fdf, int fdt, char *from, char *to)
626 {
627 	char buf[DEFB];
628 	long n, n1, rcount;
629 	int rv;
630 	char err[ERRMAX];
631 
632 	/* clear any residual error */
633 	err[0] = '\0';
634 	errstr(err, ERRMAX);
635 	rv = 0;
636 	for(rcount=0;; rcount++) {
637 		n = read(fdf, buf, DEFB);
638 		if(n <= 0)
639 			break;
640 		n1 = write(fdt, buf, n);
641 		if(n1 != n) {
642 			fprint(2, "error writing %q: %r\n", to);
643 			rv = -1;
644 			break;
645 		}
646 	}
647 	if(n < 0) {
648 		fprint(2, "error reading %q: %r\n", from);
649 		rv = -1;
650 	}
651 	return rv;
652 }
653 
654 static int
655 opentemp(char *template)
656 {
657 	int fd, i;
658 	char *p;
659 
660 	p = estrdup(template);
661 	fd = -1;
662 	for(i=0; i<10; i++){
663 		mktemp(p);
664 		if((fd=create(p, ORDWR|OEXCL|ORCLOSE, 0000)) >= 0)
665 			break;
666 		strcpy(p, template);
667 	}
668 	if(fd < 0)
669 		return -1;
670 	strcpy(template, p);
671 	free(p);
672 	return fd;
673 }
674 
675 int
676 copyfile(char *local, char *remote, Dir *d, int dowstat, int *printerror)
677 {
678 	Dir *d0, *d1;
679 	Dir nd;
680 	int rfd, tfd, wfd, didcreate;
681 	char tmp[32];
682 
683 Again:
684 	*printerror = 0;
685 	if((rfd = open(remote, OREAD)) < 0)
686 		return -1;
687 
688 	d0 = dirfstat(rfd);
689 	if(d0 == nil){
690 		close(rfd);
691 		return -1;
692 	}
693 	*printerror = 1;
694 	if(notempspool){
695 		tfd = rfd;
696 		goto DoCopy;
697 	}
698 	strcpy(tmp, "/tmp/replicaXXXXXXXX");
699 	tfd = opentemp(tmp);
700 	if(tfd < 0){
701 		close(rfd);
702 		free(d0);
703 		return -1;
704 	}
705 	if(copy1(rfd, tfd, remote, tmp) < 0 || (d1 = dirfstat(rfd)) == nil){
706 		close(rfd);
707 		close(tfd);
708 		free(d0);
709 		return -1;
710 	}
711 	close(rfd);
712 	if(d0->qid.path != d1->qid.path
713 	|| d0->qid.vers != d1->qid.vers
714 	|| d0->mtime != d1->mtime
715 	|| d0->length != d1->length){
716 		/* file changed underfoot; go around again */
717 		close(tfd);
718 		free(d0);
719 		free(d1);
720 		goto Again;
721 	}
722 	free(d1);
723 	if(seek(tfd, 0, 0) != 0){
724 		close(tfd);
725 		free(d0);
726 		return -1;
727 	}
728 DoCopy:
729 	didcreate = 0;
730 	if((wfd = open(local, OTRUNC|OWRITE)) < 0){
731 		if((wfd = create(local, OWRITE, 0)) < 0){
732 			close(tfd);
733 			free(d0);
734 			return -1;
735 		}
736 		didcreate = 1;
737 	}
738 	if(copy1(tfd, wfd, tmp, local) < 0){
739 		close(tfd);
740 		close(wfd);
741 		free(d0);
742 		return -1;
743 	}
744 	close(tfd);
745 	if(didcreate || dowstat){
746 		nulldir(&nd);
747 		nd.mode = d->mode;
748 		if(dirfwstat(wfd, &nd) < 0)
749 			fprint(2, "warning: cannot set mode on %s\n", local);
750 		nulldir(&nd);
751 		nd.gid = d->gid;
752 		if(dirfwstat(wfd, &nd) < 0)
753 			fprint(2, "warning: cannot set gid on %s\n", local);
754 		if(douid){
755 			nulldir(&nd);
756 			nd.uid = d->uid;
757 			if(dirfwstat(wfd, &nd) < 0)
758 				fprint(2, "warning: cannot set uid on %s\n", local);
759 		}
760 	}
761 	d->mtime = d0->mtime;
762 	d->length = d0->length;
763 	nulldir(&nd);
764 	nd.mtime = d->mtime;
765 	if(dirfwstat(wfd, &nd) < 0)
766 		fprint(2, "warning: cannot set mtime on %s\n", local);
767 	close(wfd);
768 	free(d0);
769 	return 0;
770 }
771 
772 /*
773  * Applylog might try to overwrite itself.
774  * To avoid problems with this, we copy ourselves
775  * into /tmp and then re-exec.
776  */
777 char *rmargv0;
778 
779 static void
780 rmself(void)
781 {
782 	remove(rmargv0);
783 }
784 
785 static int
786 genopentemp(char *template, int mode, int perm)
787 {
788 	int fd, i;
789 	char *p;
790 
791 	p = estrdup(template);
792 	fd = -1;
793 	for(i=0; i<10; i++){
794 		mktemp(p);
795 		if(access(p, 0) < 0 && (fd=create(p, mode, perm)) >= 0)
796 			break;
797 		strcpy(p, template);
798 	}
799 	if(fd < 0)
800 		sysfatal("could not create temporary file");
801 
802 	strcpy(template, p);
803 	free(p);
804 
805 	return fd;
806 }
807 
808 static void
809 membogus(char **argv)
810 {
811 	int n, fd, wfd;
812 	char template[50], buf[1024];
813 
814 	if(strncmp(argv[0], "/tmp/_inst_", 1+3+1+6)==0) {
815 		rmargv0 = argv[0];
816 		atexit(rmself);
817 		return;
818 	}
819 
820 	if((fd = open(argv[0], OREAD)) < 0)
821 		return;
822 
823 	strcpy(template, "/tmp/_applylog_XXXXXX");
824 	if((wfd = genopentemp(template, OWRITE, 0700)) < 0)
825 		return;
826 
827 	while((n = read(fd, buf, sizeof buf)) > 0)
828 		if(write(wfd, buf, n) != n)
829 			goto Error;
830 
831 	if(n != 0)
832 		goto Error;
833 
834 	close(fd);
835 	close(wfd);
836 
837 	argv[0] = template;
838 	exec(template, argv);
839 	fprint(2, "exec error %r\n");
840 
841 Error:
842 	close(fd);
843 	close(wfd);
844 	remove(template);
845 	return;
846 }
847