xref: /minix/minix/commands/synctree/synctree.c (revision 83133719)
1 /*	synctree 4.16 - Synchronise file tree.		Author: Kees J. Bot
2  *								5 Apr 1989
3  * SYNOPSYS
4  *	synctree [-iuf] [[user1@machine1:]dir1 [[user2@]machine2:]dir2
5  *
6  * Dir2 will then be synchronized to dir1 with respect to the flags.
7  * The flags mean:
8  *	-i  Be interactive on files other than directories too.
9  *	-u  Only install files that are newer, i.e. that need an update.
10  *	-f  Force.  Don't ask for confirmation, all answers are 'yes'.
11  *
12  * Hitting return lets synctree use its proposed answer.  Hitting CTRL-D is
13  * like typing return to all questions that follow.
14  *
15  * If either of the directories to be synchronized contains the file ".backup"
16  * then it is a backup directory.  The file ".backup" in this directory is
17  * an array of mode information indexed on inode number.
18  *
19  * 89/04/05, Kees J. Bot - Birth of tree synchronizing program.
20  * 92/02/02		 - General overhaul, rcp(1) like syntax.
21  */
22 
23 #define nil 0
24 #include <sys/types.h>
25 #include <stddef.h>
26 #include <stdio.h>
27 #include <sys/stat.h>
28 #include <utime.h>
29 #include <string.h>
30 #include <signal.h>
31 #include <dirent.h>
32 #include <errno.h>
33 #include <fcntl.h>
34 #include <stdlib.h>
35 #include <unistd.h>
36 #include <time.h>
37 #include <sys/wait.h>
38 
39 #define USE_SHADOWING 0
40 
41 #ifndef PATH_MAX
42 
43 #define PATH_MAX	1024
44 #endif
45 
46 #ifndef S_ISLNK
47 /* There were no symlinks in medieval times. */
48 #define S_ISLNK(mode)			(0)
49 #define lstat				stat
50 #define symlink(path1, path2)		(errno= ENOSYS, -1)
51 #define readlink(path, buf, len)	(errno= ENOSYS, -1)
52 #endif
53 
54 #define NUMBYTES     4	/* Any number fits in this many bytes. */
55 #define CHUNK     4096	/* Transfer files in this size chunks. */
56 
57 static int install= 0;	/* Install files, do not delete, update if newer. */
58 static int interact= 0;	/* Ask permission to install too. */
59 static int force= 0;	/* Force trees to be completely equal. */
60 static int backup= 0;	/* This tree is for backup. */
61 
62 static char SYNCNAME[]	= "synctree";
63 static char SLAVENAME[]	= "==SLAVE==";
64 static char MASTERNAME[]= "==MASTER==";
65 
66 
67 static char BACKUP[] = ".backup";	/* Backup filemodes. */
68 static int filemodes;			/* Filemodes fildes. */
69 
70 static int chan[2]= { 0, 1 };	/* In and output channel to opposite side. */
71 
72 #define BUCKSIZE (1+NUMBYTES+CHUNK)
73 static char bucket[BUCKSIZE];	/* Put a lot of things here before sending. */
74 static char *buckp= bucket;	/* Fill pointer. */
75 static int buckn= 0;		/* # bytes in bucket. */
76 
77 enum orders {	/* What back breaking labour should the slave perform? */
78 	ENTER= 128,	/* Make ready to process contents of directory. */
79 	ADVANCE,	/* Determine next pathname and report it back. */
80 	CAT,		/* Send contents of file. */
81 	MORE,		/* Send more file contents. */
82 	ORDER_CANCEL,		/* Current pathname is not installed, remove as link. */
83 	DIE,		/* Die with exit(0); */
84 	DIE_BAD,	/* exit(1); */
85 	POSITIVE,	/* Ask a yes/no question expecting yes. */
86 	NEGATIVE,	/* Same expecting no. */
87 	PASS_YES,	/* Pass this to the master will you. */
88 	PASS_NO		/* Same here. */
89 };
90 
91 #ifdef DEBUG
92 char *ORDERS[]= {
93 	"ENTER", "ADVANCE", "CAT", "MORE", "CANCEL", "DIE", "DIE_BAD",
94 	"POSITIVE", "NEGATIVE", "PASS_YES", "PASS_NO"
95 };
96 #endif
97 
98 enum answers {
99 	PATH= 128,	/* Report pathname, and stat(2) info. */
100 	LINK,		/* Report linkname, pathname, and stat(2) info. */
101 	DATA,		/* Contents of file follow. */
102 	NODATA,		/* Can't read file. */
103 	DONE,		/* Nothing more to advance to. */
104 	SYMLINK,	/* Report symlinkname, pathname, and stat(2) info. */
105 	YES, NO		/* Result of an ASK. */
106 };
107 
108 #ifdef DEBUG
109 char *ANSWERS[]= {
110 	"PATH", "LINK", "DATA", "NODATA", "DONE", "SYMLINK", "YES", "NO"
111 };
112 
113 #define DPRINTF(format, arg)	fprintf(stderr, format, arg0, arg)
114 #else
115 #define DPRINTF(format, arg)
116 #endif
117 
118 struct mode {
119 	unsigned short	md_mode;
120 	unsigned short	md_uid;
121 	unsigned short	md_gid;
122 	unsigned short	md_rdev;
123 	unsigned short	md_devsiz;
124 };
125 
126 static char *arg0;	/* basename(argv[0]) */
127 static int ex= 0;	/* exit status. */
128 
129 static void because()
130 {
131 	fprintf(stderr, ": %s\n", strerror(errno));
132 	ex= 1;
133 }
134 
135 static void perr(char *label)
136 {
137 	fprintf(stderr, "%s: %s: %s\n", arg0, label, strerror(errno));
138 	ex= 1;
139 }
140 
141 static void perrx(char *label)
142 {
143 	perr(label);
144 	exit(1);
145 }
146 
147 #if S_HIDDEN
148 /* Support for per achitecture hidden files. */
149 static int transparent= 0;
150 
151 static void isvisible(char *name)
152 {
153 	char *p= name + strlen(name);
154 
155 	while (p > name && *--p == '/') {}
156 
157 	if (p > name && *p == '@' && p[-1] != '/') transparent= 1;
158 }
159 #else
160 #define transparent	0
161 #define isvisible(name)	((void) 0)
162 #endif
163 
164 static void isbackup(int slave)
165 {
166 	if ((filemodes= open(BACKUP, slave ? O_RDONLY : O_RDWR)) >= 0)
167 		backup= 1;
168 	else {
169 		if (errno != ENOENT) perrx(BACKUP);
170 	}
171 }
172 
173 static char path[PATH_MAX+1];	/* Holds pathname of file being worked on. */
174 static char lnkpth[PATH_MAX+1];	/* (Sym)link to path. */
175 static char *linkpath;		/* What path is, or should be linked to. */
176 static struct stat st;		/* Corresponding stat(2) info. */
177 static char Spath[PATH_MAX+1];	/* Slave is looking at this. */
178 static char Slnkpth[PATH_MAX+1];/* (Sym)link to Spath. */
179 static char *Slinkpath=nil;	/* Either nil or Slnkpth. */
180 static struct stat Sst;		/* Slave's stat(2). */
181 
182 static char *addpath(char *p, char *n)
183 /* Add a name to the path, return pointer to end. */
184 {
185 	if (p - path + 1 + strlen(n) > PATH_MAX) {
186 		*p= 0;
187 		fprintf(stderr, "%s: %s/%s is too long.\n", arg0, path, n);
188 		fprintf(stderr, "%s: Unable to continue.\n", arg0);
189 		exit(1);
190 	}
191 	if (p == path+1 && path[0] == '.') p= path;
192 
193 	if (p > path) *p++ = '/';
194 
195 	while (*n != 0) *p++ = *n++;
196 	*p= 0;
197 	return p;
198 }
199 
200 struct entry {	/* A directory entry. */
201 	struct entry	*next;	/* Next entry in same directory */
202 	struct entry	*dir;	/* It is part of this directory */
203 	struct entry	*con;	/* If a dir, its contents */
204 	char		*name;	/* Name of this dir entry */
205 };
206 
207 static struct entry *E= nil;		/* File being processed. */
208 
209 static void setpath(struct entry *e)
210 /* Set path leading to e. */
211 {
212 	static char *pend;
213 
214 	if (e == nil)
215 		pend= path;
216 	else {
217 		setpath(e->dir);
218 		pend= addpath(pend, e->name);
219 	}
220 }
221 
222 static void sort(struct entry **ae)
223 /* This is either a stable mergesort, or thermal noise, I'm no longer sure.
224  * It must be called like this: if (L!=nil && L->next!=nil) sort(&L);
225  */
226 {
227 	/* static */ struct entry *e1, **mid;  /* Need not be local */
228 	struct entry *e2;
229 
230 	e1= *(mid= &(*ae)->next);
231 	do {
232 		if ((e1= e1->next) == nil) break;
233 		mid= &(*mid)->next;
234 	} while ((e1= e1->next) != nil);
235 
236 	e2= *mid;
237 	*mid= nil;
238 
239 	if ((*ae)->next != nil) sort(ae);
240 	if (e2->next != nil) sort(&e2);
241 
242 	e1= *ae;
243 	for (;;) {
244 		if (strcmp(e1->name, e2->name)<=0) {
245 			if ((e1= *(ae= &e1->next)) == nil) {
246 				*ae= e2;
247 				break;
248 			}
249 		} else {
250 			*ae= e2;
251 			e2= *(ae= &e2->next);
252 			*ae= e1;
253 			if (e2 == nil) break;
254 		}
255 	}
256 }
257 
258 static void enter()
259 /* Collect directory entries of E. */
260 {
261 	struct entry **last= &E->con, *new;
262 	struct dirent *e;
263 	DIR *d;
264 
265 	if ((d= opendir(path)) == nil) {
266 		fprintf(stderr, "%s: Can't read dir %s\n", arg0, path);
267 		return;
268 	}
269 
270 	while ((e= readdir(d)) != nil) {
271 		if (e->d_name[0] == '.' && (e->d_name[1] == 0
272 			|| (e->d_name[1] == '.' && e->d_name[2] == 0)
273 		)) continue;
274 
275 		new= (struct entry *) malloc(sizeof(*new));
276 
277 		new->next= nil;
278 		new->dir= E;
279 		new->con= nil;
280 		new->name= (char *) malloc(strlen(e->d_name) + 1);
281 		strcpy(new->name, e->d_name);
282 		*last= new;
283 		last= &new->next;
284 	}
285 	closedir(d);
286 	if (E->con != nil && E->con->next != nil) sort(&E->con);
287 }
288 
289 #define arraysize(a)	(sizeof(a) / sizeof((a)[0]))
290 #define arraylimit(a)	((a) + arraysize(a))
291 
292 static char *link_islink(struct stat *stp, const char *file)
293 {
294     /* Tell if a file, which stat(2) information in '*stp', has been seen
295      * earlier by this function under a different name.  If not return a
296      * null pointer with errno set to ENOENT, otherwise return the name of
297      * the link.  Return a null pointer with an error code in errno for any
298      * error, using E2BIG for a too long file name.
299      *
300      * Use link_islink(nil, nil) to reset all bookkeeping.
301      *
302      * Call for a file twice to delete it from the store.
303      */
304 
305     typedef struct link {	/* In-memory link store. */
306 	struct link	*next;		/* Hash chain on inode number. */
307 	ino_t		ino;		/* File's inode number. */
308 	off_t		off;		/* Offset to more info in temp file. */
309     } link_t;
310     typedef struct dlink {	/* On-disk link store. */
311 	dev_t		dev;		/* Device number. */
312 	char		file[PATH_MAX];	/* Name of earlier seen link. */
313     } dlink_t;
314     static link_t *links[256];		/* Hash list of known links. */
315     static int tfd= -1;			/* Temp file for file name storage. */
316     static dlink_t dlink;
317     link_t *lp, **plp;
318     size_t len;
319     off_t off;
320 
321     if (file == nil) {
322 	/* Reset everything. */
323 	for (plp= links; plp < arraylimit(links); plp++) {
324 	    while ((lp= *plp) != nil) {
325 		*plp= lp->next;
326 		free(lp);
327 	    }
328 	}
329 	if (tfd != -1) close(tfd);
330 	tfd= -1;
331 	return nil;
332     }
333 
334     /* The file must be a non-directory with more than one link. */
335     if (S_ISDIR(stp->st_mode) || stp->st_nlink <= 1) {
336 	errno= ENOENT;
337 	return nil;
338     }
339 
340     plp= &links[stp->st_ino % arraysize(links)];
341 
342     while ((lp= *plp) != nil) {
343 	if (lp->ino == stp->st_ino) {
344 	    /* May have seen this link before.  Get it and check. */
345 	    if (lseek(tfd, lp->off, SEEK_SET) == -1) return nil;
346 	    if (read(tfd, &dlink, sizeof(dlink)) < 0) return nil;
347 
348 	    /* Only need to check the device number. */
349 	    if (dlink.dev == stp->st_dev) {
350 		if (strcmp(file, dlink.file) == 0) {
351 		    /* Called twice.  Forget about this link. */
352 		    *plp= lp->next;
353 		    free(lp);
354 		    errno= ENOENT;
355 		    return nil;
356 		}
357 
358 		/* Return the name of the earlier link. */
359 		return dlink.file;
360 	    }
361 	}
362 	plp= &lp->next;
363     }
364 
365     /* First time I see this link.  Add it to the store. */
366     if (tfd == -1) {
367 	for (;;) {
368 	    char *tmp;
369 
370 	    tmp= tmpnam(nil);
371 	    tfd= open(tmp, O_RDWR|O_CREAT|O_EXCL, 0600);
372 	    if (tfd < 0) {
373 		if (errno != EEXIST) return nil;
374 	    } else {
375 		(void) unlink(tmp);
376 		break;
377 	    }
378 	}
379     }
380     if ((len= strlen(file)) >= PATH_MAX) {
381 	errno= E2BIG;
382 	return nil;
383     }
384 
385     dlink.dev= stp->st_dev;
386     strcpy(dlink.file, file);
387     len += offsetof(dlink_t, file) + 1;
388     if ((off= lseek(tfd, 0, SEEK_END)) == -1) return nil;
389     if (write(tfd, &dlink, len) != len) return nil;
390 
391     if ((lp= malloc(sizeof(*lp))) == nil) return nil;
392     lp->next= nil;
393     lp->ino= stp->st_ino;
394     lp->off= off;
395     *plp= lp;
396     errno= ENOENT;
397     return nil;
398 }
399 
400 #define cancellink()	((void) islink())
401 
402 static char *islink()
403 /* Returns the name of the file path is linked to.  If no such link can be
404  * found, then path is added to the list and nil is returned.  If all the
405  * links of a file have been seen, then it is removed from the list.
406  * Directories are not seen as linkable.
407  */
408 {
409 	char *name;
410 
411 	name= link_islink(&st, path);
412 	if (name == nil && errno != ENOENT) perrx(path);
413 	return name;
414 }
415 
416 static void setstat(ino_t ino, struct stat *stp)
417 /* Set backup status info, we know that backup is true. */
418 {
419 	struct mode md;
420 
421 	md.md_mode = stp->st_mode;
422 	md.md_uid = stp->st_uid;
423 	md.md_gid = stp->st_gid;
424 	md.md_rdev = stp->st_rdev;
425 	md.md_devsiz = stp->st_size / 1024;
426 
427 	if (lseek(filemodes, (off_t) sizeof(md) * (off_t) ino, 0) == -1
428 		|| write(filemodes, (char *) &md, sizeof(md)) != sizeof(md)
429 	) perrx(BACKUP);
430 }
431 
432 static int getstat(char *name, struct stat *stp)
433 /* Get status information of file name, skipping some files.  Backup info
434  * is inserted as needed.
435  */
436 {
437 	errno= 0;
438 
439 	if (strcmp(name, BACKUP) == 0) return -1;
440 
441 	if (lstat(name, stp) < 0) return -1;
442 
443 	if (stp->st_mode == S_IFREG && stp->st_mtime == 0) return -1;
444 
445 	if (backup) {
446 		struct mode md;
447 
448 		if (lseek(filemodes,
449 			(off_t) sizeof(md) * (off_t) stp->st_ino, 0) == -1
450 		    || read(filemodes, (char *) &md, sizeof(md)) != sizeof(md)
451 		    || md.md_mode == 0
452 		) {
453 			errno= 0;
454 			setstat(stp->st_ino, stp);
455 		} else {
456 			stp->st_mode = md.md_mode;
457 			stp->st_uid = md.md_uid;
458 			stp->st_gid = md.md_gid;
459 			stp->st_rdev = md.md_rdev;
460 			if (S_ISBLK(stp->st_mode))
461 				stp->st_size= (off_t) md.md_devsiz * 1024;
462 		}
463 	}
464 	return 0;
465 }
466 
467 static int advance()
468 /* Determine next pathname, return true on success. */
469 {
470 	for (;;) {
471 		if (E==nil) {	/* First call, enter root dir. */
472 			E= (struct entry *) malloc(sizeof(*E));
473 			E->dir= nil;
474 			E->con= nil;
475 			E->next= nil;
476 			E->name= (char *) malloc(3);
477 			strcpy(E->name, transparent ? ".@" : ".");
478 		} else
479 		if (E->con != nil)	/* Dir's files must be processed. */
480 			E= E->con;
481 		else {
482 			for (;;) {
483 				/* Remove E from it's parents list, then
484 				 * try next entry, if none, go to parent dir.
485 				 */
486 				struct entry *junk= E, *parent= E->dir;
487 
488 				if (parent != nil) parent->con= E->next;
489 				E= E->next;
490 				free(junk->name);
491 				free(junk);
492 
493 				if (E != nil) break;
494 
495 				if ((E= parent) == nil) return 0;
496 			}
497 		}
498 		setpath(E);
499 		if (getstat(path, &st) == 0) {
500 			if (S_ISLNK(st.st_mode)) {
501 				int n;
502 
503 				if ((n= readlink(path, lnkpth, PATH_MAX)) >= 0)
504 				{
505 					lnkpth[n]= 0;
506 					break;
507 				}
508 			} else {
509 				break;
510 			}
511 		}
512 		if (errno != 0 && errno != ENOENT) perr(path);
513 	}
514 
515 	linkpath= islink();
516 	DPRINTF("%s: path = %s\n", path);
517 	return 1;
518 }
519 
520 static enum orders request()
521 /* Slave reads command sent by master. */
522 {
523 	static char buf[64], *bp;
524 	static int n= 0;
525 	int req;
526 
527 	for (;;) {
528 		if (n == 0) {
529 			if ((n= read(chan[0], buf, (int) sizeof(buf))) <= 0) {
530 				if (n < 0) perrx("request()");
531 				/* Master died, try to report it then follow. */
532 				fprintf(stderr,
533 					"%s: Master died prematurely.\n", arg0);
534 				exit(1);
535 			}
536 			bp= buf;
537 		}
538 		req= *bp++ & 0xFF;
539 		n--;
540 		if (req >= (int) ENTER) break;
541 
542 		/* Master using slave to print to stdout: */
543 		putchar(req);
544 	}
545 	DPRINTF("%s: request() == %s\n", ORDERS[req - (int) ENTER]);
546 
547 	return (enum orders) req;
548 }
549 
550 static void report()
551 {
552 	int r;
553 
554 	DPRINTF("%s: reporting now!\n", 0);
555 
556 	buckp= bucket;
557 
558 	while (buckn > 0) {
559 		r = buckn;
560 		if (r > (512 << sizeof(char*))) r= (512 << sizeof(char*));
561 
562 		if ((r= write(chan[1], buckp, r)) < 0) perrx("report()");
563 
564 		buckp += r;
565 		buckn -= r;
566 	}
567 	buckp= bucket;
568 	buckn= 0;
569 }
570 
571 static void inform(enum answers a)
572 /* Slave replies to master. */
573 {
574 	DPRINTF("%s: inform(%s)\n", ANSWERS[(int) a - (int) PATH]);
575 
576 	*buckp++ = (int) a;
577 	buckn++;
578 }
579 
580 #define wwrite(buf, n)	(memcpy(buckp, (buf), (n)), buckp+= (n), buckn+= (n))
581 
582 static void sendnum(long n)
583 /* Send number from least to most significant byte. */
584 {
585 #if BYTE_ORDER == LITTLE_ENDIAN
586 	wwrite((char *) &n, sizeof(n));
587 #else
588 	char buf[NUMBYTES];
589 
590 	buf[0]= (char) (n >>  0);
591 	buf[1]= (char) (n >>  8);
592 	buf[2]= (char) (n >> 16);
593 	buf[3]= (char) (n >> 24);
594 	wwrite(buf, sizeof(buf));
595 #endif
596 }
597 
598 static void send(char *buf, int n)
599 /* Slave sends size and contents of buf. */
600 {
601 	sendnum((long) n);
602 	if (n > 0) wwrite(buf, (size_t) n);
603 }
604 
605 static void sendstat(struct stat *stp)
606 {
607 	sendnum((long) stp->st_mode);
608 	sendnum((long) stp->st_uid);
609 	sendnum((long) stp->st_gid);
610 	sendnum((long) stp->st_rdev);
611 	sendnum((long) stp->st_size);
612 	sendnum((long) stp->st_mtime);
613 }
614 
615 static int ask();
616 
617 static void slave()
618 /* Carry out orders from the master, such as transmitting path names.
619  * Note that the slave uses path, not Spath, the master uses Spath.
620  */
621 {
622 	int f, n;
623 	char buf[CHUNK];
624 	enum { run, done, die } state= run;
625 
626 	do {
627 		switch (request()) {
628 		case ENTER:
629 			enter();
630 			break;
631 		case ADVANCE:
632 			if (!advance() || state == done) {
633 				inform(DONE);
634 				state= done;
635 			} else {
636 				if (linkpath!=nil) {
637 					inform(LINK);
638 					send(linkpath, strlen(linkpath) + 1);
639 				} else
640 				if (S_ISLNK(st.st_mode)) {
641 					inform(SYMLINK);
642 					send(lnkpth, strlen(lnkpth) + 1);
643 				} else {
644 					inform(PATH);
645 				}
646 				send(path, strlen(path) + 1);
647 				sendstat(&st);
648 			}
649 			break;
650 		case CAT:
651 			if ((f= open(path, O_RDONLY))<0) {
652 				fprintf(stderr, "%s: Can't open %s",
653 					arg0, path);
654 				because();
655 				inform(NODATA);
656 				break;
657 			}
658 			inform(DATA);
659 			do {
660 				n= read(f, buf, sizeof(buf));
661 				if (n < 0) perr(path);
662 				send(buf, n);
663 				if (n > 0) report();
664 			} while (n > 0);
665 			close(f);
666 			break;
667 		case ORDER_CANCEL:
668 			cancellink();
669 			break;
670 		case DIE_BAD:
671 			ex= 1;
672 			/*FALL THROUGH*/
673 		case DIE:
674 			state= die;
675 			break;
676 		case POSITIVE:
677 			inform(ask('y') ? YES : NO);
678 			break;
679 		case NEGATIVE:
680 			inform(ask('n') ? YES : NO);
681 			break;
682 		case PASS_YES:
683 			inform(YES);
684 			break;
685 		case PASS_NO:
686 			inform(NO);
687 			break;
688 		default:
689 			fprintf(stderr, "%s: strange request\n", arg0);
690 			exit(1);
691 		}
692 		report();
693 	} while (state != die);
694 }
695 
696 static int execute(char **argv)
697 /* Execute a command and return its success or failure. */
698 {
699 	int pid, r, status;
700 
701 	if ((pid= fork())<0) {
702 		perr("fork()");
703 		return 0;
704 	}
705 	if (pid == 0) {
706 		execvp(argv[0], argv);
707 		perrx(argv[0]);
708 	}
709 	while ((r= wait(&status)) != pid) {
710 		if (r < 0) {
711 			perr(argv[0]);
712 			return 0;
713 		}
714 	}
715 	return status == 0;
716 }
717 
718 static int removedir(char *dir)
719 /* Remove a directory and its contents. */
720 {
721 	static char *argv[] = { "rm", "-r", nil, nil };
722 
723 	printf("(rm -r %s)\n", dir);
724 
725 	argv[2]= dir;
726 	return execute(argv);
727 }
728 
729 static void order(enum orders o)
730 /* Master tells slave what to do. */
731 {
732 	char c= (char) o;
733 
734 	DPRINTF("%s: order(%s)\n", ORDERS[o - (int) ENTER]);
735 
736 	if (write(chan[1], &c, 1) != 1) perrx("order()");
737 }
738 
739 static void rread(char *buf, int n)
740 /* Master gets buf of size n from slave, doing multiple reads if needed. */
741 {
742 	int r;
743 
744 	while (n > 0) {
745 		if (buckn == 0) {
746 			switch (buckn= read(chan[0], bucket, BUCKSIZE)) {
747 			case -1:
748 				perrx("reply channel from slave");
749 			case  0:
750 				fprintf(stderr,
751 					"%s: slave died prematurely.\n",
752 					arg0);
753 				exit(1);
754 			}
755 			buckp= bucket;
756 		}
757 		r= n < buckn ? n : buckn;
758 		memcpy(buf, buckp, r);
759 		buckp+= r;
760 		buckn-= r;
761 		buf+= r;
762 		n-= r;
763 	}
764 }
765 
766 static enum answers answer()
767 /* Master reads slave's reply. */
768 {
769 	char c;
770 	int a;
771 
772 	rread(&c, 1);
773 	a= c & 0xFF;
774 
775 	DPRINTF("%s: answer() == %s\n", ANSWERS[a - (int) PATH]);
776 
777 	return (enum answers) a;
778 }
779 
780 static long recnum()
781 /* Read number as pack of bytes from least to most significant.  The data
782  * is on the wire in little-endian format.  (Mostly run on PC's).
783  */
784 {
785 #if BYTE_ORDER == LITTLE_ENDIAN
786 	long n;
787 
788 	rread((char *) &n, (int) sizeof(n));
789 	return n;
790 #else
791 	unsigned char buf[NUMBYTES];
792 
793 	rread(buf, sizeof(buf));
794 	return	buf[0]
795 		| ((unsigned) buf[1] << 8)
796 		| ((unsigned long) buf[2] << 16)
797 		| ((unsigned long) buf[3] << 24);
798 #endif
799 }
800 
801 static int receive(char *buf, int max)
802 /* Master get's data from slave, by first reading size, then data. */
803 {
804 	int n;
805 
806 	n= recnum();
807 	if (n > max) {
808 		fprintf(stderr, "%s: panic: Can't read %d bytes\n", arg0, n);
809 		exit(1);
810 	}
811 	if (n > 0) rread(buf, n);
812 	return n;
813 }
814 
815 static void recstat(struct stat *stp)
816 {
817 	stp->st_mode= recnum();
818 	stp->st_uid= recnum();
819 	stp->st_gid= recnum();
820 	stp->st_rdev= recnum();
821 	stp->st_size= recnum();
822 	stp->st_mtime= recnum();
823 }
824 
825 static int key()
826 {
827 	int c;
828 	static int tty= -1;
829 
830 	if (tty < 0) tty= isatty(0);
831 
832 	if (feof(stdin) || (c= getchar()) == EOF) {
833 		c= '\n';
834 		if (tty) putchar('\n');
835 	}
836 
837 	if (!tty) putchar(c);
838 
839 	return c;
840 }
841 
842 static int ask(int def)
843 /* Ask for a yes or no, anything else means choose def. */
844 {
845 	int y, c;
846 
847 	if (chan[0] == 0) {
848 		/* I'm running remote, tell the slave to ask. */
849 		fflush(stdout);
850 		order(def == 'y' ? POSITIVE : NEGATIVE);
851 		return answer() == YES;
852 	}
853 
854 	printf("? (%c) ", def);
855 	fflush(stdout);
856 
857 	do c= key(); while (c == ' ' || c == '\t');
858 
859 	y= c;
860 
861 	while (c != '\n') c= key();
862 
863 	if (y != 'y' && y != 'Y' && y != 'n' && y != 'N') y= def;
864 
865 	return y == 'y' || y == 'Y';
866 }
867 
868 static void setmodes(int silent)
869 {
870 	struct stat st;
871 	int change= 0;
872 	struct utimbuf tms;
873 
874 	errno= 0;
875 	getstat(Spath, &st);
876 	if (backup && silent) {
877 		setstat(st.st_ino, &Sst);
878 		getstat(Spath, &st);
879 	}
880 
881 	if (S_ISLNK(st.st_mode)) return;
882 
883 	if (errno == 0 && st.st_mode != Sst.st_mode) {
884 		if (!backup) chmod(Spath, Sst.st_mode & 07777);
885 		change= 1;
886 	}
887 	if (errno == 0
888 		&& (st.st_uid != Sst.st_uid || st.st_gid != Sst.st_gid)
889 		&& (backup || geteuid() == 0)
890 	) {
891 		errno= 0;
892 		if (!backup) chown(Spath, Sst.st_uid, Sst.st_gid);
893 		change= 1;
894 	}
895 
896 	if (backup && !silent) setstat(st.st_ino, &Sst);
897 
898 	if (errno == 0 && S_ISREG(Sst.st_mode) && st.st_mtime != Sst.st_mtime) {
899 		time(&tms.actime);
900 		tms.modtime= Sst.st_mtime;
901 		errno= 0;
902 		utime(Spath, &tms);
903 		change= 1;
904 	}
905 	if (errno != 0) {
906 		fprintf(stderr, "%s: Can't set modes of %s", arg0, Spath);
907 		because();
908 	} else
909 	if (change && !silent) {
910 		printf("Mode changed of %s\n", Spath);
911 	}
912 }
913 
914 static void makeold()
915 {
916 	static struct utimbuf tms= { 0, 0 };
917 
918 	if (utime(Spath, &tms) < 0) {
919 		if (errno != ENOENT) {
920 			fprintf(stderr,
921 				"%s: can't make %s look old", arg0, Spath);
922 			because();
923 		}
924 	} else {
925 		fprintf(stderr, "%s: made %s look old.\n", arg0, Spath);
926 	}
927 }
928 
929 static int busy= 0;
930 
931 static void bail_out(int sig)
932 {
933 	signal(sig, SIG_IGN);
934 
935 	fprintf(stderr, "%s: Exiting after signal %d\n", arg0, sig);
936 
937 	if (busy) {
938 		fprintf(stderr, "%s: was installing %s\n", arg0, Spath);
939 		makeold();
940 	}
941 	order(DIE_BAD);
942 
943 	exit(sig);
944 }
945 
946 static int makenode(char *name, int mode, dev_t addr, off_t size)
947 {
948 	int r;
949 
950 	if (!backup) {
951 		r= mknod(name, mode, addr);
952 	} else {
953 		if ((r= creat(name, 0644)) >= 0) close(r);
954 	}
955 	return r;
956 }
957 
958 static void add(int update)
959 /* Add Spath to the filesystem. */
960 {
961 	int f, n;
962 	char buf[CHUNK];
963 	int forced_update= force && update;
964 
965 	if (Slinkpath != nil && !S_ISLNK(Sst.st_mode)) {
966 		if (interact && !update) {
967 			printf("Link %s to %s", Spath, Slinkpath);
968 			if (!ask('n')) return;
969 		}
970 		if (link(Slinkpath, Spath) >= 0) {
971 			printf("Linked %s to %s\n", Spath, Slinkpath);
972 			return;
973 		} else {
974 			fprintf(stderr,
975 				"%s: Can't link %s to %s",
976 				arg0, Slinkpath, Spath);
977 			because();
978 			/* Try to install instead. */
979 		}
980 	}
981 	switch (Sst.st_mode & S_IFMT) {
982 	case S_IFDIR:
983 		if (!force) {
984 			printf("Add dir %s", Spath);
985 			if (!ask('n')) return;
986 		}
987 		if (mkdir(Spath, backup ? 0755 : Sst.st_mode) < 0) {
988 			perr(Spath);
989 			return;
990 		}
991 		printf("Directory %s created.\n", Spath);
992 		order(ENTER);
993 		break;
994 	case S_IFBLK:
995 	case S_IFCHR:
996 	case S_IFIFO:
997 		if (interact && !update) {
998 			printf("Create special file %s", Spath);
999 			if (!ask('n')) { order(ORDER_CANCEL); return; }
1000 		}
1001 		if (makenode(Spath, Sst.st_mode, Sst.st_rdev, Sst.st_size)<0) {
1002 			fprintf(stderr,
1003 				"%s: Can't create special file %s",
1004 				arg0, Spath);
1005 			because();
1006 			return;
1007 		}
1008 		printf("Special file %s created\n", Spath);
1009 		break;
1010 #ifdef S_IFLNK
1011 	case S_IFLNK:
1012 		if (interact && !update) {
1013 			printf("Install %s -> %s", Spath, Slnkpth);
1014 			if (!ask('n')) { order(ORDER_CANCEL); return; }
1015 		}
1016 		if (symlink(Slnkpth, Spath) < 0) {
1017 			fprintf(stderr, "%s: Can't create symlink %s",
1018 				arg0, Spath);
1019 			because();
1020 			return;
1021 		}
1022 		printf("%s %s -> %s\n",
1023 			forced_update ? "Updated:  " : "Installed:",
1024 			Spath, Slnkpth);
1025 		break;
1026 #endif
1027 	case S_IFREG:
1028 		if (interact && !update) {
1029 			printf("Install %s", Spath);
1030 			if (!ask('n')) { order(ORDER_CANCEL); return; }
1031 		}
1032 		order(CAT);
1033 		if (answer() != DATA) return;
1034 
1035 		busy= 1;
1036 		if ((f= creat(Spath, backup ? 0644 : Sst.st_mode&07777)) < 0) {
1037 			busy= 0;
1038 			fprintf(stderr, "%s: Can't create %s", arg0, Spath);
1039 			because();
1040 		}
1041 
1042 		while ((n= receive(buf, sizeof(buf)))>0) {
1043 			if (f >= 0 && write(f, buf, n) != n) {
1044 				fprintf(stderr, "%s: Write error on %s",
1045 					arg0, Spath);
1046 				because();
1047 				close(f); f= -1;
1048 			}
1049 		}
1050 		if (n < 0) {
1051 			fprintf(stderr, "%s: Slave read err on %s\n",
1052 				arg0, Spath);
1053 		}
1054 		if (f >= 0) close(f);
1055 		if (n < 0 || f < 0) { makeold(); busy= 0; return; }
1056 		busy= 0;
1057 		printf("%s %s\n",
1058 			forced_update ?
1059 				Sst.st_mtime < st.st_mtime ? "Restored: " :
1060 					"Updated:  " :
1061 				"Installed:",
1062 			Spath
1063 		);
1064 		break;
1065 	default:
1066 		fprintf(stderr,
1067 			"%s: Won't add file with strange mode %05o: %s\n",
1068 			arg0, Sst.st_mode, Spath);
1069 		order(ORDER_CANCEL);
1070 		return;
1071 	}
1072 	setmodes(1);
1073 }
1074 
1075 static int delete(int update)
1076 /* Delete path. */
1077 {
1078 	int forced_update= force && update;
1079 
1080 	if (S_ISDIR(st.st_mode)) {
1081 		if (install) return 0;
1082 		if (!force) {
1083 			printf("Delete dir %s", path);
1084 			if (!ask('n')) return 0;
1085 		}
1086 		if (!removedir(path)) { ex= 1; return 0; }
1087 		if (!forced_update) printf("Directory %s deleted.\n", path);
1088 		return 1;
1089 	}
1090 
1091 	if (install && !update) return 0;
1092 
1093 	if (!force) {
1094 		printf("Delete %s", path);
1095 		if (!ask((interact && !update) ? 'n' : 'y')) return 0;
1096 	}
1097 
1098 	if (unlink(path)<0) {
1099 		fprintf(stderr, "Can't delete %s", path);
1100 		because();
1101 		return 0;
1102 	}
1103 	cancellink();
1104 	if (!forced_update) printf("Deleted:   %s\n", path);
1105 	return 1;
1106 }
1107 
1108 static int different()
1109 /* Return true iff path and Spath are different. */
1110 {
1111 	if (! ( (linkpath == nil && Slinkpath == nil)
1112 		|| (linkpath != nil && Slinkpath != nil
1113 			&& strcmp(linkpath, Slinkpath) == 0)
1114 	)) {
1115 		linkpath= Slinkpath;
1116 		return 1;
1117 	}
1118 
1119 	if ((st.st_mode & S_IFMT) != (Sst.st_mode & S_IFMT)) return 1;
1120 
1121 	switch (st.st_mode & S_IFMT) {
1122 	case S_IFDIR:
1123 		return 0;
1124 	case S_IFBLK:
1125 	case S_IFCHR:
1126 		return st.st_rdev != Sst.st_rdev;
1127 	case S_IFREG:
1128 		if (install) return Sst.st_mtime > st.st_mtime;
1129 		return st.st_size != Sst.st_size
1130 			|| st.st_mtime != Sst.st_mtime;
1131 	case S_IFIFO:	return 0;
1132 #ifdef S_IFLNK
1133 	case S_IFLNK:	return strcmp(lnkpth, Slnkpth) != 0;
1134 #endif
1135 	default:	return 1;
1136 	}
1137 }
1138 
1139 static void compare()
1140 /* See if path and Spath are same. */
1141 {
1142 	if (different()) {
1143 		if (!force) {
1144 			printf("%sing %s (delete + add)\n",
1145 				Sst.st_mtime < st.st_mtime ? "Restor" : "Updat",
1146 				path);
1147 		}
1148 		if (delete(1)) add(1);
1149 	} else {
1150 		if (!install) setmodes(0);
1151 
1152 		if (S_ISDIR(st.st_mode)) {
1153 			order(ENTER);
1154 			enter();
1155 		}
1156 	}
1157 }
1158 
1159 static int done= 0, Sdone= 0;
1160 
1161 static enum action { ADD, COMPARE, DELETE } action()
1162 /* Look at path's of master and slave, compare them alphabetically to see
1163  * who is ahead of who, then tell what is to be done.
1164  */
1165 {
1166 	int c;
1167 	char *Sp, *p;
1168 
1169 	if (done) return ADD;		/* Slave still has names. */
1170 	if (Sdone) return DELETE;	/* Master has too many names. */
1171 
1172 	/* Compare paths.  Let "a/a" come before "a.a". */
1173 	Sp= Spath;
1174 	p= path;
1175 	while (*Sp == *p && *Sp != 0) { Sp++; p++; }
1176 	if (*Sp == '/') return ADD;
1177 	if (*p == '/') return DELETE;
1178 	return (c= strcmp(Sp, p)) == 0 ? COMPARE : c < 0 ? ADD : DELETE;
1179 }
1180 
1181 static void master()
1182 /* Synchronise file tree to that of its slave. */
1183 {
1184 	enum action a= COMPARE;	/* Trick first advances. */
1185 
1186 	umask(backup ? 0022 : 0000);
1187 
1188 	signal(SIGPIPE, SIG_IGN);
1189 	signal(SIGHUP, bail_out);
1190 	signal(SIGINT, bail_out);
1191 	signal(SIGTERM, bail_out);
1192 
1193 	while (!done || !Sdone) {
1194 		if (!Sdone && (a == ADD || a == COMPARE)) {
1195 			/* Slave advances. */
1196 			order(ADVANCE);
1197 			switch (answer()) {
1198 			case PATH:
1199 				Slinkpath= nil;
1200 				receive(Spath, sizeof(Spath));
1201 				recstat(&Sst);
1202 				break;
1203 			case LINK:
1204 				receive(Slnkpth, sizeof(Slnkpth));
1205 				Slinkpath= Slnkpth;
1206 				receive(Spath, sizeof(Spath));
1207 				recstat(&Sst);
1208 				break;
1209 			case SYMLINK:
1210 				Slinkpath= nil;
1211 				receive(Slnkpth, sizeof(Slnkpth));
1212 				receive(Spath, sizeof(Spath));
1213 				recstat(&Sst);
1214 				break;
1215 			case DONE:
1216 				Sdone= 1;
1217 				break;
1218 			default:
1219 				fprintf(stderr,
1220 					"%s: Strange answer from slave.\n",
1221 					arg0);
1222 				exit(1);
1223 			}
1224 		}
1225 		if (!done && (a == COMPARE || a == DELETE)) {
1226 			/* Master advances. */
1227 			if (!advance()) done= 1;
1228 		}
1229 
1230 		if (done && Sdone) break;
1231 
1232 		switch (a= action()) {
1233 		case ADD:	/* Spath exists, path doesn't, add? */
1234 			add(0);
1235 			break;
1236 		case COMPARE:	/* They both exist, are they the same? */
1237 			compare();
1238 			break;
1239 		case DELETE:	/* path exists, Spath doesn't, delete? */
1240 			delete(0);
1241 		}
1242 		fflush(stdout);	/* Don't keep user in suspense. */
1243 	}
1244 	order(ex == 0 ? DIE : DIE_BAD);
1245 }
1246 
1247 static void mediator()
1248 /* Sits at the local machine and passes orders from master to slave, both
1249  * on remote machines.  Only diagnostics and questions are handled.
1250  */
1251 {
1252 	enum orders req;
1253 
1254 	for (;;) {
1255 		switch (req= request()) {
1256 		case DIE_BAD:
1257 			ex= 1;
1258 			/*FALL THROUGH*/
1259 		case DIE:
1260 			order(DIE);
1261 			return;
1262 		case POSITIVE:
1263 			order(ask('y') ? PASS_YES : PASS_NO);
1264 			break;
1265 		case NEGATIVE:
1266 			order(ask('n') ? PASS_YES : PASS_NO);
1267 			break;
1268 		default:
1269 			order(req);
1270 		}
1271 	}
1272 }
1273 
1274 #define P_EXIT		1	/* Make sure process doesn't return. */
1275 #define P_SHADOW	2	/* Always use exec on 68000. */
1276 
1277 static void startprocess(void (*proc)(), char *machine, char *path,
1278 	int p_flags)
1279 {
1280 	char *argv[10], **argp= argv;
1281 	char flags[10], *pfl= flags;
1282 
1283 	if (machine != nil) {
1284 		char *u= machine, *m;
1285 
1286 		*argp++ = "rsh";
1287 		if ((m= strchr(machine, '@')) != nil) {
1288 			*m++ = 0;
1289 			*argp++ = "-l";
1290 			*argp++ = u;
1291 			machine= m;
1292 		}
1293 		*argp++ = machine;
1294 	} else
1295 	/* Without this check it would run like a pig on an non MMU 68000: */
1296 	if (!(USE_SHADOWING && p_flags & P_SHADOW)) {
1297 		if (chdir(path) < 0) {
1298 			if (proc != master || errno != ENOENT
1299 						|| mkdir(path, 0700) < 0)
1300 				perrx(path);
1301 			if (chdir(path) < 0) perrx(path);
1302 			printf("Destination directory %s created\n", path);
1303 		}
1304 		isvisible(path);
1305 		isbackup(proc == slave);
1306 		(*proc)();
1307 		if (p_flags & P_EXIT) exit(ex);
1308 		return;
1309 	}
1310 	*argp++ = SYNCNAME;
1311 	*pfl++ = '-';
1312 	if (interact) *pfl++ = 'i';
1313 	if (install) *pfl++ = 'u';
1314 	if (force) *pfl++ = 'f';
1315 	*pfl= 0;
1316 	*argp++ = flags;
1317 	*argp++ = proc == slave ? SLAVENAME : MASTERNAME;
1318 	*argp++ = path;
1319 	*argp++ = nil;
1320 #ifdef DEBUG
1321 	fprintf(stderr, "execlp(");
1322 	for (argp= argv; *argp != nil; argp++) fprintf(stderr, "%s, ", *argp);
1323 	fprintf(stderr, "nil);\n");
1324 #endif
1325 	execvp(argv[0], argv);
1326 	perrx(argv[0]);
1327 }
1328 
1329 void splitcolon(char *path, char **amach, char **adir)
1330 {
1331 	char *dir= path;
1332 
1333 	for (;;) {
1334 		if (*dir == ':') {
1335 			*dir++ = 0;
1336 			*amach= path;
1337 			*adir= dir;
1338 			break;
1339 		}
1340 		if (*dir == 0 || *dir == '/') {
1341 			*amach= nil;
1342 			*adir= path;
1343 			break;
1344 		}
1345 		dir++;
1346 	}
1347 }
1348 
1349 static void Usage()
1350 {
1351 	fprintf(stderr,
1352 	    "Usage: %s [-iuf] [[user@]machine:]dir1 [[user@]machine:]dir2\n",
1353 		arg0);
1354 	exit(1);
1355 }
1356 
1357 int main(int argc, char **argv)
1358 {
1359 	char *s_mach, *s_dir;
1360 	char *m_mach, *m_dir;
1361 	int m2s[2], s2m[2], m2m[2];
1362 	int s_pid= 0, m_pid= 0;
1363 	int r;
1364 
1365 	if ((arg0= strrchr(argv[0], '/')) == nil) arg0= argv[0]; else arg0++;
1366 
1367 	while (argc>1 && argv[1][0] == '-') {
1368 		char *f= argv[1]+1;
1369 
1370 		while (*f != 0) {
1371 			switch (*f++) {
1372 			case 'i':	interact= 1; break;
1373 			case 'u':	install= 1; break;
1374 			case 'f':	force= 1; break;
1375 			default:	Usage();
1376 			}
1377 		}
1378 		argc--;
1379 		argv++;
1380 	}
1381 
1382 	if (argc != 3) Usage();
1383 
1384 	if (strcmp(argv[1], SLAVENAME) == 0) {
1385 		arg0= "Slave";
1386 		splitcolon(argv[2], &s_mach, &s_dir);
1387 		startprocess(slave, s_mach, s_dir, P_EXIT);
1388 	} else
1389 	if (strcmp(argv[1], MASTERNAME) == 0) {
1390 		arg0= "Master";
1391 		splitcolon(argv[2], &m_mach, &m_dir);
1392 		startprocess(master, m_mach, m_dir, P_EXIT);
1393 	}
1394 
1395 	splitcolon(argv[1], &s_mach, &s_dir);
1396 	splitcolon(argv[2], &m_mach, &m_dir);
1397 
1398 	/* How difficult can plumbing be? */
1399 	if (pipe(m2s) < 0 || pipe(s2m) < 0) perrx("pipe()");
1400 
1401 	if (m_mach == nil) {
1402 		/* synctree [machine:]dir1 dir2 */
1403 		switch (s_pid= fork()) {
1404 		case -1:
1405 			perrx("fork()");
1406 		case 0:
1407 			dup2(m2s[0], 0); close(m2s[0]); close(m2s[1]);
1408 			dup2(s2m[1], 1); close(s2m[0]); close(s2m[1]);
1409 			arg0= "Slave";
1410 			startprocess(slave, s_mach, s_dir, P_EXIT|P_SHADOW);
1411 		}
1412 		chan[0]= s2m[0]; close(s2m[1]);
1413 		chan[1]= m2s[1]; close(m2s[0]);
1414 		startprocess(master, m_mach, m_dir, 0);
1415 	} else
1416 	if (s_mach == nil) {
1417 		/* synctree dir1 machine:dir2 */
1418 		switch (m_pid= fork()) {
1419 		case -1:
1420 			perrx("fork()");
1421 		case 0:
1422 			dup2(s2m[0], 0); close(s2m[0]); close(s2m[1]);
1423 			dup2(m2s[1], 1); close(m2s[0]); close(m2s[1]);
1424 			arg0= "Master";
1425 			startprocess(master, m_mach, m_dir, P_EXIT|P_SHADOW);
1426 		}
1427 		chan[0]= m2s[0]; close(m2s[1]);
1428 		chan[1]= s2m[1]; close(s2m[0]);
1429 		startprocess(slave, s_mach, s_dir, 0);
1430 	} else {
1431 		/* synctree machine1:dir1 machine2:dir2 */
1432 		if (pipe(m2m) < 0) perrx(pipe);
1433 
1434 		switch (s_pid= fork()) {
1435 		case -1:
1436 			perrx("fork()");
1437 		case 0:
1438 			dup2(m2s[0], 0); close(m2s[0]); close(m2s[1]);
1439 			dup2(s2m[1], 1); close(s2m[0]); close(s2m[1]);
1440 			close(m2m[0]); close(m2m[1]);
1441 			arg0= "Slave";
1442 			startprocess(slave, s_mach, s_dir, P_EXIT|P_SHADOW);
1443 		}
1444 
1445 		switch (m_pid= fork()) {
1446 		case -1:
1447 			perrx("fork()");
1448 		case 0:
1449 			dup2(s2m[0], 0); close(s2m[0]); close(s2m[1]);
1450 			close(m2s[0]); close(m2s[1]);
1451 			dup2(m2m[1], 1); close(m2m[0]); close(m2m[1]);
1452 			arg0= "Master";
1453 			startprocess(master, m_mach, m_dir, P_EXIT|P_SHADOW);
1454 		}
1455 		close(s2m[0]); close(s2m[1]);
1456 		chan[0]= m2m[0]; close(m2m[1]);
1457 		chan[1]= m2s[1]; close(m2s[0]);
1458 		mediator();
1459 	}
1460 	close(chan[0]);
1461 	close(chan[1]);
1462 
1463 	alarm(15); /* Don't wait(2) forever. */
1464 
1465 	while (s_pid != 0 || m_pid != 0) {
1466 		if ((r= wait((int *) nil)) < 0) perrx("wait()");
1467 		if (r == s_pid) s_pid= 0;
1468 		if (r == m_pid) m_pid= 0;
1469 	}
1470 	exit(ex);
1471 }
1472