xref: /minix/minix/commands/remsync/remsync.c (revision ebfedea0)
1 /*	remsync 1.5 - remotely synchronize file trees	Author: Kees J. Bot
2  *								10 Jun 1994
3  */
4 #define nil 0
5 #include <sys/types.h>
6 #include <sys/stat.h>
7 #include <stdio.h>
8 #include <stdlib.h>
9 #include <stdarg.h>
10 #include <string.h>
11 #include <dirent.h>
12 #include <unistd.h>
13 #include <fcntl.h>
14 #include <errno.h>
15 #include <limits.h>
16 #include <time.h>
17 #include <utime.h>
18 
19 #define arraysize(a)	(sizeof(a) / sizeof((a)[0]))
20 #define arraylimit(a)	((a) + arraysize(a))
21 
22 #ifndef major
23 #define major(dev)	((int) ((dev) >> 8))
24 #define minor(dev)	((int) ((dev) & 0xFF))
25 #endif
26 
27 #ifndef S_ISLNK
28 /* There were no symlinks in medieval times. */
29 #define S_ISLNK(mode)			(0)
30 #define lstat				stat
31 #define symlink(path1, path2)		(errno= ENOSYS, -1)
32 #define readlink(path, buf, len)	(errno= ENOSYS, -1)
33 #endif
34 
35 int sflag;		/* Make state file. */
36 int dflag;		/* Make list of differences. */
37 int uflag;		/* Only update files with newer versions. */
38 int xflag;		/* Do not cross device boundaries. */
39 int Dflag;		/* Debug: Readable differences, no file contents. */
40 int vflag;		/* Verbose. */
41 
42 #define NO_DEVICE	(-1)
43 dev_t xdev= NO_DEVICE;	/* The device that you should stay within. */
44 
45 int excode= 0;		/* Exit(excode); */
46 
47 #define BASE_INDENT	2	/* State file basic indent. */
48 
49 void report(const char *label)
50 {
51 	fprintf(stderr, "remsync: %s: %s\n", label, strerror(errno));
52 	excode= 1;
53 }
54 
55 void fatal(const char *label)
56 {
57 	report(label);
58 	exit(1);
59 }
60 
61 void *allocate(void *mem, size_t size)
62 {
63 	if ((mem= mem == nil ? malloc(size) : realloc(mem, size)) == nil) {
64 		fprintf(stderr, "remsync: Out of memory: %s\n",
65 			strerror(errno));
66 		exit(1);
67 	}
68 	return mem;
69 }
70 
71 void deallocate(void *mem)
72 {
73 	if (mem != nil) free(mem);
74 }
75 
76 /* One needs to slowly forget two sets of objects: for the code that reads
77  * the state file, and for the code that traverses trees.
78  */
79 int keep;
80 #define KEEP_STATE	0
81 #define KEEP_TRAVERSE	1
82 
83 void forget(void *mem)
84 /* Some objects must be deleted in time, but not just yet. */
85 {
86 	static void *death_row[2][50];
87 	static void **dp[2]= { death_row[0], death_row[1] };
88 
89 	deallocate(*dp[keep]);
90 	*dp[keep]++= mem;
91 	if (dp[keep] == arraylimit(death_row[keep])) dp[keep]= death_row[keep];
92 }
93 
94 char *copystr(const char *s)
95 {
96 	char *c= allocate(nil, (strlen(s) + 1) * sizeof(c[0]));
97 	strcpy(c, s);
98 	return c;
99 }
100 
101 typedef struct pathname {
102 	char		*path;	/* The actual pathname. */
103 	size_t		idx;	/* Index for the terminating null byte. */
104 	size_t		lim;	/* Actual length of the path array. */
105 } pathname_t;
106 
107 void path_init(pathname_t *pp)
108 /* Initialize a pathname to the null string. */
109 {
110 	pp->path= allocate(nil, (pp->lim= 16) * sizeof(pp->path[0]));
111 	pp->path[pp->idx= 0]= 0;
112 }
113 
114 void path_add(pathname_t *pp, const char *name)
115 /* Add a component to a pathname. */
116 {
117 	size_t lim;
118 	char *p;
119 	int slash;
120 
121 	lim= pp->idx + strlen(name) + 2;
122 
123 	if (lim > pp->lim) {
124 		pp->lim= lim + lim/2;	/* add an extra 50% growing space. */
125 		pp->path= allocate(pp->path, pp->lim * sizeof(pp->path[0]));
126 	}
127 
128 	p= pp->path + pp->idx;
129 	slash= (pp->idx > 0);
130 	if (pp->idx == 1 && p[-1] == '/') p--;
131 
132 	while (*name != 0) {
133 		if (*name == '/') {
134 			slash= 1;
135 		} else {
136 			if (slash) { *p++ = '/'; slash= 0; }
137 			*p++= *name;
138 		}
139 		name++;
140 	}
141 	if (slash && p == pp->path) *p++= '/';
142 	*p = 0;
143 	pp->idx= p - pp->path;
144 }
145 
146 void path_trunc(pathname_t *pp, size_t didx)
147 /* Delete part of a pathname to a remembered length. */
148 {
149 	pp->path[pp->idx= didx]= 0;
150 }
151 
152 #if kept_for_comments_only
153 
154 const char *path_name(const pathname_t *pp)
155 /* Return the actual name as a char array. */
156 {
157 	return pp->path;
158 }
159 
160 size_t path_length(const pathname_t *pp)
161 /* The length of the pathname. */
162 {
163 	return pp->idx;
164 }
165 
166 void path_drop(pathname_t *pp)
167 /* Release the storage occupied by the pathname. */
168 {
169 	free(pp->path);
170 }
171 #endif
172 
173 #define path_name(pp)		((const char *) (pp)->path)
174 #define path_length(pp)		((pp)->idx)
175 #define path_drop(pp)		free((void *) (pp)->path)
176 
177 typedef struct namelist {	/* Obviously a list of names. */
178 	struct namelist	*next;
179 	char		*name;
180 } namelist_t;
181 
182 char *rdlink(const char *link, off_t size)
183 /* Look where "link" points. */
184 {
185 	static char *path= nil;
186 	static size_t len= 0;
187 	size_t n;
188 
189 	if (len <= size) {
190 		path= allocate(path, (len= size * 2) * sizeof(path[0]));
191 	}
192 	if ((n= readlink(link, path, len)) == -1) return nil;
193 	path[n]= 0;
194 	return path;
195 }
196 
197 void sort(namelist_t **anl)
198 /* A stable mergesort disguised as line noise.  Must be called like this:
199  *	if (L!=nil && L->next!=nil) sort(&L);
200  */
201 {
202 	/* static */ namelist_t *nl1, **mid;  /* Need not be local */
203 	namelist_t *nl2;
204 
205 	nl1= *(mid= &(*anl)->next);
206 	do {
207 		if ((nl1= nl1->next) == nil) break;
208 		mid= &(*mid)->next;
209 	} while ((nl1= nl1->next) != nil);
210 
211 	nl2= *mid;
212 	*mid= nil;
213 
214 	if ((*anl)->next != nil) sort(anl);
215 	if (nl2->next != nil) sort(&nl2);
216 
217 	nl1= *anl;
218 	for (;;) {
219 		if (strcmp(nl1->name, nl2->name)<=0) {
220 			if ((nl1= *(anl= &nl1->next)) == nil) {
221 				*anl= nl2;
222 				break;
223 			}
224 		} else {
225 			*anl= nl2;
226 			nl2= *(anl= &nl2->next);
227 			*anl= nl1;
228 			if (nl2 == nil) break;
229 		}
230 	}
231 }
232 
233 namelist_t *collect(const char *dir)
234 /* Return a sorted list of directory entries.  Returns null with errno != 0
235  * on error.
236  */
237 {
238 	namelist_t *names, **pn= &names;
239 	DIR *dp;
240 	struct dirent *entry;
241 
242 	if ((dp= opendir(dir)) == nil) return nil;
243 
244 	while ((entry= readdir(dp)) != nil) {
245 		if (entry->d_name[0] == '.'
246 			&& (entry->d_name[1] == 0
247 				|| (entry->d_name[1] == '.'
248 					&& entry->d_name[2] == 0))) {
249 			continue;
250 		}
251 		*pn= allocate(nil, sizeof(**pn));
252 		(*pn)->name= copystr(entry->d_name);
253 		pn= &(*pn)->next;
254 	}
255 	closedir(dp);
256 	*pn= nil;
257 	errno= 0;
258 	if (names != nil && names->next != nil) sort(&names);
259 	return names;
260 }
261 
262 char *pop_name(namelist_t **names)
263 /* Return one name of a name list. */
264 {
265 	char *name;
266 	namelist_t *junk;
267 
268 	junk= *names;
269 	*names= junk->next;
270 	name= junk->name;
271 	deallocate(junk);
272 	forget(name);
273 	return name;
274 }
275 
276 typedef enum filetype {		/* The files we know about. */
277 	F_DIR,
278 	F_FILE,
279 	F_BLK,
280 	F_CHR,
281 	F_PIPE,
282 	F_LINK
283 } filetype_t;
284 
285 typedef struct entry {		/* One file. */
286 	int		depth;		/* Depth in directory tree. */
287 	const char	*name;		/* Name of entry. */
288 	const char	*path;		/* Path name. */
289 	int		ignore;		/* Ignore this entry (errno number.) */
290 	unsigned long	fake_ino;	/* Fake inode number for hard links. */
291 	int		linked;		/* Is the file hard linked? */
292 	int		lastlink;	/* Is it the last link? */
293 	char		*link;		/* Where a (sym)link points to. */
294 	filetype_t	type;
295 	mode_t		mode;		/* Not unlike those in struct stat. */
296 	uid_t		uid;
297 	gid_t		gid;
298 	off_t		size;
299 	time_t		mtime;
300 	dev_t		rdev;
301 } entry_t;
302 
303 void linked(entry_t *entry, struct stat *stp)
304 /* Return an "inode number" if a file could have links (link count > 1).
305  * Also return a path to the first link if you see the file again.
306  */
307 {
308 	static unsigned long new_fake_ino= 0;
309 	static struct links {
310 		struct links	*next;
311 		char		*path;
312 		ino_t		ino;
313 		dev_t		dev;
314 		nlink_t		nlink;
315 		unsigned long	fake_ino;
316 	} *links[1024];
317 	struct links **plp, *lp;
318 
319 	entry->linked= entry->lastlink= 0;
320 	entry->fake_ino= 0;
321 	entry->link= nil;
322 
323 	if (S_ISDIR(stp->st_mode) || stp->st_nlink < 2) return;
324 
325 	plp= &links[stp->st_ino % arraysize(links)];
326 	while ((lp= *plp) != nil && (lp->ino != stp->st_ino
327 				|| lp->dev != stp->st_dev)) plp= &lp->next;
328 
329 	if (lp == nil) {
330 		/* New file, store it with a new fake inode number. */
331 		*plp= lp= allocate(nil, sizeof(*lp));
332 		lp->next= nil;
333 		lp->path= copystr(entry->path);
334 		lp->ino= stp->st_ino;
335 		lp->dev= stp->st_dev;
336 		lp->nlink= stp->st_nlink;
337 		lp->fake_ino= ++new_fake_ino;
338 	} else {
339 		entry->link= lp->path;
340 		entry->linked= 1;
341 	}
342 	entry->fake_ino= lp->fake_ino;
343 
344 	if (--lp->nlink == 0) {
345 		/* No need to remember this one, no more links coming. */
346 		*plp= lp->next;
347 		forget(lp->path);
348 		deallocate(lp);
349 		entry->lastlink= 1;
350 	}
351 }
352 
353 char *tree;		/* Tree to work on. */
354 FILE *statefp;		/* State file. */
355 char *state_file;
356 FILE *difffp;		/* File of differences. */
357 char *diff_file;
358 
359 entry_t *traverse(void)
360 /* Get one name from the directory tree. */
361 {
362 	static int depth;
363 	static pathname_t path;
364 	static entry_t entry;
365 	static namelist_t **entries;
366 	static size_t *trunc;
367 	static size_t deep;
368 	static namelist_t *newentries;
369 	struct stat st;
370 
371 recurse:
372 	keep= KEEP_TRAVERSE;
373 
374 	if (deep == 0) {
375 		/* Initialize for the root of the tree. */
376 		path_init(&path);
377 		path_add(&path, tree);
378 		entries= allocate(nil, 1 * sizeof(entries[0]));
379 		entries[0]= allocate(nil, sizeof(*entries[0]));
380 		entries[0]->next= nil;
381 		entries[0]->name= copystr("/");
382 		trunc= allocate(nil, 1 * sizeof(trunc[0]));
383 		trunc[0]= path_length(&path);
384 		deep= 1;
385 	} else
386 	if (newentries != nil) {
387 		/* Last entry was a directory, need to go down. */
388 		if (entry.ignore) {
389 			/* Ouch, it is to be ignored! */
390 			while (newentries != nil) (void) pop_name(&newentries);
391 			goto recurse;
392 		}
393 		if (++depth == deep) {
394 			deep++;
395 			entries= allocate(entries, deep * sizeof(entries[0]));
396 			trunc= allocate(trunc, deep * sizeof(trunc[0]));
397 		}
398 		entries[depth]= newentries;
399 		newentries= nil;
400 		trunc[depth]= path_length(&path);
401 	} else {
402 		/* Pop up out of emptied directories. */
403 		while (entries[depth] == nil) {
404 			if (depth == 0) return nil;	/* Back at the root. */
405 
406 			/* Go up one level. */
407 			depth--;
408 		}
409 	}
410 	entry.name= pop_name(&entries[depth]);
411 	path_trunc(&path, trunc[depth]);
412 	path_add(&path, entry.name);
413 	if (depth == 0) {
414 		entry.path= "/";
415 	} else {
416 		entry.path= path_name(&path) + trunc[0];
417 		if (entry.path[0] == '/') entry.path++;
418 	}
419 	entry.depth= depth;
420 	entry.ignore= 0;
421 
422 	if (lstat(path_name(&path), &st) < 0) {
423 		if (depth == 0 || errno != ENOENT) {
424 			/* Something wrong with this entry, complain about
425 			 * it and ignore it further.
426 			 */
427 			entry.ignore= errno;
428 			report(path_name(&path));
429 			return &entry;
430 		} else {
431 			/* Entry strangely nonexistent; simply continue. */
432 			goto recurse;
433 		}
434 	}
435 
436 	/* Don't cross mountpoints if -x is set. */
437 	if (xflag) {
438 		if (xdev == NO_DEVICE) xdev= st.st_dev;
439 		if (st.st_dev != xdev) {
440 			/* Ignore the mountpoint. */
441 			entry.ignore= EXDEV;
442 			return &entry;
443 		}
444 	}
445 
446 	entry.mode= st.st_mode & 07777;
447 	entry.uid= st.st_uid;
448 	entry.gid= st.st_gid;
449 	entry.size= st.st_size;
450 	entry.mtime= st.st_mtime;
451 	entry.rdev= st.st_rdev;
452 
453 	linked(&entry, &st);
454 
455 	if (S_ISDIR(st.st_mode)) {
456 		/* A directory. */
457 		entry.type= F_DIR;
458 
459 		/* Gather directory entries for the next traverse. */
460 		if ((newentries= collect(path_name(&path))) == nil
461 							&& errno != 0) {
462 			entry.ignore= errno;
463 			report(path_name(&path));
464 		}
465 	} else
466 	if (S_ISREG(st.st_mode)) {
467 		/* A plain file. */
468 		entry.type= F_FILE;
469 	} else
470 	if (S_ISBLK(st.st_mode)) {
471 		/* A block special file. */
472 		entry.type= F_BLK;
473 	} else
474 	if (S_ISCHR(st.st_mode)) {
475 		/* A character special file. */
476 		entry.type= F_CHR;
477 	} else
478 	if (S_ISFIFO(st.st_mode)) {
479 		/* A named pipe. */
480 		entry.type= F_PIPE;
481 	} else
482 	if (S_ISLNK(st.st_mode)) {
483 		/* A symbolic link. */
484 		entry.type= F_LINK;
485 		if ((entry.link= rdlink(path_name(&path), st.st_size)) == nil) {
486 			entry.ignore= errno;
487 			report(path_name(&path));
488 		}
489 	} else {
490 		/* Unknown type of file. */
491 		entry.ignore= EINVAL;
492 	}
493 	return &entry;
494 }
495 
496 void checkstate(void)
497 {
498 	if (ferror(statefp)) fatal(state_file);
499 }
500 
501 void indent(int depth)
502 /* Provide indentation to show directory depth. */
503 {
504 	int n= BASE_INDENT * (depth - 1);
505 
506 	while (n >= 8) {
507 		if (putc('\t', statefp) == EOF) checkstate();
508 		n-= 8;
509 	}
510 	while (n > 0) {
511 		if (putc(' ', statefp) == EOF) checkstate();
512 		n--;
513 	}
514 }
515 
516 int print_name(FILE *fp, const char *name)
517 /* Encode a name. */
518 {
519 	const char *p;
520 	int c;
521 
522 	for (p= name; (c= (unsigned char) *p) != 0; p++) {
523 		if (c <= ' ' || c == '\\') {
524 			fprintf(fp, "\\%03o", c);
525 			if (ferror(fp)) return 0;
526 		} else {
527 			if (putc(c, fp) == EOF) return 0;
528 		}
529 	}
530 	return 1;
531 }
532 
533 void mkstatefile(void)
534 /* Make a state file out of the directory tree. */
535 {
536 	entry_t *entry;
537 
538 	while ((entry= traverse()) != nil) {
539 		indent(entry->depth);
540 		if (!print_name(statefp, entry->name)) checkstate();
541 
542 		if (entry->ignore) {
543 			fprintf(statefp, "\tignore (%s)\n",
544 				strerror(entry->ignore));
545 			checkstate();
546 			continue;
547 		}
548 
549 		switch (entry->type) {
550 		case F_DIR:
551 			fprintf(statefp, "\td%03o %u %u",
552 				(unsigned) entry->mode,
553 				(unsigned) entry->uid, (unsigned) entry->gid);
554 			break;
555 		case F_FILE:
556 			fprintf(statefp, "\t%03o %u %u %lu %lu",
557 				(unsigned) entry->mode,
558 				(unsigned) entry->uid, (unsigned) entry->gid,
559 				(unsigned long) entry->size,
560 				(unsigned long) entry->mtime);
561 			break;
562 		case F_BLK:
563 		case F_CHR:
564 			fprintf(statefp, "\t%c%03o %u %u %x",
565 				entry->type == F_BLK ? 'b' : 'c',
566 				(unsigned) entry->mode,
567 				(unsigned) entry->uid, (unsigned) entry->gid,
568 				(unsigned) entry->rdev);
569 			break;
570 		case F_PIPE:
571 			fprintf(statefp, "\tp%03o %u %u",
572 				(unsigned) entry->mode,
573 				(unsigned) entry->uid, (unsigned) entry->gid);
574 			break;
575 		case F_LINK:
576 			fprintf(statefp, "\t-> ");
577 			checkstate();
578 			(void) print_name(statefp, entry->link);
579 			break;
580 		}
581 		checkstate();
582 		if (entry->fake_ino != 0)
583 			fprintf(statefp, " %lu", entry->fake_ino);
584 		if (entry->lastlink)
585 			fprintf(statefp, " last");
586 		if (fputc('\n', statefp) == EOF) checkstate();
587 	}
588 	fflush(statefp);
589 	checkstate();
590 }
591 
592 char *read1line(FILE *fp)
593 /* Read one line from a file.  Return null on EOF or error. */
594 {
595 	static char *line;
596 	static size_t len;
597 	size_t idx;
598 	int c;
599 
600 	if (len == 0) line= allocate(nil, (len= 16) * sizeof(line[0]));
601 
602 	idx= 0;
603 	while ((c= getc(fp)) != EOF && c != '\n') {
604 		if (c < '\t') {
605 			/* Control characters are not possible. */
606 			fprintf(stderr,
607 				"remsync: control character in data file!\n");
608 			exit(1);
609 		}
610 		line[idx++]= c;
611 		if (idx == len) {
612 			line= allocate(line, (len*= 2) * sizeof(line[0]));
613 		}
614 	}
615 	if (c == EOF) {
616 		if (ferror(fp)) return nil;
617 		if (idx == 0) return nil;
618 	}
619 	line[idx]= 0;
620 	return line;
621 }
622 
623 void getword(char **pline, char **parg, size_t *plen)
624 /* Get one word from a line, interpret octal escapes. */
625 {
626 	char *line= *pline;
627 	char *arg= *parg;
628 	size_t len= *plen;
629 	int i;
630 	int c;
631 	size_t idx;
632 
633 	idx= 0;
634 	while ((c= *line) != 0 && c != ' ' && c != '\t') {
635 		line++;
636 		if (c == '\\') {
637 			c= 0;
638 			for (i= 0; i < 3; i++) {
639 				if ((unsigned) (*line - '0') >= 010) break;
640 				c= (c << 3) | (*line - '0');
641 				line++;
642 			}
643 		}
644 		arg[idx++]= c;
645 		if (idx == len) arg= allocate(arg, (len*= 2) * sizeof(arg[0]));
646 	}
647 	arg[idx]= 0;
648 	*pline= line;
649 	*parg= arg;
650 	*plen= len;
651 }
652 
653 void splitline(char *line, char ***pargv, size_t *pargc)
654 /* Split a line into an array of words. */
655 {
656 	static char **argv;
657 	static size_t *lenv;
658 	static size_t len;
659 	size_t idx;
660 
661 	idx= 0;
662 	for (;;) {
663 		while (*line == ' ' || *line == '\t') line++;
664 
665 		if (*line == 0) break;
666 
667 		if (idx == len) {
668 			len++;
669 			argv= allocate(argv, len * sizeof(argv[0]));
670 			lenv= allocate(lenv, len * sizeof(lenv[0]));
671 			argv[idx]= allocate(nil, 16 * sizeof(argv[idx][0]));
672 			lenv[idx]= 16;
673 		}
674 		getword(&line, &argv[idx], &lenv[idx]);
675 		idx++;
676 	}
677 	*pargv= argv;
678 	*pargc= idx;
679 }
680 
681 int getattributes(entry_t *entry, int argc, char **argv)
682 /* Convert state or difference file info into file attributes. */
683 {
684 	int i;
685 	int attr;
686 #define A_MODE1		0x01	/* Some of these attributes follow the name */
687 #define A_MODE		0x02
688 #define	A_OWNER		0x04
689 #define A_SIZETIME	0x08
690 #define A_DEV		0x10
691 #define A_LINK		0x20
692 
693 	switch (argv[0][0]) {
694 	case 'd':
695 		/* Directory. */
696 		entry->type= F_DIR;
697 		attr= A_MODE1 | A_OWNER;
698 		break;
699 	case 'b':
700 		/* Block device. */
701 		entry->type= F_BLK;
702 		attr= A_MODE1 | A_OWNER | A_DEV;
703 		break;
704 	case 'c':
705 		/* Character device. */
706 		entry->type= F_CHR;
707 		attr= A_MODE1 | A_OWNER | A_DEV;
708 		break;
709 	case 'p':
710 		/* Named pipe. */
711 		entry->type= F_PIPE;
712 		attr= A_MODE1 | A_OWNER;
713 		break;
714 	case '-':
715 		/* Symlink. */
716 		entry->type= F_LINK;
717 		attr= A_LINK;
718 		break;
719 	default:
720 		/* Normal file. */
721 		entry->type= F_FILE;
722 		attr= A_MODE | A_OWNER | A_SIZETIME;
723 	}
724 
725 	if (attr & (A_MODE | A_MODE1)) {
726 		entry->mode= strtoul(argv[0] + (attr & A_MODE1), nil, 010);
727 	}
728 	i= 1;
729 	if (attr & A_OWNER) {
730 		if (i + 2 > argc) return 0;
731 		entry->uid= strtoul(argv[i++], nil, 10);
732 		entry->gid= strtoul(argv[i++], nil, 10);
733 	}
734 	if (attr & A_SIZETIME) {
735 		if (i + 2 > argc) return 0;
736 		entry->size= strtoul(argv[i++], nil, 10);
737 		entry->mtime= strtoul(argv[i++], nil, 10);
738 	}
739 	if (attr & A_DEV) {
740 		if (i + 1 > argc) return 0;
741 		entry->rdev= strtoul(argv[i++], nil, 0x10);
742 	}
743 	if (attr & A_LINK) {
744 		if (i + 1 > argc) return 0;
745 		entry->link= argv[i++];
746 	}
747 	entry->linked= entry->lastlink= 0;
748 	if (i < argc) {
749 		/* It has a fake inode number, so it is a hard link. */
750 		static struct links {	/* List of hard links. */
751 			struct links	*next;
752 			unsigned long	fake_ino;
753 			char		*path;
754 		} *links[1024];
755 		struct links **plp, *lp;
756 		unsigned long fake_ino;
757 
758 		fake_ino= strtoul(argv[i++], nil, 10);
759 
760 		plp= &links[fake_ino % arraysize(links)];
761 		while ((lp= *plp) != nil && lp->fake_ino != fake_ino)
762 			plp= &lp->next;
763 
764 		if (lp == nil) {
765 			/* New link. */
766 			*plp= lp= allocate(nil, sizeof(*lp));
767 			lp->next= nil;
768 			lp->fake_ino= fake_ino;
769 			lp->path= copystr(entry->path);
770 		} else {
771 			/* Linked to. */
772 			entry->link= lp->path;
773 			entry->linked= 1;
774 		}
775 
776 		if (i < argc) {
777 			if (strcmp(argv[i++], "last") != 0) return 0;
778 
779 			/* Last hard link of a file. */
780 			forget(lp->path);
781 			*plp= lp->next;
782 			deallocate(lp);
783 			entry->lastlink= 1;
784 		}
785 	}
786 	if (i != argc) return 0;
787 	return 1;
788 }
789 
790 void state_syntax(off_t line)
791 {
792 	fprintf(stderr, "remsync: %s: syntax error on line %lu\n",
793 		state_file, (unsigned long) line);
794 	exit(1);
795 }
796 
797 entry_t *readstate(void)
798 /* Read one entry from the state file. */
799 {
800 	static entry_t entry;
801 	static pathname_t path;
802 	static size_t *trunc;
803 	static size_t trunc_len;
804 	static int base_indent;
805 	char *line;
806 	char **argv;
807 	size_t argc;
808 	static off_t lineno;
809 	int indent, depth;
810 
811 recurse:
812 	keep= KEEP_STATE;
813 
814 	if (feof(statefp) || (line= read1line(statefp)) == nil) {
815 		checkstate();
816 		return nil;
817 	}
818 	lineno++;
819 
820 	/* How far is this entry indented? */
821 	indent= 0;
822 	while (*line != 0) {
823 		if (*line == ' ') indent++;
824 		else
825 		if (*line == '\t') indent= (indent + 8) & ~7;
826 		else
827 			break;
828 		line++;
829 	}
830 	if (indent > 0 && base_indent == 0) base_indent= indent;
831 	depth= (base_indent == 0 ? 0 : indent / base_indent) + 1;
832 
833 	if (entry.ignore && depth > entry.depth) {
834 		/* If the old directory is ignored, then so are its entries. */
835 		goto recurse;
836 	}
837 	entry.depth= depth;
838 
839 	splitline(line, &argv, &argc);
840 	if (argc < 2) state_syntax(lineno);
841 
842 	if (trunc == nil) {
843 		/* The root of the tree, initialize path. */
844 		if (argv[0][0] != '/') state_syntax(lineno);
845 		path_init(&path);
846 		path_add(&path, "/");
847 		trunc= allocate(nil, (trunc_len= 16) * sizeof(trunc[0]));
848 
849 		/* The root has depth 0. */
850 		entry.depth= 0;
851 		trunc[0]= 0;
852 	} else {
853 		if (entry.depth > trunc_len) {
854 			trunc= allocate(trunc,
855 					(trunc_len*= 2) * sizeof(trunc[0]));
856 		}
857 		path_trunc(&path, trunc[entry.depth - 1]);
858 		path_add(&path, argv[0]);
859 		trunc[entry.depth]= path_length(&path);
860 	}
861 
862 	entry.path= path_name(&path);
863 	entry.name= argv[0];
864 	entry.link= nil;
865 	if ((entry.ignore= strcmp(argv[1], "ignore") == 0)) {
866 		return &entry;
867 	}
868 	if (!getattributes(&entry, argc - 1, argv + 1)) state_syntax(lineno);
869 	return &entry;
870 }
871 
872 void checkdiff(void)
873 {
874 	if (ferror(difffp)) fatal(diff_file);
875 }
876 
877 enum { DELETE, REPLACE, COPY, SIMILAR, EQUAL, ADD }
878 compare(entry_t *remote, entry_t *local)
879 /* Compare the local and remote entries and tell what need to be done. */
880 {
881 	int cmp;
882 
883 	/* Surplus entries? */
884 	if (local == nil) return DELETE;
885 	if (remote == nil) return ADD;
886 
887 	/* Extra directory entries? */
888 	if (remote->depth > local->depth) return DELETE;
889 	if (local->depth > remote->depth) return ADD;
890 
891 	/* Compare names. */
892 	cmp= strcmp(remote->name, local->name);
893 	if (cmp < 0) return DELETE;
894 	if (cmp > 0) return ADD;
895 
896 	/* The files have the same name.  Ignore one, ignore the other. */
897 	if (remote->ignore || local->ignore) {
898 		remote->ignore= local->ignore= 1;
899 		return EQUAL;
900 	}
901 
902 	/* Reasons for replacement? */
903 	if (remote->type != local->type) return REPLACE;
904 
905 	/* Should be hard linked to the same file. */
906 	if (remote->linked || local->linked) {
907 		if (!remote->linked || !local->linked) return REPLACE;
908 		if (strcmp(remote->link, local->link) != 0) return REPLACE;
909 	}
910 
911 	switch (remote->type) {
912 	case F_FILE:
913 		if (uflag) {
914 			if (remote->mtime < local->mtime) return COPY;
915 		} else {
916 			if (remote->size != local->size
917 					|| remote->mtime != local->mtime)
918 				return COPY;
919 		}
920 		goto check_modes;
921 	case F_BLK:
922 	case F_CHR:
923 		if (remote->rdev != local->rdev) return REPLACE;
924 		goto check_modes;
925 	case F_DIR:
926 	case F_PIPE:
927 	check_modes:
928 		if (remote->mode != local->mode
929 			|| remote->uid != local->uid
930 			|| remote->gid != local->gid) return SIMILAR;
931 		break;
932 	case F_LINK:
933 		if (strcmp(remote->link, local->link) != 0) return REPLACE;
934 		break;
935 	}
936 	return EQUAL;
937 }
938 
939 void delete(entry_t *old)
940 /* Emit an instruction to remove an entry. */
941 {
942 	if (old->ignore) return;
943 	if (uflag) return;
944 
945 	fprintf(difffp, "rm ");
946 	checkdiff();
947 	if (!print_name(difffp, old->path)) checkdiff();
948 	if (putc('\n', difffp) == EOF) checkdiff();
949 	if (vflag) fprintf(stderr, "rm %s\n", old->path);
950 }
951 
952 void change_modes(entry_t *old, entry_t *new)
953 /* Emit an instruction to change the attributes of an entry. */
954 {
955 	if (new->ignore) return;
956 
957 	fprintf(difffp, "chmod ");
958 	checkdiff();
959 	if (!print_name(difffp, new->path)) checkdiff();
960 	fprintf(difffp, " %03o %u %u\n",
961 		(unsigned) new->mode,
962 		(unsigned) new->uid, (unsigned) new->gid);
963 	checkdiff();
964 	if (vflag && old->mode != new->mode) {
965 		fprintf(stderr, "chmod %s %03o %u %u\n",
966 			new->path,
967 			(unsigned) new->mode,
968 			(unsigned) new->uid, (unsigned) new->gid);
969 	}
970 }
971 
972 int cat(int f, off_t size)
973 /* Include the contents of a file in the differences file. */
974 {
975 	ssize_t n;
976 	unsigned char buf[1024 << sizeof(int)];
977 	unsigned char *p;
978 	int c;
979 
980 	if (Dflag) return 1;	/* Debug: Don't need the file contents. */
981 
982 	while ((n= read(f, buf, sizeof(buf))) > 0) {
983 		p= buf;
984 		do {
985 			if (size == 0) {
986 				/* File suddenly larger. */
987 				errno= EINVAL;
988 				return 0;
989 			}
990 			c= *p++;
991 			if (putc(c, difffp) == EOF) checkdiff();
992 			size--;
993 		} while (--n != 0);
994 	}
995 	if (size > 0) {
996 		int err= errno;
997 
998 		/* File somehow shrunk, pad it out. */
999 		do {
1000 			if (putc(0, difffp) == EOF) checkdiff();
1001 		} while (--size != 0);
1002 		errno= n == 0 ? EINVAL : err;
1003 		n= -1;
1004 	}
1005 	return n == 0;
1006 }
1007 
1008 void add(entry_t *old, entry_t *new)
1009 /* Emit an instruction to add an entry. */
1010 {
1011 	pathname_t file;
1012 	int f;
1013 
1014 	if (new->ignore) return;
1015 
1016 	if (new->linked) {
1017 		/* This file is to be a hard link to an existing file. */
1018 		fprintf(difffp, "ln ");
1019 		checkdiff();
1020 		if (!print_name(difffp, new->link)) checkdiff();
1021 		if (fputc(' ', difffp) == EOF) checkdiff();
1022 		if (!print_name(difffp, new->path)) checkdiff();
1023 		if (fputc('\n', difffp) == EOF) checkdiff();
1024 		if (vflag) {
1025 			fprintf(stderr, "ln %s %s\n", new->link, new->path);
1026 		}
1027 		return;
1028 	}
1029 
1030 	/* Add some other type of file. */
1031 	fprintf(difffp, "add ");
1032 	checkdiff();
1033 	if (!print_name(difffp, new->path)) checkdiff();
1034 
1035 	switch (new->type) {
1036 	case F_DIR:
1037 		fprintf(difffp, " d%03o %u %u\n",
1038 			(unsigned) new->mode,
1039 			(unsigned) new->uid, (unsigned) new->gid);
1040 		if (vflag) fprintf(stderr, "mkdir %s\n", new->path);
1041 		break;
1042 	case F_FILE:
1043 		path_init(&file);
1044 		path_add(&file, tree);
1045 		path_add(&file, new->path);
1046 		if ((f= open(path_name(&file), O_RDONLY)) < 0) {
1047 			report(path_name(&file));
1048 			path_drop(&file);
1049 			fprintf(difffp, " ignore\n");
1050 			break;
1051 		}
1052 		fprintf(difffp, " %03o %u %u %lu %lu\n",
1053 			(unsigned) new->mode,
1054 			(unsigned) new->uid, (unsigned) new->gid,
1055 			(unsigned long) new->size,
1056 			(unsigned long) new->mtime);
1057 		checkdiff();
1058 		if (!cat(f, new->size)) {
1059 			int err= errno;
1060 			report(path_name(&file));
1061 			fprintf(difffp, "old ");
1062 			checkdiff();
1063 			print_name(difffp, err == EINVAL
1064 				? "File changed when copied" : strerror(err));
1065 			fputc('\n', difffp);
1066 			checkdiff();
1067 		} else {
1068 			if (vflag) {
1069 				fprintf(stderr, "%s %s\n",
1070 					old == nil ? "add" :
1071 						old->mtime > new->mtime ?
1072 							"restore" : "update",
1073 					new->path);
1074 			}
1075 		}
1076 		close(f);
1077 		path_drop(&file);
1078 		break;
1079 	case F_BLK:
1080 	case F_CHR:
1081 		fprintf(difffp, " %c%03o %u %u %lx\n",
1082 			new->type == F_BLK ? 'b' : 'c',
1083 			(unsigned) new->mode,
1084 			(unsigned) new->uid, (unsigned) new->gid,
1085 			(unsigned long) new->rdev);
1086 		if (vflag) fprintf(stderr, "mknod %s\n", new->path);
1087 		break;
1088 	case F_PIPE:
1089 		fprintf(difffp, " p%03o %u %u\n",
1090 			(unsigned) new->mode,
1091 			(unsigned) new->uid, (unsigned) new->gid);
1092 		if (vflag) fprintf(stderr, "mkfifo %s\n", new->path);
1093 		break;
1094 	case F_LINK:
1095 		fprintf(difffp, " -> ");
1096 		checkdiff();
1097 		(void) print_name(difffp, new->link);
1098 		checkdiff();
1099 		fputc('\n', difffp);
1100 		if (vflag) {
1101 			fprintf(stderr, "ln -s %s %s\n", new->link, new->path);
1102 		}
1103 		break;
1104 	}
1105 	checkdiff();
1106 }
1107 
1108 void mkdifferences(void)
1109 {
1110 	entry_t *remote;
1111 	entry_t *local;
1112 
1113 	remote= readstate();
1114 	local= traverse();
1115 
1116 	while (remote != nil || local != nil) {
1117 		switch (compare(remote, local)) {
1118 		case DELETE:
1119 			/* Remove the remote file. */
1120 			delete(remote);
1121 			remote->ignore= 1;
1122 			remote= readstate();
1123 			break;
1124 		case REPLACE:
1125 			/* Replace the remote file with the local one. */
1126 			if (remote->type == F_FILE && local->type == F_FILE
1127 							&& !local->linked) {
1128 				/* Don't overwrite, remove first. */
1129 				delete(remote);
1130 			}
1131 			/*FALL THROUGH*/
1132 		case COPY:
1133 			/* Overwrite the remote file with the local one. */
1134 			add(remote, local);
1135 			remote->ignore= 1;
1136 			goto skip2;
1137 		case SIMILAR:
1138 			/* About the same, but the attributes need changing. */
1139 			change_modes(remote, local);
1140 			goto skip2;
1141 		case EQUAL:
1142 		skip2:
1143 			/* Skip two files. */
1144 			remote= readstate();
1145 			local= traverse();
1146 			break;
1147 		case ADD:
1148 			/* Add the local file. */
1149 			add(nil, local);
1150 			local= traverse();
1151 			break;
1152 		}
1153 	}
1154 	fprintf(difffp, "end\n");
1155 	fflush(difffp);
1156 	checkdiff();
1157 }
1158 
1159 void apply_remove(pathname_t *pp)
1160 /* Remove an obsolete file. */
1161 {
1162 	struct stat st;
1163 
1164 	if (lstat(path_name(pp), &st) < 0) {
1165 		if (errno != ENOENT) report(path_name(pp));
1166 		return;
1167 	}
1168 
1169 	if (S_ISDIR(st.st_mode)) {
1170 		/* Recursively delete directories. */
1171 		size_t len;
1172 		namelist_t *entries;
1173 
1174 		if ((entries= collect(path_name(pp))) == nil && errno != 0) {
1175 			report(path_name(pp));
1176 			return;
1177 		}
1178 		len= path_length(pp);
1179 
1180 		while (entries != nil) {
1181 			path_add(pp, pop_name(&entries));
1182 			apply_remove(pp);
1183 			path_trunc(pp, len);
1184 		}
1185 		if (rmdir(path_name(pp)) < 0) {
1186 			report(path_name(pp));
1187 			return;
1188 		}
1189 		if (vflag) fprintf(stderr, "rmdir %s\n", path_name(pp));
1190 	} else {
1191 		/* Some other type of file. */
1192 		if (unlink(path_name(pp)) < 0) {
1193 			report(path_name(pp));
1194 			return;
1195 		}
1196 		if (vflag) fprintf(stderr, "rm %s\n", path_name(pp));
1197 	}
1198 }
1199 
1200 void apply_mkold(const char *file, const char *err)
1201 /* Make a file very old.  (An error occurred when it was added.) */
1202 {
1203 	struct utimbuf utb;
1204 
1205 	utb.actime= utb.modtime= 0;
1206 	if (utime(file, &utb) < 0) {
1207 		report(file);
1208 		return;
1209 	}
1210 	fprintf(stderr, "made %s look old", file);
1211 	if (err != nil)
1212 		fprintf(stderr, " due to a remote problem: %s\n", err);
1213 	else
1214 		fprintf(stderr, "\n");
1215 }
1216 
1217 void apply_chmod(const char *file, mode_t mode, uid_t uid, gid_t gid, int talk)
1218 /* Change mode and ownership. */
1219 {
1220 	struct stat st;
1221 
1222 	if (lstat(file, &st) < 0) {
1223 		report(file);
1224 		return;
1225 	}
1226 	if ((st.st_mode & 07777) != mode) {
1227 		if (chmod(file, mode) < 0) {
1228 			report(file);
1229 			return;
1230 		}
1231 		if (vflag && talk) {
1232 			fprintf(stderr, "chmod %03o %s\n",
1233 						(unsigned) mode, file);
1234 		}
1235 	}
1236 	if (st.st_uid != uid || st.st_gid != gid) {
1237 		if (chown(file, uid, gid) < 0) {
1238 			if (errno != EPERM) report(file);
1239 			return;
1240 		}
1241 		if (vflag && talk) {
1242 			fprintf(stderr, "chown %u:%u %s\n",
1243 				(unsigned) uid, (unsigned) gid, file);
1244 		}
1245 	}
1246 }
1247 
1248 void apply_add(pathname_t *pp, entry_t *entry)
1249 /* Add or replace a file. */
1250 {
1251 	const char *file;
1252 	off_t size;
1253 	int f;
1254 	unsigned char buf[1024 << sizeof(int)];
1255 	unsigned char *p;
1256 	int c;
1257 	int dirty;
1258 	struct stat st;
1259 	struct utimbuf utb;
1260 
1261 	if (entry->ignore) return;
1262 
1263 	if (lstat(path_name(pp), &st) >= 0 && (entry->type != F_FILE
1264 					|| !S_ISREG(st.st_mode))) {
1265 		apply_remove(pp);
1266 	}
1267 
1268 	file= path_name(pp);
1269 
1270 	switch (entry->type) {
1271 	case F_DIR:
1272 		if (mkdir(file, entry->mode) < 0) {
1273 			report(file);
1274 			return;
1275 		}
1276 		if (vflag) fprintf(stderr, "mkdir %s\n", file);
1277 		break;
1278 	case F_FILE:
1279 		size= entry->size;
1280 
1281 		f= -1;
1282 		st.st_mode= 0;
1283 		if (lstat(file, &st) < 0 || S_ISREG(st.st_mode)) {
1284 			f= open(file, O_WRONLY | O_CREAT | O_TRUNC,
1285 						entry->mode);
1286 			if (f < 0) {
1287 				(void) chmod(file, entry->mode | 0200);
1288 				f= open(file, O_WRONLY | O_CREAT | O_TRUNC,
1289 						entry->mode);
1290 			}
1291 			if (f < 0) {
1292 				(void) unlink(file);
1293 				f= open(file, O_WRONLY | O_CREAT | O_TRUNC,
1294 						entry->mode);
1295 			}
1296 			if (f < 0) report(file);
1297 		}
1298 		dirty= (f >= 0);
1299 		p= buf;
1300 		while (size > 0 && (c= getc(difffp)) != EOF) {
1301 			size--;
1302 			*p++= c;
1303 			if (p == arraylimit(buf) || size == 0) {
1304 				if (f >= 0 && write(f, buf, p - buf) < 0) {
1305 					report(file);
1306 					close(f);
1307 					f= -1;
1308 				}
1309 				p= buf;
1310 			}
1311 		}
1312 		if (size > 0) {
1313 			if (ferror(difffp)) report(diff_file);
1314 			if (feof(difffp)) {
1315 				fprintf(stderr, "remspec: %s: premature EOF\n",
1316 					diff_file);
1317 			}
1318 			if (dirty) apply_mkold(file, nil);
1319 			exit(1);
1320 		}
1321 		if (f < 0) {
1322 			if (dirty) apply_mkold(file, nil);
1323 			return;
1324 		}
1325 		close(f);
1326 		if (vflag) {
1327 			fprintf(stderr, st.st_mode == 0 ? "add %s\n"
1328 				: entry->mtime >= st.st_mtime
1329 					? "update %s\n" : "restore %s\n", file);
1330 		}
1331 		utb.actime= time(nil);
1332 		utb.modtime= entry->mtime;
1333 		if (utime(file, &utb) < 0) report(file);
1334 		break;
1335 	case F_BLK:
1336 		if (mknod(file, S_IFBLK | entry->mode, entry->rdev) < 0) {
1337 			report(file);
1338 			return;
1339 		}
1340 		if (vflag) {
1341 			fprintf(stderr, "mknod %s b %d %d\n", file,
1342 				major(entry->rdev), minor(entry->rdev));
1343 		}
1344 		break;
1345 	case F_CHR:
1346 		if (mknod(file, S_IFCHR | entry->mode, entry->rdev) < 0) {
1347 			report(file);
1348 			return;
1349 		}
1350 		if (vflag) {
1351 			fprintf(stderr, "mknod %s c %d %d\n", file,
1352 				major(entry->rdev), minor(entry->rdev));
1353 		}
1354 		break;
1355 	case F_PIPE:
1356 		if (mknod(file, S_IFIFO | entry->mode, 0) < 0) {
1357 			report(file);
1358 			return;
1359 		}
1360 		if (vflag) fprintf(stderr, "mknod %s p\n", file);
1361 		break;
1362 	case F_LINK:
1363 		if (symlink(entry->link, file) < 0) {
1364 			report(file);
1365 			return;
1366 		}
1367 		if (vflag) fprintf(stderr, "ln -s %s %s\n", entry->link, file);
1368 		return;
1369 	}
1370 	apply_chmod(file, entry->mode, entry->uid, entry->gid, 0);
1371 }
1372 
1373 void apply_link(const char *file, pathname_t *pp)
1374 /* Hard link *pp to file. */
1375 {
1376 	struct stat st1, st2;
1377 
1378 	if (lstat(file, &st1) < 0) {
1379 		report(file);
1380 		return;
1381 	}
1382 	if (lstat(path_name(pp), &st2) >= 0) {
1383 		if (st1.st_ino == st2.st_ino && st1.st_dev == st2.st_dev)
1384 			return;
1385 		apply_remove(pp);
1386 		if (lstat(path_name(pp), &st2) >= 0) return;
1387 	}
1388 	if (link(file, path_name(pp)) < 0) {
1389 		fprintf(stderr, "remsync: ln %s %s: %s\n", file, path_name(pp),
1390 			strerror(errno));
1391 		excode= 1;
1392 		return;
1393 	}
1394 	if (vflag) fprintf(stderr, "ln %s %s\n", file, path_name(pp));
1395 }
1396 
1397 void diff_syntax(const char *line)
1398 {
1399 	fprintf(stderr, "remsync: %s: syntax error on this line: %s\n",
1400 		diff_file, line);
1401 	exit(1);
1402 }
1403 
1404 void apply_differences(void)
1405 /* Update a tree to a list of differences derived from a remote tree. */
1406 {
1407 	char *line;
1408 	char **argv;
1409 	size_t argc;
1410 	pathname_t path, link;
1411 	size_t trunc;
1412 
1413 	path_init(&path);
1414 	path_init(&link);
1415 	path_add(&path, tree);
1416 	path_add(&link, tree);
1417 	trunc= path_length(&path);
1418 
1419 	while (!feof(difffp) && (line= read1line(difffp)) != nil) {
1420 		splitline(line, &argv, &argc);
1421 		if (argc == 0) diff_syntax(line);
1422 
1423 		path_trunc(&path, trunc);
1424 
1425 		if (strcmp(argv[0], "add") == 0) {
1426 			entry_t entry;
1427 
1428 			if (argc < 3) diff_syntax(line);
1429 			path_add(&path, argv[1]);
1430 			entry.ignore= (strcmp(argv[2], "ignore") == 0);
1431 			if (!entry.ignore && !getattributes(&entry,
1432 							argc - 2, argv + 2))
1433 				diff_syntax(line);
1434 			apply_add(&path, &entry);
1435 		} else
1436 		if (strcmp(argv[0], "rm") == 0) {
1437 			if (argc != 2) diff_syntax(line);
1438 			path_add(&path, argv[1]);
1439 			apply_remove(&path);
1440 		} else
1441 		if (strcmp(argv[0], "ln") == 0) {
1442 			if (argc != 3) diff_syntax(line);
1443 			path_trunc(&link, trunc);
1444 			path_add(&link, argv[1]);
1445 			path_add(&path, argv[2]);
1446 			apply_link(path_name(&link), &path);
1447 		} else
1448 		if (strcmp(argv[0], "chmod") == 0) {
1449 			if (argc != 5) diff_syntax(line);
1450 			path_add(&path, argv[1]);
1451 			apply_chmod(path_name(&path),
1452 				strtoul(argv[2], nil, 010),
1453 				strtoul(argv[3], nil, 10),
1454 				strtoul(argv[4], nil, 10),
1455 				1);
1456 		} else
1457 		if (strcmp(argv[0], "old") == 0) {
1458 			if (argc != 3) diff_syntax(line);
1459 			path_add(&path, argv[1]);
1460 			apply_mkold(path_name(&path), argv[2]);
1461 		} else
1462 		if (strcmp(argv[0], "end") == 0) {
1463 			if (argc != 1) diff_syntax(line);
1464 			break;
1465 		} else {
1466 			diff_syntax(line);
1467 		}
1468 	}
1469 	checkdiff();
1470 }
1471 
1472 void usage(void)
1473 {
1474     fprintf(stderr, "Usage: remsync -sxv tree [state-file]\n");
1475     fprintf(stderr, "       remsync -duxvD tree [state-file [diff-file]]\n");
1476     fprintf(stderr, "       remsync [-xv] tree [diff-file]\n");
1477     exit(1);
1478 }
1479 
1480 int main(int argc, char **argv)
1481 {
1482 	int i;
1483 
1484 	for (i= 1; i < argc && argv[i][0] == '-'; i++) {
1485 		char *p= argv[i] + 1;
1486 
1487 		if (p[0] == '-' && p[1] == 0) { i++; break; }
1488 
1489 		while (*p != 0) {
1490 			switch (*p++) {
1491 			case 's':	sflag= 1; break;
1492 			case 'd':	dflag= 1; break;
1493 			case 'u':	uflag= 1; break;
1494 			case 'x':	xflag= 1; break;
1495 			case 'D':	Dflag= 1; break;
1496 			case 'v':	vflag= 1; break;
1497 			default:	usage();
1498 			}
1499 		}
1500 	}
1501 	if (sflag && dflag) usage();
1502 	if (sflag && uflag) usage();
1503 	if (!sflag && !dflag && uflag) usage();
1504 	if (!dflag && Dflag) usage();
1505 
1506 	if (i == argc) usage();
1507 	tree= argv[i++];
1508 
1509 	if (sflag) {
1510 		/* Make a state file. */
1511 		state_file= i < argc ? argv[i++] : "-";
1512 		if (i != argc) usage();
1513 
1514 		statefp= stdout;
1515 		if (strcmp(state_file, "-") != 0) {
1516 			if ((statefp= fopen(state_file, "w")) == nil)
1517 				fatal(state_file);
1518 		}
1519 		mkstatefile();
1520 	} else
1521 	if (dflag) {
1522 		/* Make a file of differences. */
1523 		state_file= i < argc ? argv[i++] : "-";
1524 
1525 		diff_file= i < argc ? argv[i++] : "-";
1526 		if (i != argc) usage();
1527 
1528 		statefp= stdin;
1529 		if (strcmp(state_file, "-") != 0) {
1530 			if ((statefp= fopen(state_file, "r")) == nil)
1531 				fatal(state_file);
1532 		}
1533 
1534 		difffp= stdout;
1535 		if (strcmp(diff_file, "-") != 0) {
1536 			if ((difffp= fopen(diff_file, "w")) == nil)
1537 				fatal(diff_file);
1538 		}
1539 		mkdifferences();
1540 	} else {
1541 		/* Apply a file of differences. */
1542 		diff_file= i < argc ? argv[i++] : "-";
1543 		if (i != argc) usage();
1544 
1545 		difffp= stdin;
1546 		if (strcmp(diff_file, "-") != 0) {
1547 			if ((difffp= fopen(diff_file, "r")) == nil)
1548 				fatal(diff_file);
1549 		}
1550 		apply_differences();
1551 	}
1552 	exit(excode);
1553 }
1554