xref: /minix3/minix/commands/remsync/remsync.c (revision d0055759dd8892194db7fce6acc5085d5c9aeaee)
1433d6423SLionel Sambuc /*	remsync 1.5 - remotely synchronize file trees	Author: Kees J. Bot
2433d6423SLionel Sambuc  *								10 Jun 1994
3433d6423SLionel Sambuc  */
4433d6423SLionel Sambuc #define nil 0
5433d6423SLionel Sambuc #include <sys/types.h>
6433d6423SLionel Sambuc #include <sys/stat.h>
7433d6423SLionel Sambuc #include <stdio.h>
8433d6423SLionel Sambuc #include <stdlib.h>
9433d6423SLionel Sambuc #include <stdarg.h>
10433d6423SLionel Sambuc #include <string.h>
11433d6423SLionel Sambuc #include <dirent.h>
12433d6423SLionel Sambuc #include <unistd.h>
13433d6423SLionel Sambuc #include <fcntl.h>
14433d6423SLionel Sambuc #include <errno.h>
15433d6423SLionel Sambuc #include <limits.h>
16433d6423SLionel Sambuc #include <time.h>
17433d6423SLionel Sambuc #include <utime.h>
18433d6423SLionel Sambuc 
19433d6423SLionel Sambuc #define arraysize(a)	(sizeof(a) / sizeof((a)[0]))
20433d6423SLionel Sambuc #define arraylimit(a)	((a) + arraysize(a))
21433d6423SLionel Sambuc 
22433d6423SLionel Sambuc #ifndef major
23433d6423SLionel Sambuc #define major(dev)	((int) ((dev) >> 8))
24433d6423SLionel Sambuc #define minor(dev)	((int) ((dev) & 0xFF))
25433d6423SLionel Sambuc #endif
26433d6423SLionel Sambuc 
27433d6423SLionel Sambuc #ifndef S_ISLNK
28433d6423SLionel Sambuc /* There were no symlinks in medieval times. */
29433d6423SLionel Sambuc #define S_ISLNK(mode)			(0)
30433d6423SLionel Sambuc #define lstat				stat
31433d6423SLionel Sambuc #define symlink(path1, path2)		(errno= ENOSYS, -1)
32433d6423SLionel Sambuc #define readlink(path, buf, len)	(errno= ENOSYS, -1)
33433d6423SLionel Sambuc #endif
34433d6423SLionel Sambuc 
35433d6423SLionel Sambuc int sflag;		/* Make state file. */
36433d6423SLionel Sambuc int dflag;		/* Make list of differences. */
37433d6423SLionel Sambuc int uflag;		/* Only update files with newer versions. */
38433d6423SLionel Sambuc int xflag;		/* Do not cross device boundaries. */
39433d6423SLionel Sambuc int Dflag;		/* Debug: Readable differences, no file contents. */
40433d6423SLionel Sambuc int vflag;		/* Verbose. */
41433d6423SLionel Sambuc 
42433d6423SLionel Sambuc #define NO_DEVICE	(-1)
43433d6423SLionel Sambuc dev_t xdev= NO_DEVICE;	/* The device that you should stay within. */
44433d6423SLionel Sambuc 
45433d6423SLionel Sambuc int excode= 0;		/* Exit(excode); */
46433d6423SLionel Sambuc 
47433d6423SLionel Sambuc #define BASE_INDENT	2	/* State file basic indent. */
48433d6423SLionel Sambuc 
report(const char * label)49433d6423SLionel Sambuc void report(const char *label)
50433d6423SLionel Sambuc {
51433d6423SLionel Sambuc 	fprintf(stderr, "remsync: %s: %s\n", label, strerror(errno));
52433d6423SLionel Sambuc 	excode= 1;
53433d6423SLionel Sambuc }
54433d6423SLionel Sambuc 
fatal(const char * label)55433d6423SLionel Sambuc void fatal(const char *label)
56433d6423SLionel Sambuc {
57433d6423SLionel Sambuc 	report(label);
58433d6423SLionel Sambuc 	exit(1);
59433d6423SLionel Sambuc }
60433d6423SLionel Sambuc 
allocate(void * mem,size_t size)61433d6423SLionel Sambuc void *allocate(void *mem, size_t size)
62433d6423SLionel Sambuc {
63433d6423SLionel Sambuc 	if ((mem= mem == nil ? malloc(size) : realloc(mem, size)) == nil) {
64433d6423SLionel Sambuc 		fprintf(stderr, "remsync: Out of memory: %s\n",
65433d6423SLionel Sambuc 			strerror(errno));
66433d6423SLionel Sambuc 		exit(1);
67433d6423SLionel Sambuc 	}
68433d6423SLionel Sambuc 	return mem;
69433d6423SLionel Sambuc }
70433d6423SLionel Sambuc 
deallocate(void * mem)71433d6423SLionel Sambuc void deallocate(void *mem)
72433d6423SLionel Sambuc {
73433d6423SLionel Sambuc 	if (mem != nil) free(mem);
74433d6423SLionel Sambuc }
75433d6423SLionel Sambuc 
76433d6423SLionel Sambuc /* One needs to slowly forget two sets of objects: for the code that reads
77433d6423SLionel Sambuc  * the state file, and for the code that traverses trees.
78433d6423SLionel Sambuc  */
79433d6423SLionel Sambuc int keep;
80433d6423SLionel Sambuc #define KEEP_STATE	0
81433d6423SLionel Sambuc #define KEEP_TRAVERSE	1
82433d6423SLionel Sambuc 
forget(void * mem)83433d6423SLionel Sambuc void forget(void *mem)
84433d6423SLionel Sambuc /* Some objects must be deleted in time, but not just yet. */
85433d6423SLionel Sambuc {
86433d6423SLionel Sambuc 	static void *death_row[2][50];
87433d6423SLionel Sambuc 	static void **dp[2]= { death_row[0], death_row[1] };
88433d6423SLionel Sambuc 
89433d6423SLionel Sambuc 	deallocate(*dp[keep]);
90433d6423SLionel Sambuc 	*dp[keep]++= mem;
91433d6423SLionel Sambuc 	if (dp[keep] == arraylimit(death_row[keep])) dp[keep]= death_row[keep];
92433d6423SLionel Sambuc }
93433d6423SLionel Sambuc 
copystr(const char * s)94433d6423SLionel Sambuc char *copystr(const char *s)
95433d6423SLionel Sambuc {
96433d6423SLionel Sambuc 	char *c= allocate(nil, (strlen(s) + 1) * sizeof(c[0]));
97433d6423SLionel Sambuc 	strcpy(c, s);
98433d6423SLionel Sambuc 	return c;
99433d6423SLionel Sambuc }
100433d6423SLionel Sambuc 
101433d6423SLionel Sambuc typedef struct pathname {
102433d6423SLionel Sambuc 	char		*path;	/* The actual pathname. */
103433d6423SLionel Sambuc 	size_t		idx;	/* Index for the terminating null byte. */
104433d6423SLionel Sambuc 	size_t		lim;	/* Actual length of the path array. */
105433d6423SLionel Sambuc } pathname_t;
106433d6423SLionel Sambuc 
path_init(pathname_t * pp)107433d6423SLionel Sambuc void path_init(pathname_t *pp)
108433d6423SLionel Sambuc /* Initialize a pathname to the null string. */
109433d6423SLionel Sambuc {
110433d6423SLionel Sambuc 	pp->path= allocate(nil, (pp->lim= 16) * sizeof(pp->path[0]));
111433d6423SLionel Sambuc 	pp->path[pp->idx= 0]= 0;
112433d6423SLionel Sambuc }
113433d6423SLionel Sambuc 
path_add(pathname_t * pp,const char * name)114433d6423SLionel Sambuc void path_add(pathname_t *pp, const char *name)
115433d6423SLionel Sambuc /* Add a component to a pathname. */
116433d6423SLionel Sambuc {
117433d6423SLionel Sambuc 	size_t lim;
118433d6423SLionel Sambuc 	char *p;
119433d6423SLionel Sambuc 	int slash;
120433d6423SLionel Sambuc 
121433d6423SLionel Sambuc 	lim= pp->idx + strlen(name) + 2;
122433d6423SLionel Sambuc 
123433d6423SLionel Sambuc 	if (lim > pp->lim) {
124433d6423SLionel Sambuc 		pp->lim= lim + lim/2;	/* add an extra 50% growing space. */
125433d6423SLionel Sambuc 		pp->path= allocate(pp->path, pp->lim * sizeof(pp->path[0]));
126433d6423SLionel Sambuc 	}
127433d6423SLionel Sambuc 
128433d6423SLionel Sambuc 	p= pp->path + pp->idx;
129433d6423SLionel Sambuc 	slash= (pp->idx > 0);
130433d6423SLionel Sambuc 	if (pp->idx == 1 && p[-1] == '/') p--;
131433d6423SLionel Sambuc 
132433d6423SLionel Sambuc 	while (*name != 0) {
133433d6423SLionel Sambuc 		if (*name == '/') {
134433d6423SLionel Sambuc 			slash= 1;
135433d6423SLionel Sambuc 		} else {
136433d6423SLionel Sambuc 			if (slash) { *p++ = '/'; slash= 0; }
137433d6423SLionel Sambuc 			*p++= *name;
138433d6423SLionel Sambuc 		}
139433d6423SLionel Sambuc 		name++;
140433d6423SLionel Sambuc 	}
141433d6423SLionel Sambuc 	if (slash && p == pp->path) *p++= '/';
142433d6423SLionel Sambuc 	*p = 0;
143433d6423SLionel Sambuc 	pp->idx= p - pp->path;
144433d6423SLionel Sambuc }
145433d6423SLionel Sambuc 
path_trunc(pathname_t * pp,size_t didx)146433d6423SLionel Sambuc void path_trunc(pathname_t *pp, size_t didx)
147433d6423SLionel Sambuc /* Delete part of a pathname to a remembered length. */
148433d6423SLionel Sambuc {
149433d6423SLionel Sambuc 	pp->path[pp->idx= didx]= 0;
150433d6423SLionel Sambuc }
151433d6423SLionel Sambuc 
152433d6423SLionel Sambuc #if kept_for_comments_only
153433d6423SLionel Sambuc 
path_name(const pathname_t * pp)154433d6423SLionel Sambuc const char *path_name(const pathname_t *pp)
155433d6423SLionel Sambuc /* Return the actual name as a char array. */
156433d6423SLionel Sambuc {
157433d6423SLionel Sambuc 	return pp->path;
158433d6423SLionel Sambuc }
159433d6423SLionel Sambuc 
path_length(const pathname_t * pp)160433d6423SLionel Sambuc size_t path_length(const pathname_t *pp)
161433d6423SLionel Sambuc /* The length of the pathname. */
162433d6423SLionel Sambuc {
163433d6423SLionel Sambuc 	return pp->idx;
164433d6423SLionel Sambuc }
165433d6423SLionel Sambuc 
path_drop(pathname_t * pp)166433d6423SLionel Sambuc void path_drop(pathname_t *pp)
167433d6423SLionel Sambuc /* Release the storage occupied by the pathname. */
168433d6423SLionel Sambuc {
169433d6423SLionel Sambuc 	free(pp->path);
170433d6423SLionel Sambuc }
171433d6423SLionel Sambuc #endif
172433d6423SLionel Sambuc 
173433d6423SLionel Sambuc #define path_name(pp)		((const char *) (pp)->path)
174433d6423SLionel Sambuc #define path_length(pp)		((pp)->idx)
175433d6423SLionel Sambuc #define path_drop(pp)		free((void *) (pp)->path)
176433d6423SLionel Sambuc 
177433d6423SLionel Sambuc typedef struct namelist {	/* Obviously a list of names. */
178433d6423SLionel Sambuc 	struct namelist	*next;
179433d6423SLionel Sambuc 	char		*name;
180433d6423SLionel Sambuc } namelist_t;
181433d6423SLionel Sambuc 
rdlink(const char * link,off_t size)182433d6423SLionel Sambuc char *rdlink(const char *link, off_t size)
183433d6423SLionel Sambuc /* Look where "link" points. */
184433d6423SLionel Sambuc {
185433d6423SLionel Sambuc 	static char *path= nil;
186433d6423SLionel Sambuc 	static size_t len= 0;
187433d6423SLionel Sambuc 	size_t n;
188433d6423SLionel Sambuc 
189433d6423SLionel Sambuc 	if (len <= size) {
190433d6423SLionel Sambuc 		path= allocate(path, (len= size * 2) * sizeof(path[0]));
191433d6423SLionel Sambuc 	}
192433d6423SLionel Sambuc 	if ((n= readlink(link, path, len)) == -1) return nil;
193433d6423SLionel Sambuc 	path[n]= 0;
194433d6423SLionel Sambuc 	return path;
195433d6423SLionel Sambuc }
196433d6423SLionel Sambuc 
sort(namelist_t ** anl)197433d6423SLionel Sambuc void sort(namelist_t **anl)
198433d6423SLionel Sambuc /* A stable mergesort disguised as line noise.  Must be called like this:
199433d6423SLionel Sambuc  *	if (L!=nil && L->next!=nil) sort(&L);
200433d6423SLionel Sambuc  */
201433d6423SLionel Sambuc {
202433d6423SLionel Sambuc 	/* static */ namelist_t *nl1, **mid;  /* Need not be local */
203433d6423SLionel Sambuc 	namelist_t *nl2;
204433d6423SLionel Sambuc 
205433d6423SLionel Sambuc 	nl1= *(mid= &(*anl)->next);
206433d6423SLionel Sambuc 	do {
207433d6423SLionel Sambuc 		if ((nl1= nl1->next) == nil) break;
208433d6423SLionel Sambuc 		mid= &(*mid)->next;
209433d6423SLionel Sambuc 	} while ((nl1= nl1->next) != nil);
210433d6423SLionel Sambuc 
211433d6423SLionel Sambuc 	nl2= *mid;
212433d6423SLionel Sambuc 	*mid= nil;
213433d6423SLionel Sambuc 
214433d6423SLionel Sambuc 	if ((*anl)->next != nil) sort(anl);
215433d6423SLionel Sambuc 	if (nl2->next != nil) sort(&nl2);
216433d6423SLionel Sambuc 
217433d6423SLionel Sambuc 	nl1= *anl;
218433d6423SLionel Sambuc 	for (;;) {
219433d6423SLionel Sambuc 		if (strcmp(nl1->name, nl2->name)<=0) {
220433d6423SLionel Sambuc 			if ((nl1= *(anl= &nl1->next)) == nil) {
221433d6423SLionel Sambuc 				*anl= nl2;
222433d6423SLionel Sambuc 				break;
223433d6423SLionel Sambuc 			}
224433d6423SLionel Sambuc 		} else {
225433d6423SLionel Sambuc 			*anl= nl2;
226433d6423SLionel Sambuc 			nl2= *(anl= &nl2->next);
227433d6423SLionel Sambuc 			*anl= nl1;
228433d6423SLionel Sambuc 			if (nl2 == nil) break;
229433d6423SLionel Sambuc 		}
230433d6423SLionel Sambuc 	}
231433d6423SLionel Sambuc }
232433d6423SLionel Sambuc 
collect(const char * dir)233433d6423SLionel Sambuc namelist_t *collect(const char *dir)
234433d6423SLionel Sambuc /* Return a sorted list of directory entries.  Returns null with errno != 0
235433d6423SLionel Sambuc  * on error.
236433d6423SLionel Sambuc  */
237433d6423SLionel Sambuc {
238433d6423SLionel Sambuc 	namelist_t *names, **pn= &names;
239433d6423SLionel Sambuc 	DIR *dp;
240433d6423SLionel Sambuc 	struct dirent *entry;
241433d6423SLionel Sambuc 
242433d6423SLionel Sambuc 	if ((dp= opendir(dir)) == nil) return nil;
243433d6423SLionel Sambuc 
244433d6423SLionel Sambuc 	while ((entry= readdir(dp)) != nil) {
245433d6423SLionel Sambuc 		if (entry->d_name[0] == '.'
246433d6423SLionel Sambuc 			&& (entry->d_name[1] == 0
247433d6423SLionel Sambuc 				|| (entry->d_name[1] == '.'
248433d6423SLionel Sambuc 					&& entry->d_name[2] == 0))) {
249433d6423SLionel Sambuc 			continue;
250433d6423SLionel Sambuc 		}
251433d6423SLionel Sambuc 		*pn= allocate(nil, sizeof(**pn));
252433d6423SLionel Sambuc 		(*pn)->name= copystr(entry->d_name);
253433d6423SLionel Sambuc 		pn= &(*pn)->next;
254433d6423SLionel Sambuc 	}
255433d6423SLionel Sambuc 	closedir(dp);
256433d6423SLionel Sambuc 	*pn= nil;
257433d6423SLionel Sambuc 	errno= 0;
258433d6423SLionel Sambuc 	if (names != nil && names->next != nil) sort(&names);
259433d6423SLionel Sambuc 	return names;
260433d6423SLionel Sambuc }
261433d6423SLionel Sambuc 
pop_name(namelist_t ** names)262433d6423SLionel Sambuc char *pop_name(namelist_t **names)
263433d6423SLionel Sambuc /* Return one name of a name list. */
264433d6423SLionel Sambuc {
265433d6423SLionel Sambuc 	char *name;
266433d6423SLionel Sambuc 	namelist_t *junk;
267433d6423SLionel Sambuc 
268433d6423SLionel Sambuc 	junk= *names;
269433d6423SLionel Sambuc 	*names= junk->next;
270433d6423SLionel Sambuc 	name= junk->name;
271433d6423SLionel Sambuc 	deallocate(junk);
272433d6423SLionel Sambuc 	forget(name);
273433d6423SLionel Sambuc 	return name;
274433d6423SLionel Sambuc }
275433d6423SLionel Sambuc 
276433d6423SLionel Sambuc typedef enum filetype {		/* The files we know about. */
277433d6423SLionel Sambuc 	F_DIR,
278433d6423SLionel Sambuc 	F_FILE,
279433d6423SLionel Sambuc 	F_BLK,
280433d6423SLionel Sambuc 	F_CHR,
281433d6423SLionel Sambuc 	F_PIPE,
282433d6423SLionel Sambuc 	F_LINK
283433d6423SLionel Sambuc } filetype_t;
284433d6423SLionel Sambuc 
285433d6423SLionel Sambuc typedef struct entry {		/* One file. */
286433d6423SLionel Sambuc 	int		depth;		/* Depth in directory tree. */
287433d6423SLionel Sambuc 	const char	*name;		/* Name of entry. */
288433d6423SLionel Sambuc 	const char	*path;		/* Path name. */
289433d6423SLionel Sambuc 	int		ignore;		/* Ignore this entry (errno number.) */
290433d6423SLionel Sambuc 	unsigned long	fake_ino;	/* Fake inode number for hard links. */
291433d6423SLionel Sambuc 	int		linked;		/* Is the file hard linked? */
292433d6423SLionel Sambuc 	int		lastlink;	/* Is it the last link? */
293433d6423SLionel Sambuc 	char		*link;		/* Where a (sym)link points to. */
294433d6423SLionel Sambuc 	filetype_t	type;
295433d6423SLionel Sambuc 	mode_t		mode;		/* Not unlike those in struct stat. */
296433d6423SLionel Sambuc 	uid_t		uid;
297433d6423SLionel Sambuc 	gid_t		gid;
298433d6423SLionel Sambuc 	off_t		size;
299433d6423SLionel Sambuc 	time_t		mtime;
300433d6423SLionel Sambuc 	dev_t		rdev;
301433d6423SLionel Sambuc } entry_t;
302433d6423SLionel Sambuc 
linked(entry_t * entry,struct stat * stp)303433d6423SLionel Sambuc void linked(entry_t *entry, struct stat *stp)
304433d6423SLionel Sambuc /* Return an "inode number" if a file could have links (link count > 1).
305433d6423SLionel Sambuc  * Also return a path to the first link if you see the file again.
306433d6423SLionel Sambuc  */
307433d6423SLionel Sambuc {
308433d6423SLionel Sambuc 	static unsigned long new_fake_ino= 0;
309433d6423SLionel Sambuc 	static struct links {
310433d6423SLionel Sambuc 		struct links	*next;
311433d6423SLionel Sambuc 		char		*path;
312433d6423SLionel Sambuc 		ino_t		ino;
313433d6423SLionel Sambuc 		dev_t		dev;
314433d6423SLionel Sambuc 		nlink_t		nlink;
315433d6423SLionel Sambuc 		unsigned long	fake_ino;
316433d6423SLionel Sambuc 	} *links[1024];
317433d6423SLionel Sambuc 	struct links **plp, *lp;
318433d6423SLionel Sambuc 
319433d6423SLionel Sambuc 	entry->linked= entry->lastlink= 0;
320433d6423SLionel Sambuc 	entry->fake_ino= 0;
321433d6423SLionel Sambuc 	entry->link= nil;
322433d6423SLionel Sambuc 
323433d6423SLionel Sambuc 	if (S_ISDIR(stp->st_mode) || stp->st_nlink < 2) return;
324433d6423SLionel Sambuc 
325433d6423SLionel Sambuc 	plp= &links[stp->st_ino % arraysize(links)];
326433d6423SLionel Sambuc 	while ((lp= *plp) != nil && (lp->ino != stp->st_ino
327433d6423SLionel Sambuc 				|| lp->dev != stp->st_dev)) plp= &lp->next;
328433d6423SLionel Sambuc 
329433d6423SLionel Sambuc 	if (lp == nil) {
330433d6423SLionel Sambuc 		/* New file, store it with a new fake inode number. */
331433d6423SLionel Sambuc 		*plp= lp= allocate(nil, sizeof(*lp));
332433d6423SLionel Sambuc 		lp->next= nil;
333433d6423SLionel Sambuc 		lp->path= copystr(entry->path);
334433d6423SLionel Sambuc 		lp->ino= stp->st_ino;
335433d6423SLionel Sambuc 		lp->dev= stp->st_dev;
336433d6423SLionel Sambuc 		lp->nlink= stp->st_nlink;
337433d6423SLionel Sambuc 		lp->fake_ino= ++new_fake_ino;
338433d6423SLionel Sambuc 	} else {
339433d6423SLionel Sambuc 		entry->link= lp->path;
340433d6423SLionel Sambuc 		entry->linked= 1;
341433d6423SLionel Sambuc 	}
342433d6423SLionel Sambuc 	entry->fake_ino= lp->fake_ino;
343433d6423SLionel Sambuc 
344433d6423SLionel Sambuc 	if (--lp->nlink == 0) {
345433d6423SLionel Sambuc 		/* No need to remember this one, no more links coming. */
346433d6423SLionel Sambuc 		*plp= lp->next;
347433d6423SLionel Sambuc 		forget(lp->path);
348433d6423SLionel Sambuc 		deallocate(lp);
349433d6423SLionel Sambuc 		entry->lastlink= 1;
350433d6423SLionel Sambuc 	}
351433d6423SLionel Sambuc }
352433d6423SLionel Sambuc 
353433d6423SLionel Sambuc char *tree;		/* Tree to work on. */
354433d6423SLionel Sambuc FILE *statefp;		/* State file. */
355433d6423SLionel Sambuc char *state_file;
356433d6423SLionel Sambuc FILE *difffp;		/* File of differences. */
357433d6423SLionel Sambuc char *diff_file;
358433d6423SLionel Sambuc 
traverse(void)359433d6423SLionel Sambuc entry_t *traverse(void)
360433d6423SLionel Sambuc /* Get one name from the directory tree. */
361433d6423SLionel Sambuc {
362433d6423SLionel Sambuc 	static int depth;
363433d6423SLionel Sambuc 	static pathname_t path;
364433d6423SLionel Sambuc 	static entry_t entry;
365433d6423SLionel Sambuc 	static namelist_t **entries;
366433d6423SLionel Sambuc 	static size_t *trunc;
367433d6423SLionel Sambuc 	static size_t deep;
368433d6423SLionel Sambuc 	static namelist_t *newentries;
369433d6423SLionel Sambuc 	struct stat st;
370433d6423SLionel Sambuc 
371433d6423SLionel Sambuc recurse:
372433d6423SLionel Sambuc 	keep= KEEP_TRAVERSE;
373433d6423SLionel Sambuc 
374433d6423SLionel Sambuc 	if (deep == 0) {
375433d6423SLionel Sambuc 		/* Initialize for the root of the tree. */
376433d6423SLionel Sambuc 		path_init(&path);
377433d6423SLionel Sambuc 		path_add(&path, tree);
378433d6423SLionel Sambuc 		entries= allocate(nil, 1 * sizeof(entries[0]));
379433d6423SLionel Sambuc 		entries[0]= allocate(nil, sizeof(*entries[0]));
380433d6423SLionel Sambuc 		entries[0]->next= nil;
381433d6423SLionel Sambuc 		entries[0]->name= copystr("/");
382433d6423SLionel Sambuc 		trunc= allocate(nil, 1 * sizeof(trunc[0]));
383433d6423SLionel Sambuc 		trunc[0]= path_length(&path);
384433d6423SLionel Sambuc 		deep= 1;
385433d6423SLionel Sambuc 	} else
386433d6423SLionel Sambuc 	if (newentries != nil) {
387433d6423SLionel Sambuc 		/* Last entry was a directory, need to go down. */
388433d6423SLionel Sambuc 		if (entry.ignore) {
389433d6423SLionel Sambuc 			/* Ouch, it is to be ignored! */
390433d6423SLionel Sambuc 			while (newentries != nil) (void) pop_name(&newentries);
391433d6423SLionel Sambuc 			goto recurse;
392433d6423SLionel Sambuc 		}
393433d6423SLionel Sambuc 		if (++depth == deep) {
394433d6423SLionel Sambuc 			deep++;
395433d6423SLionel Sambuc 			entries= allocate(entries, deep * sizeof(entries[0]));
396433d6423SLionel Sambuc 			trunc= allocate(trunc, deep * sizeof(trunc[0]));
397433d6423SLionel Sambuc 		}
398433d6423SLionel Sambuc 		entries[depth]= newentries;
399433d6423SLionel Sambuc 		newentries= nil;
400433d6423SLionel Sambuc 		trunc[depth]= path_length(&path);
401433d6423SLionel Sambuc 	} else {
402433d6423SLionel Sambuc 		/* Pop up out of emptied directories. */
403433d6423SLionel Sambuc 		while (entries[depth] == nil) {
404433d6423SLionel Sambuc 			if (depth == 0) return nil;	/* Back at the root. */
405433d6423SLionel Sambuc 
406433d6423SLionel Sambuc 			/* Go up one level. */
407433d6423SLionel Sambuc 			depth--;
408433d6423SLionel Sambuc 		}
409433d6423SLionel Sambuc 	}
410433d6423SLionel Sambuc 	entry.name= pop_name(&entries[depth]);
411433d6423SLionel Sambuc 	path_trunc(&path, trunc[depth]);
412433d6423SLionel Sambuc 	path_add(&path, entry.name);
413433d6423SLionel Sambuc 	if (depth == 0) {
414433d6423SLionel Sambuc 		entry.path= "/";
415433d6423SLionel Sambuc 	} else {
416433d6423SLionel Sambuc 		entry.path= path_name(&path) + trunc[0];
417433d6423SLionel Sambuc 		if (entry.path[0] == '/') entry.path++;
418433d6423SLionel Sambuc 	}
419433d6423SLionel Sambuc 	entry.depth= depth;
420433d6423SLionel Sambuc 	entry.ignore= 0;
421433d6423SLionel Sambuc 
422433d6423SLionel Sambuc 	if (lstat(path_name(&path), &st) < 0) {
423433d6423SLionel Sambuc 		if (depth == 0 || errno != ENOENT) {
424433d6423SLionel Sambuc 			/* Something wrong with this entry, complain about
425433d6423SLionel Sambuc 			 * it and ignore it further.
426433d6423SLionel Sambuc 			 */
427433d6423SLionel Sambuc 			entry.ignore= errno;
428433d6423SLionel Sambuc 			report(path_name(&path));
429433d6423SLionel Sambuc 			return &entry;
430433d6423SLionel Sambuc 		} else {
431433d6423SLionel Sambuc 			/* Entry strangely nonexistent; simply continue. */
432433d6423SLionel Sambuc 			goto recurse;
433433d6423SLionel Sambuc 		}
434433d6423SLionel Sambuc 	}
435433d6423SLionel Sambuc 
436433d6423SLionel Sambuc 	/* Don't cross mountpoints if -x is set. */
437433d6423SLionel Sambuc 	if (xflag) {
438433d6423SLionel Sambuc 		if (xdev == NO_DEVICE) xdev= st.st_dev;
439433d6423SLionel Sambuc 		if (st.st_dev != xdev) {
440433d6423SLionel Sambuc 			/* Ignore the mountpoint. */
441433d6423SLionel Sambuc 			entry.ignore= EXDEV;
442433d6423SLionel Sambuc 			return &entry;
443433d6423SLionel Sambuc 		}
444433d6423SLionel Sambuc 	}
445433d6423SLionel Sambuc 
446433d6423SLionel Sambuc 	entry.mode= st.st_mode & 07777;
447433d6423SLionel Sambuc 	entry.uid= st.st_uid;
448433d6423SLionel Sambuc 	entry.gid= st.st_gid;
449433d6423SLionel Sambuc 	entry.size= st.st_size;
450433d6423SLionel Sambuc 	entry.mtime= st.st_mtime;
451433d6423SLionel Sambuc 	entry.rdev= st.st_rdev;
452433d6423SLionel Sambuc 
453433d6423SLionel Sambuc 	linked(&entry, &st);
454433d6423SLionel Sambuc 
455433d6423SLionel Sambuc 	if (S_ISDIR(st.st_mode)) {
456433d6423SLionel Sambuc 		/* A directory. */
457433d6423SLionel Sambuc 		entry.type= F_DIR;
458433d6423SLionel Sambuc 
459433d6423SLionel Sambuc 		/* Gather directory entries for the next traverse. */
460433d6423SLionel Sambuc 		if ((newentries= collect(path_name(&path))) == nil
461433d6423SLionel Sambuc 							&& errno != 0) {
462433d6423SLionel Sambuc 			entry.ignore= errno;
463433d6423SLionel Sambuc 			report(path_name(&path));
464433d6423SLionel Sambuc 		}
465433d6423SLionel Sambuc 	} else
466433d6423SLionel Sambuc 	if (S_ISREG(st.st_mode)) {
467433d6423SLionel Sambuc 		/* A plain file. */
468433d6423SLionel Sambuc 		entry.type= F_FILE;
469433d6423SLionel Sambuc 	} else
470433d6423SLionel Sambuc 	if (S_ISBLK(st.st_mode)) {
471433d6423SLionel Sambuc 		/* A block special file. */
472433d6423SLionel Sambuc 		entry.type= F_BLK;
473433d6423SLionel Sambuc 	} else
474433d6423SLionel Sambuc 	if (S_ISCHR(st.st_mode)) {
475433d6423SLionel Sambuc 		/* A character special file. */
476433d6423SLionel Sambuc 		entry.type= F_CHR;
477433d6423SLionel Sambuc 	} else
478433d6423SLionel Sambuc 	if (S_ISFIFO(st.st_mode)) {
479433d6423SLionel Sambuc 		/* A named pipe. */
480433d6423SLionel Sambuc 		entry.type= F_PIPE;
481433d6423SLionel Sambuc 	} else
482433d6423SLionel Sambuc 	if (S_ISLNK(st.st_mode)) {
483433d6423SLionel Sambuc 		/* A symbolic link. */
484433d6423SLionel Sambuc 		entry.type= F_LINK;
485433d6423SLionel Sambuc 		if ((entry.link= rdlink(path_name(&path), st.st_size)) == nil) {
486433d6423SLionel Sambuc 			entry.ignore= errno;
487433d6423SLionel Sambuc 			report(path_name(&path));
488433d6423SLionel Sambuc 		}
489433d6423SLionel Sambuc 	} else {
490433d6423SLionel Sambuc 		/* Unknown type of file. */
491433d6423SLionel Sambuc 		entry.ignore= EINVAL;
492433d6423SLionel Sambuc 	}
493433d6423SLionel Sambuc 	return &entry;
494433d6423SLionel Sambuc }
495433d6423SLionel Sambuc 
checkstate(void)496433d6423SLionel Sambuc void checkstate(void)
497433d6423SLionel Sambuc {
498433d6423SLionel Sambuc 	if (ferror(statefp)) fatal(state_file);
499433d6423SLionel Sambuc }
500433d6423SLionel Sambuc 
indent(int depth)501433d6423SLionel Sambuc void indent(int depth)
502433d6423SLionel Sambuc /* Provide indentation to show directory depth. */
503433d6423SLionel Sambuc {
504433d6423SLionel Sambuc 	int n= BASE_INDENT * (depth - 1);
505433d6423SLionel Sambuc 
506433d6423SLionel Sambuc 	while (n >= 8) {
507433d6423SLionel Sambuc 		if (putc('\t', statefp) == EOF) checkstate();
508433d6423SLionel Sambuc 		n-= 8;
509433d6423SLionel Sambuc 	}
510433d6423SLionel Sambuc 	while (n > 0) {
511433d6423SLionel Sambuc 		if (putc(' ', statefp) == EOF) checkstate();
512433d6423SLionel Sambuc 		n--;
513433d6423SLionel Sambuc 	}
514433d6423SLionel Sambuc }
515433d6423SLionel Sambuc 
print_name(FILE * fp,const char * name)516433d6423SLionel Sambuc int print_name(FILE *fp, const char *name)
517433d6423SLionel Sambuc /* Encode a name. */
518433d6423SLionel Sambuc {
519433d6423SLionel Sambuc 	const char *p;
520433d6423SLionel Sambuc 	int c;
521433d6423SLionel Sambuc 
522433d6423SLionel Sambuc 	for (p= name; (c= (unsigned char) *p) != 0; p++) {
523433d6423SLionel Sambuc 		if (c <= ' ' || c == '\\') {
524433d6423SLionel Sambuc 			fprintf(fp, "\\%03o", c);
525433d6423SLionel Sambuc 			if (ferror(fp)) return 0;
526433d6423SLionel Sambuc 		} else {
527433d6423SLionel Sambuc 			if (putc(c, fp) == EOF) return 0;
528433d6423SLionel Sambuc 		}
529433d6423SLionel Sambuc 	}
530433d6423SLionel Sambuc 	return 1;
531433d6423SLionel Sambuc }
532433d6423SLionel Sambuc 
mkstatefile(void)533433d6423SLionel Sambuc void mkstatefile(void)
534433d6423SLionel Sambuc /* Make a state file out of the directory tree. */
535433d6423SLionel Sambuc {
536433d6423SLionel Sambuc 	entry_t *entry;
537433d6423SLionel Sambuc 
538433d6423SLionel Sambuc 	while ((entry= traverse()) != nil) {
539433d6423SLionel Sambuc 		indent(entry->depth);
540433d6423SLionel Sambuc 		if (!print_name(statefp, entry->name)) checkstate();
541433d6423SLionel Sambuc 
542433d6423SLionel Sambuc 		if (entry->ignore) {
543433d6423SLionel Sambuc 			fprintf(statefp, "\tignore (%s)\n",
544433d6423SLionel Sambuc 				strerror(entry->ignore));
545433d6423SLionel Sambuc 			checkstate();
546433d6423SLionel Sambuc 			continue;
547433d6423SLionel Sambuc 		}
548433d6423SLionel Sambuc 
549433d6423SLionel Sambuc 		switch (entry->type) {
550433d6423SLionel Sambuc 		case F_DIR:
551433d6423SLionel Sambuc 			fprintf(statefp, "\td%03o %u %u",
552433d6423SLionel Sambuc 				(unsigned) entry->mode,
553433d6423SLionel Sambuc 				(unsigned) entry->uid, (unsigned) entry->gid);
554433d6423SLionel Sambuc 			break;
555433d6423SLionel Sambuc 		case F_FILE:
556433d6423SLionel Sambuc 			fprintf(statefp, "\t%03o %u %u %lu %lu",
557433d6423SLionel Sambuc 				(unsigned) entry->mode,
558433d6423SLionel Sambuc 				(unsigned) entry->uid, (unsigned) entry->gid,
559433d6423SLionel Sambuc 				(unsigned long) entry->size,
560433d6423SLionel Sambuc 				(unsigned long) entry->mtime);
561433d6423SLionel Sambuc 			break;
562433d6423SLionel Sambuc 		case F_BLK:
563433d6423SLionel Sambuc 		case F_CHR:
564433d6423SLionel Sambuc 			fprintf(statefp, "\t%c%03o %u %u %x",
565433d6423SLionel Sambuc 				entry->type == F_BLK ? 'b' : 'c',
566433d6423SLionel Sambuc 				(unsigned) entry->mode,
567433d6423SLionel Sambuc 				(unsigned) entry->uid, (unsigned) entry->gid,
568433d6423SLionel Sambuc 				(unsigned) entry->rdev);
569433d6423SLionel Sambuc 			break;
570433d6423SLionel Sambuc 		case F_PIPE:
571433d6423SLionel Sambuc 			fprintf(statefp, "\tp%03o %u %u",
572433d6423SLionel Sambuc 				(unsigned) entry->mode,
573433d6423SLionel Sambuc 				(unsigned) entry->uid, (unsigned) entry->gid);
574433d6423SLionel Sambuc 			break;
575433d6423SLionel Sambuc 		case F_LINK:
576433d6423SLionel Sambuc 			fprintf(statefp, "\t-> ");
577433d6423SLionel Sambuc 			checkstate();
578433d6423SLionel Sambuc 			(void) print_name(statefp, entry->link);
579433d6423SLionel Sambuc 			break;
580433d6423SLionel Sambuc 		}
581433d6423SLionel Sambuc 		checkstate();
582433d6423SLionel Sambuc 		if (entry->fake_ino != 0)
583433d6423SLionel Sambuc 			fprintf(statefp, " %lu", entry->fake_ino);
584433d6423SLionel Sambuc 		if (entry->lastlink)
585433d6423SLionel Sambuc 			fprintf(statefp, " last");
586433d6423SLionel Sambuc 		if (fputc('\n', statefp) == EOF) checkstate();
587433d6423SLionel Sambuc 	}
588433d6423SLionel Sambuc 	fflush(statefp);
589433d6423SLionel Sambuc 	checkstate();
590433d6423SLionel Sambuc }
591433d6423SLionel Sambuc 
read1line(FILE * fp)592433d6423SLionel Sambuc char *read1line(FILE *fp)
593433d6423SLionel Sambuc /* Read one line from a file.  Return null on EOF or error. */
594433d6423SLionel Sambuc {
595433d6423SLionel Sambuc 	static char *line;
596433d6423SLionel Sambuc 	static size_t len;
597433d6423SLionel Sambuc 	size_t idx;
598433d6423SLionel Sambuc 	int c;
599433d6423SLionel Sambuc 
600433d6423SLionel Sambuc 	if (len == 0) line= allocate(nil, (len= 16) * sizeof(line[0]));
601433d6423SLionel Sambuc 
602433d6423SLionel Sambuc 	idx= 0;
603433d6423SLionel Sambuc 	while ((c= getc(fp)) != EOF && c != '\n') {
604433d6423SLionel Sambuc 		if (c < '\t') {
605433d6423SLionel Sambuc 			/* Control characters are not possible. */
606433d6423SLionel Sambuc 			fprintf(stderr,
607433d6423SLionel Sambuc 				"remsync: control character in data file!\n");
608433d6423SLionel Sambuc 			exit(1);
609433d6423SLionel Sambuc 		}
610433d6423SLionel Sambuc 		line[idx++]= c;
611433d6423SLionel Sambuc 		if (idx == len) {
612433d6423SLionel Sambuc 			line= allocate(line, (len*= 2) * sizeof(line[0]));
613433d6423SLionel Sambuc 		}
614433d6423SLionel Sambuc 	}
615433d6423SLionel Sambuc 	if (c == EOF) {
616433d6423SLionel Sambuc 		if (ferror(fp)) return nil;
617433d6423SLionel Sambuc 		if (idx == 0) return nil;
618433d6423SLionel Sambuc 	}
619433d6423SLionel Sambuc 	line[idx]= 0;
620433d6423SLionel Sambuc 	return line;
621433d6423SLionel Sambuc }
622433d6423SLionel Sambuc 
getword(char ** pline,char ** parg,size_t * plen)623433d6423SLionel Sambuc void getword(char **pline, char **parg, size_t *plen)
624433d6423SLionel Sambuc /* Get one word from a line, interpret octal escapes. */
625433d6423SLionel Sambuc {
626433d6423SLionel Sambuc 	char *line= *pline;
627433d6423SLionel Sambuc 	char *arg= *parg;
628433d6423SLionel Sambuc 	size_t len= *plen;
629433d6423SLionel Sambuc 	int i;
630433d6423SLionel Sambuc 	int c;
631433d6423SLionel Sambuc 	size_t idx;
632433d6423SLionel Sambuc 
633433d6423SLionel Sambuc 	idx= 0;
634433d6423SLionel Sambuc 	while ((c= *line) != 0 && c != ' ' && c != '\t') {
635433d6423SLionel Sambuc 		line++;
636433d6423SLionel Sambuc 		if (c == '\\') {
637433d6423SLionel Sambuc 			c= 0;
638433d6423SLionel Sambuc 			for (i= 0; i < 3; i++) {
639433d6423SLionel Sambuc 				if ((unsigned) (*line - '0') >= 010) break;
640433d6423SLionel Sambuc 				c= (c << 3) | (*line - '0');
641433d6423SLionel Sambuc 				line++;
642433d6423SLionel Sambuc 			}
643433d6423SLionel Sambuc 		}
644433d6423SLionel Sambuc 		arg[idx++]= c;
645433d6423SLionel Sambuc 		if (idx == len) arg= allocate(arg, (len*= 2) * sizeof(arg[0]));
646433d6423SLionel Sambuc 	}
647433d6423SLionel Sambuc 	arg[idx]= 0;
648433d6423SLionel Sambuc 	*pline= line;
649433d6423SLionel Sambuc 	*parg= arg;
650433d6423SLionel Sambuc 	*plen= len;
651433d6423SLionel Sambuc }
652433d6423SLionel Sambuc 
splitline(char * line,char *** pargv,size_t * pargc)653433d6423SLionel Sambuc void splitline(char *line, char ***pargv, size_t *pargc)
654433d6423SLionel Sambuc /* Split a line into an array of words. */
655433d6423SLionel Sambuc {
656433d6423SLionel Sambuc 	static char **argv;
657433d6423SLionel Sambuc 	static size_t *lenv;
658433d6423SLionel Sambuc 	static size_t len;
659433d6423SLionel Sambuc 	size_t idx;
660433d6423SLionel Sambuc 
661433d6423SLionel Sambuc 	idx= 0;
662433d6423SLionel Sambuc 	for (;;) {
663433d6423SLionel Sambuc 		while (*line == ' ' || *line == '\t') line++;
664433d6423SLionel Sambuc 
665433d6423SLionel Sambuc 		if (*line == 0) break;
666433d6423SLionel Sambuc 
667433d6423SLionel Sambuc 		if (idx == len) {
668433d6423SLionel Sambuc 			len++;
669433d6423SLionel Sambuc 			argv= allocate(argv, len * sizeof(argv[0]));
670433d6423SLionel Sambuc 			lenv= allocate(lenv, len * sizeof(lenv[0]));
671433d6423SLionel Sambuc 			argv[idx]= allocate(nil, 16 * sizeof(argv[idx][0]));
672433d6423SLionel Sambuc 			lenv[idx]= 16;
673433d6423SLionel Sambuc 		}
674433d6423SLionel Sambuc 		getword(&line, &argv[idx], &lenv[idx]);
675433d6423SLionel Sambuc 		idx++;
676433d6423SLionel Sambuc 	}
677433d6423SLionel Sambuc 	*pargv= argv;
678433d6423SLionel Sambuc 	*pargc= idx;
679433d6423SLionel Sambuc }
680433d6423SLionel Sambuc 
getattributes(entry_t * entry,int argc,char ** argv)681433d6423SLionel Sambuc int getattributes(entry_t *entry, int argc, char **argv)
682433d6423SLionel Sambuc /* Convert state or difference file info into file attributes. */
683433d6423SLionel Sambuc {
684433d6423SLionel Sambuc 	int i;
685433d6423SLionel Sambuc 	int attr;
686433d6423SLionel Sambuc #define A_MODE1		0x01	/* Some of these attributes follow the name */
687433d6423SLionel Sambuc #define A_MODE		0x02
688433d6423SLionel Sambuc #define	A_OWNER		0x04
689433d6423SLionel Sambuc #define A_SIZETIME	0x08
690433d6423SLionel Sambuc #define A_DEV		0x10
691433d6423SLionel Sambuc #define A_LINK		0x20
692433d6423SLionel Sambuc 
693433d6423SLionel Sambuc 	switch (argv[0][0]) {
694433d6423SLionel Sambuc 	case 'd':
695433d6423SLionel Sambuc 		/* Directory. */
696433d6423SLionel Sambuc 		entry->type= F_DIR;
697433d6423SLionel Sambuc 		attr= A_MODE1 | A_OWNER;
698433d6423SLionel Sambuc 		break;
699433d6423SLionel Sambuc 	case 'b':
700433d6423SLionel Sambuc 		/* Block device. */
701433d6423SLionel Sambuc 		entry->type= F_BLK;
702433d6423SLionel Sambuc 		attr= A_MODE1 | A_OWNER | A_DEV;
703433d6423SLionel Sambuc 		break;
704433d6423SLionel Sambuc 	case 'c':
705433d6423SLionel Sambuc 		/* Character device. */
706433d6423SLionel Sambuc 		entry->type= F_CHR;
707433d6423SLionel Sambuc 		attr= A_MODE1 | A_OWNER | A_DEV;
708433d6423SLionel Sambuc 		break;
709433d6423SLionel Sambuc 	case 'p':
710433d6423SLionel Sambuc 		/* Named pipe. */
711433d6423SLionel Sambuc 		entry->type= F_PIPE;
712433d6423SLionel Sambuc 		attr= A_MODE1 | A_OWNER;
713433d6423SLionel Sambuc 		break;
714433d6423SLionel Sambuc 	case '-':
715433d6423SLionel Sambuc 		/* Symlink. */
716433d6423SLionel Sambuc 		entry->type= F_LINK;
717433d6423SLionel Sambuc 		attr= A_LINK;
718433d6423SLionel Sambuc 		break;
719433d6423SLionel Sambuc 	default:
720433d6423SLionel Sambuc 		/* Normal file. */
721433d6423SLionel Sambuc 		entry->type= F_FILE;
722433d6423SLionel Sambuc 		attr= A_MODE | A_OWNER | A_SIZETIME;
723433d6423SLionel Sambuc 	}
724433d6423SLionel Sambuc 
725433d6423SLionel Sambuc 	if (attr & (A_MODE | A_MODE1)) {
726433d6423SLionel Sambuc 		entry->mode= strtoul(argv[0] + (attr & A_MODE1), nil, 010);
727433d6423SLionel Sambuc 	}
728433d6423SLionel Sambuc 	i= 1;
729433d6423SLionel Sambuc 	if (attr & A_OWNER) {
730433d6423SLionel Sambuc 		if (i + 2 > argc) return 0;
731433d6423SLionel Sambuc 		entry->uid= strtoul(argv[i++], nil, 10);
732433d6423SLionel Sambuc 		entry->gid= strtoul(argv[i++], nil, 10);
733433d6423SLionel Sambuc 	}
734433d6423SLionel Sambuc 	if (attr & A_SIZETIME) {
735433d6423SLionel Sambuc 		if (i + 2 > argc) return 0;
736433d6423SLionel Sambuc 		entry->size= strtoul(argv[i++], nil, 10);
737433d6423SLionel Sambuc 		entry->mtime= strtoul(argv[i++], nil, 10);
738433d6423SLionel Sambuc 	}
739433d6423SLionel Sambuc 	if (attr & A_DEV) {
740433d6423SLionel Sambuc 		if (i + 1 > argc) return 0;
741433d6423SLionel Sambuc 		entry->rdev= strtoul(argv[i++], nil, 0x10);
742433d6423SLionel Sambuc 	}
743433d6423SLionel Sambuc 	if (attr & A_LINK) {
744433d6423SLionel Sambuc 		if (i + 1 > argc) return 0;
745433d6423SLionel Sambuc 		entry->link= argv[i++];
746433d6423SLionel Sambuc 	}
747433d6423SLionel Sambuc 	entry->linked= entry->lastlink= 0;
748433d6423SLionel Sambuc 	if (i < argc) {
749433d6423SLionel Sambuc 		/* It has a fake inode number, so it is a hard link. */
750433d6423SLionel Sambuc 		static struct links {	/* List of hard links. */
751433d6423SLionel Sambuc 			struct links	*next;
752433d6423SLionel Sambuc 			unsigned long	fake_ino;
753433d6423SLionel Sambuc 			char		*path;
754433d6423SLionel Sambuc 		} *links[1024];
755433d6423SLionel Sambuc 		struct links **plp, *lp;
756433d6423SLionel Sambuc 		unsigned long fake_ino;
757433d6423SLionel Sambuc 
758433d6423SLionel Sambuc 		fake_ino= strtoul(argv[i++], nil, 10);
759433d6423SLionel Sambuc 
760433d6423SLionel Sambuc 		plp= &links[fake_ino % arraysize(links)];
761433d6423SLionel Sambuc 		while ((lp= *plp) != nil && lp->fake_ino != fake_ino)
762433d6423SLionel Sambuc 			plp= &lp->next;
763433d6423SLionel Sambuc 
764433d6423SLionel Sambuc 		if (lp == nil) {
765433d6423SLionel Sambuc 			/* New link. */
766433d6423SLionel Sambuc 			*plp= lp= allocate(nil, sizeof(*lp));
767433d6423SLionel Sambuc 			lp->next= nil;
768433d6423SLionel Sambuc 			lp->fake_ino= fake_ino;
769433d6423SLionel Sambuc 			lp->path= copystr(entry->path);
770433d6423SLionel Sambuc 		} else {
771433d6423SLionel Sambuc 			/* Linked to. */
772433d6423SLionel Sambuc 			entry->link= lp->path;
773433d6423SLionel Sambuc 			entry->linked= 1;
774433d6423SLionel Sambuc 		}
775433d6423SLionel Sambuc 
776433d6423SLionel Sambuc 		if (i < argc) {
777433d6423SLionel Sambuc 			if (strcmp(argv[i++], "last") != 0) return 0;
778433d6423SLionel Sambuc 
779433d6423SLionel Sambuc 			/* Last hard link of a file. */
780433d6423SLionel Sambuc 			forget(lp->path);
781433d6423SLionel Sambuc 			*plp= lp->next;
782433d6423SLionel Sambuc 			deallocate(lp);
783433d6423SLionel Sambuc 			entry->lastlink= 1;
784433d6423SLionel Sambuc 		}
785433d6423SLionel Sambuc 	}
786433d6423SLionel Sambuc 	if (i != argc) return 0;
787433d6423SLionel Sambuc 	return 1;
788433d6423SLionel Sambuc }
789433d6423SLionel Sambuc 
state_syntax(off_t line)790433d6423SLionel Sambuc void state_syntax(off_t line)
791433d6423SLionel Sambuc {
792433d6423SLionel Sambuc 	fprintf(stderr, "remsync: %s: syntax error on line %lu\n",
793433d6423SLionel Sambuc 		state_file, (unsigned long) line);
794433d6423SLionel Sambuc 	exit(1);
795433d6423SLionel Sambuc }
796433d6423SLionel Sambuc 
readstate(void)797433d6423SLionel Sambuc entry_t *readstate(void)
798433d6423SLionel Sambuc /* Read one entry from the state file. */
799433d6423SLionel Sambuc {
800433d6423SLionel Sambuc 	static entry_t entry;
801433d6423SLionel Sambuc 	static pathname_t path;
802433d6423SLionel Sambuc 	static size_t *trunc;
803433d6423SLionel Sambuc 	static size_t trunc_len;
804433d6423SLionel Sambuc 	static int base_indent;
805433d6423SLionel Sambuc 	char *line;
806433d6423SLionel Sambuc 	char **argv;
807433d6423SLionel Sambuc 	size_t argc;
808433d6423SLionel Sambuc 	static off_t lineno;
809433d6423SLionel Sambuc 	int indent, depth;
810433d6423SLionel Sambuc 
811433d6423SLionel Sambuc recurse:
812433d6423SLionel Sambuc 	keep= KEEP_STATE;
813433d6423SLionel Sambuc 
814433d6423SLionel Sambuc 	if (feof(statefp) || (line= read1line(statefp)) == nil) {
815433d6423SLionel Sambuc 		checkstate();
816433d6423SLionel Sambuc 		return nil;
817433d6423SLionel Sambuc 	}
818433d6423SLionel Sambuc 	lineno++;
819433d6423SLionel Sambuc 
820433d6423SLionel Sambuc 	/* How far is this entry indented? */
821433d6423SLionel Sambuc 	indent= 0;
822433d6423SLionel Sambuc 	while (*line != 0) {
823433d6423SLionel Sambuc 		if (*line == ' ') indent++;
824433d6423SLionel Sambuc 		else
825433d6423SLionel Sambuc 		if (*line == '\t') indent= (indent + 8) & ~7;
826433d6423SLionel Sambuc 		else
827433d6423SLionel Sambuc 			break;
828433d6423SLionel Sambuc 		line++;
829433d6423SLionel Sambuc 	}
830433d6423SLionel Sambuc 	if (indent > 0 && base_indent == 0) base_indent= indent;
831433d6423SLionel Sambuc 	depth= (base_indent == 0 ? 0 : indent / base_indent) + 1;
832433d6423SLionel Sambuc 
833433d6423SLionel Sambuc 	if (entry.ignore && depth > entry.depth) {
834433d6423SLionel Sambuc 		/* If the old directory is ignored, then so are its entries. */
835433d6423SLionel Sambuc 		goto recurse;
836433d6423SLionel Sambuc 	}
837433d6423SLionel Sambuc 	entry.depth= depth;
838433d6423SLionel Sambuc 
839433d6423SLionel Sambuc 	splitline(line, &argv, &argc);
840433d6423SLionel Sambuc 	if (argc < 2) state_syntax(lineno);
841433d6423SLionel Sambuc 
842433d6423SLionel Sambuc 	if (trunc == nil) {
843433d6423SLionel Sambuc 		/* The root of the tree, initialize path. */
844433d6423SLionel Sambuc 		if (argv[0][0] != '/') state_syntax(lineno);
845433d6423SLionel Sambuc 		path_init(&path);
846433d6423SLionel Sambuc 		path_add(&path, "/");
847433d6423SLionel Sambuc 		trunc= allocate(nil, (trunc_len= 16) * sizeof(trunc[0]));
848433d6423SLionel Sambuc 
849433d6423SLionel Sambuc 		/* The root has depth 0. */
850433d6423SLionel Sambuc 		entry.depth= 0;
851433d6423SLionel Sambuc 		trunc[0]= 0;
852433d6423SLionel Sambuc 	} else {
853433d6423SLionel Sambuc 		if (entry.depth > trunc_len) {
854433d6423SLionel Sambuc 			trunc= allocate(trunc,
855433d6423SLionel Sambuc 					(trunc_len*= 2) * sizeof(trunc[0]));
856433d6423SLionel Sambuc 		}
857433d6423SLionel Sambuc 		path_trunc(&path, trunc[entry.depth - 1]);
858433d6423SLionel Sambuc 		path_add(&path, argv[0]);
859433d6423SLionel Sambuc 		trunc[entry.depth]= path_length(&path);
860433d6423SLionel Sambuc 	}
861433d6423SLionel Sambuc 
862433d6423SLionel Sambuc 	entry.path= path_name(&path);
863433d6423SLionel Sambuc 	entry.name= argv[0];
864433d6423SLionel Sambuc 	entry.link= nil;
865433d6423SLionel Sambuc 	if ((entry.ignore= strcmp(argv[1], "ignore") == 0)) {
866433d6423SLionel Sambuc 		return &entry;
867433d6423SLionel Sambuc 	}
868433d6423SLionel Sambuc 	if (!getattributes(&entry, argc - 1, argv + 1)) state_syntax(lineno);
869433d6423SLionel Sambuc 	return &entry;
870433d6423SLionel Sambuc }
871433d6423SLionel Sambuc 
checkdiff(void)872433d6423SLionel Sambuc void checkdiff(void)
873433d6423SLionel Sambuc {
874433d6423SLionel Sambuc 	if (ferror(difffp)) fatal(diff_file);
875433d6423SLionel Sambuc }
876433d6423SLionel Sambuc 
877433d6423SLionel Sambuc enum { DELETE, REPLACE, COPY, SIMILAR, EQUAL, ADD }
compare(entry_t * remote,entry_t * local)878433d6423SLionel Sambuc compare(entry_t *remote, entry_t *local)
879433d6423SLionel Sambuc /* Compare the local and remote entries and tell what need to be done. */
880433d6423SLionel Sambuc {
881433d6423SLionel Sambuc 	int cmp;
882433d6423SLionel Sambuc 
883433d6423SLionel Sambuc 	/* Surplus entries? */
884433d6423SLionel Sambuc 	if (local == nil) return DELETE;
885433d6423SLionel Sambuc 	if (remote == nil) return ADD;
886433d6423SLionel Sambuc 
887433d6423SLionel Sambuc 	/* Extra directory entries? */
888433d6423SLionel Sambuc 	if (remote->depth > local->depth) return DELETE;
889433d6423SLionel Sambuc 	if (local->depth > remote->depth) return ADD;
890433d6423SLionel Sambuc 
891433d6423SLionel Sambuc 	/* Compare names. */
892433d6423SLionel Sambuc 	cmp= strcmp(remote->name, local->name);
893433d6423SLionel Sambuc 	if (cmp < 0) return DELETE;
894433d6423SLionel Sambuc 	if (cmp > 0) return ADD;
895433d6423SLionel Sambuc 
896433d6423SLionel Sambuc 	/* The files have the same name.  Ignore one, ignore the other. */
897433d6423SLionel Sambuc 	if (remote->ignore || local->ignore) {
898433d6423SLionel Sambuc 		remote->ignore= local->ignore= 1;
899433d6423SLionel Sambuc 		return EQUAL;
900433d6423SLionel Sambuc 	}
901433d6423SLionel Sambuc 
902433d6423SLionel Sambuc 	/* Reasons for replacement? */
903433d6423SLionel Sambuc 	if (remote->type != local->type) return REPLACE;
904433d6423SLionel Sambuc 
905433d6423SLionel Sambuc 	/* Should be hard linked to the same file. */
906433d6423SLionel Sambuc 	if (remote->linked || local->linked) {
907433d6423SLionel Sambuc 		if (!remote->linked || !local->linked) return REPLACE;
908433d6423SLionel Sambuc 		if (strcmp(remote->link, local->link) != 0) return REPLACE;
909433d6423SLionel Sambuc 	}
910433d6423SLionel Sambuc 
911433d6423SLionel Sambuc 	switch (remote->type) {
912433d6423SLionel Sambuc 	case F_FILE:
913433d6423SLionel Sambuc 		if (uflag) {
914433d6423SLionel Sambuc 			if (remote->mtime < local->mtime) return COPY;
915433d6423SLionel Sambuc 		} else {
916433d6423SLionel Sambuc 			if (remote->size != local->size
917433d6423SLionel Sambuc 					|| remote->mtime != local->mtime)
918433d6423SLionel Sambuc 				return COPY;
919433d6423SLionel Sambuc 		}
920433d6423SLionel Sambuc 		goto check_modes;
921433d6423SLionel Sambuc 	case F_BLK:
922433d6423SLionel Sambuc 	case F_CHR:
923433d6423SLionel Sambuc 		if (remote->rdev != local->rdev) return REPLACE;
924433d6423SLionel Sambuc 		goto check_modes;
925433d6423SLionel Sambuc 	case F_DIR:
926433d6423SLionel Sambuc 	case F_PIPE:
927433d6423SLionel Sambuc 	check_modes:
928433d6423SLionel Sambuc 		if (remote->mode != local->mode
929433d6423SLionel Sambuc 			|| remote->uid != local->uid
930433d6423SLionel Sambuc 			|| remote->gid != local->gid) return SIMILAR;
931433d6423SLionel Sambuc 		break;
932433d6423SLionel Sambuc 	case F_LINK:
933433d6423SLionel Sambuc 		if (strcmp(remote->link, local->link) != 0) return REPLACE;
934433d6423SLionel Sambuc 		break;
935433d6423SLionel Sambuc 	}
936433d6423SLionel Sambuc 	return EQUAL;
937433d6423SLionel Sambuc }
938433d6423SLionel Sambuc 
delete(entry_t * old)939433d6423SLionel Sambuc void delete(entry_t *old)
940433d6423SLionel Sambuc /* Emit an instruction to remove an entry. */
941433d6423SLionel Sambuc {
942433d6423SLionel Sambuc 	if (old->ignore) return;
943433d6423SLionel Sambuc 	if (uflag) return;
944433d6423SLionel Sambuc 
945433d6423SLionel Sambuc 	fprintf(difffp, "rm ");
946433d6423SLionel Sambuc 	checkdiff();
947433d6423SLionel Sambuc 	if (!print_name(difffp, old->path)) checkdiff();
948433d6423SLionel Sambuc 	if (putc('\n', difffp) == EOF) checkdiff();
949433d6423SLionel Sambuc 	if (vflag) fprintf(stderr, "rm %s\n", old->path);
950433d6423SLionel Sambuc }
951433d6423SLionel Sambuc 
change_modes(entry_t * old,entry_t * new)952433d6423SLionel Sambuc void change_modes(entry_t *old, entry_t *new)
953433d6423SLionel Sambuc /* Emit an instruction to change the attributes of an entry. */
954433d6423SLionel Sambuc {
955433d6423SLionel Sambuc 	if (new->ignore) return;
956433d6423SLionel Sambuc 
957433d6423SLionel Sambuc 	fprintf(difffp, "chmod ");
958433d6423SLionel Sambuc 	checkdiff();
959433d6423SLionel Sambuc 	if (!print_name(difffp, new->path)) checkdiff();
960433d6423SLionel Sambuc 	fprintf(difffp, " %03o %u %u\n",
961433d6423SLionel Sambuc 		(unsigned) new->mode,
962433d6423SLionel Sambuc 		(unsigned) new->uid, (unsigned) new->gid);
963433d6423SLionel Sambuc 	checkdiff();
964433d6423SLionel Sambuc 	if (vflag && old->mode != new->mode) {
965433d6423SLionel Sambuc 		fprintf(stderr, "chmod %s %03o %u %u\n",
966433d6423SLionel Sambuc 			new->path,
967433d6423SLionel Sambuc 			(unsigned) new->mode,
968433d6423SLionel Sambuc 			(unsigned) new->uid, (unsigned) new->gid);
969433d6423SLionel Sambuc 	}
970433d6423SLionel Sambuc }
971433d6423SLionel Sambuc 
cat(int f,off_t size)972433d6423SLionel Sambuc int cat(int f, off_t size)
973433d6423SLionel Sambuc /* Include the contents of a file in the differences file. */
974433d6423SLionel Sambuc {
975433d6423SLionel Sambuc 	ssize_t n;
976433d6423SLionel Sambuc 	unsigned char buf[1024 << sizeof(int)];
977433d6423SLionel Sambuc 	unsigned char *p;
978433d6423SLionel Sambuc 	int c;
979433d6423SLionel Sambuc 
980433d6423SLionel Sambuc 	if (Dflag) return 1;	/* Debug: Don't need the file contents. */
981433d6423SLionel Sambuc 
982433d6423SLionel Sambuc 	while ((n= read(f, buf, sizeof(buf))) > 0) {
983433d6423SLionel Sambuc 		p= buf;
984433d6423SLionel Sambuc 		do {
985433d6423SLionel Sambuc 			if (size == 0) {
986433d6423SLionel Sambuc 				/* File suddenly larger. */
987433d6423SLionel Sambuc 				errno= EINVAL;
988433d6423SLionel Sambuc 				return 0;
989433d6423SLionel Sambuc 			}
990433d6423SLionel Sambuc 			c= *p++;
991433d6423SLionel Sambuc 			if (putc(c, difffp) == EOF) checkdiff();
992433d6423SLionel Sambuc 			size--;
993433d6423SLionel Sambuc 		} while (--n != 0);
994433d6423SLionel Sambuc 	}
995433d6423SLionel Sambuc 	if (size > 0) {
996433d6423SLionel Sambuc 		int err= errno;
997433d6423SLionel Sambuc 
998433d6423SLionel Sambuc 		/* File somehow shrunk, pad it out. */
999433d6423SLionel Sambuc 		do {
1000433d6423SLionel Sambuc 			if (putc(0, difffp) == EOF) checkdiff();
1001433d6423SLionel Sambuc 		} while (--size != 0);
1002433d6423SLionel Sambuc 		errno= n == 0 ? EINVAL : err;
1003433d6423SLionel Sambuc 		n= -1;
1004433d6423SLionel Sambuc 	}
1005433d6423SLionel Sambuc 	return n == 0;
1006433d6423SLionel Sambuc }
1007433d6423SLionel Sambuc 
add(entry_t * old,entry_t * new)1008433d6423SLionel Sambuc void add(entry_t *old, entry_t *new)
1009433d6423SLionel Sambuc /* Emit an instruction to add an entry. */
1010433d6423SLionel Sambuc {
1011433d6423SLionel Sambuc 	pathname_t file;
1012433d6423SLionel Sambuc 	int f;
1013433d6423SLionel Sambuc 
1014433d6423SLionel Sambuc 	if (new->ignore) return;
1015433d6423SLionel Sambuc 
1016433d6423SLionel Sambuc 	if (new->linked) {
1017433d6423SLionel Sambuc 		/* This file is to be a hard link to an existing file. */
1018433d6423SLionel Sambuc 		fprintf(difffp, "ln ");
1019433d6423SLionel Sambuc 		checkdiff();
1020433d6423SLionel Sambuc 		if (!print_name(difffp, new->link)) checkdiff();
1021433d6423SLionel Sambuc 		if (fputc(' ', difffp) == EOF) checkdiff();
1022433d6423SLionel Sambuc 		if (!print_name(difffp, new->path)) checkdiff();
1023433d6423SLionel Sambuc 		if (fputc('\n', difffp) == EOF) checkdiff();
1024433d6423SLionel Sambuc 		if (vflag) {
1025433d6423SLionel Sambuc 			fprintf(stderr, "ln %s %s\n", new->link, new->path);
1026433d6423SLionel Sambuc 		}
1027433d6423SLionel Sambuc 		return;
1028433d6423SLionel Sambuc 	}
1029433d6423SLionel Sambuc 
1030433d6423SLionel Sambuc 	/* Add some other type of file. */
1031433d6423SLionel Sambuc 	fprintf(difffp, "add ");
1032433d6423SLionel Sambuc 	checkdiff();
1033433d6423SLionel Sambuc 	if (!print_name(difffp, new->path)) checkdiff();
1034433d6423SLionel Sambuc 
1035433d6423SLionel Sambuc 	switch (new->type) {
1036433d6423SLionel Sambuc 	case F_DIR:
1037433d6423SLionel Sambuc 		fprintf(difffp, " d%03o %u %u\n",
1038433d6423SLionel Sambuc 			(unsigned) new->mode,
1039433d6423SLionel Sambuc 			(unsigned) new->uid, (unsigned) new->gid);
1040433d6423SLionel Sambuc 		if (vflag) fprintf(stderr, "mkdir %s\n", new->path);
1041433d6423SLionel Sambuc 		break;
1042433d6423SLionel Sambuc 	case F_FILE:
1043433d6423SLionel Sambuc 		path_init(&file);
1044433d6423SLionel Sambuc 		path_add(&file, tree);
1045433d6423SLionel Sambuc 		path_add(&file, new->path);
1046433d6423SLionel Sambuc 		if ((f= open(path_name(&file), O_RDONLY)) < 0) {
1047433d6423SLionel Sambuc 			report(path_name(&file));
1048433d6423SLionel Sambuc 			path_drop(&file);
1049433d6423SLionel Sambuc 			fprintf(difffp, " ignore\n");
1050433d6423SLionel Sambuc 			break;
1051433d6423SLionel Sambuc 		}
1052433d6423SLionel Sambuc 		fprintf(difffp, " %03o %u %u %lu %lu\n",
1053433d6423SLionel Sambuc 			(unsigned) new->mode,
1054433d6423SLionel Sambuc 			(unsigned) new->uid, (unsigned) new->gid,
1055433d6423SLionel Sambuc 			(unsigned long) new->size,
1056433d6423SLionel Sambuc 			(unsigned long) new->mtime);
1057433d6423SLionel Sambuc 		checkdiff();
1058433d6423SLionel Sambuc 		if (!cat(f, new->size)) {
1059433d6423SLionel Sambuc 			int err= errno;
1060433d6423SLionel Sambuc 			report(path_name(&file));
1061433d6423SLionel Sambuc 			fprintf(difffp, "old ");
1062433d6423SLionel Sambuc 			checkdiff();
1063433d6423SLionel Sambuc 			print_name(difffp, err == EINVAL
1064433d6423SLionel Sambuc 				? "File changed when copied" : strerror(err));
1065433d6423SLionel Sambuc 			fputc('\n', difffp);
1066433d6423SLionel Sambuc 			checkdiff();
1067433d6423SLionel Sambuc 		} else {
1068433d6423SLionel Sambuc 			if (vflag) {
1069433d6423SLionel Sambuc 				fprintf(stderr, "%s %s\n",
1070433d6423SLionel Sambuc 					old == nil ? "add" :
1071433d6423SLionel Sambuc 						old->mtime > new->mtime ?
1072433d6423SLionel Sambuc 							"restore" : "update",
1073433d6423SLionel Sambuc 					new->path);
1074433d6423SLionel Sambuc 			}
1075433d6423SLionel Sambuc 		}
1076433d6423SLionel Sambuc 		close(f);
1077433d6423SLionel Sambuc 		path_drop(&file);
1078433d6423SLionel Sambuc 		break;
1079433d6423SLionel Sambuc 	case F_BLK:
1080433d6423SLionel Sambuc 	case F_CHR:
1081433d6423SLionel Sambuc 		fprintf(difffp, " %c%03o %u %u %lx\n",
1082433d6423SLionel Sambuc 			new->type == F_BLK ? 'b' : 'c',
1083433d6423SLionel Sambuc 			(unsigned) new->mode,
1084433d6423SLionel Sambuc 			(unsigned) new->uid, (unsigned) new->gid,
1085433d6423SLionel Sambuc 			(unsigned long) new->rdev);
1086433d6423SLionel Sambuc 		if (vflag) fprintf(stderr, "mknod %s\n", new->path);
1087433d6423SLionel Sambuc 		break;
1088433d6423SLionel Sambuc 	case F_PIPE:
1089433d6423SLionel Sambuc 		fprintf(difffp, " p%03o %u %u\n",
1090433d6423SLionel Sambuc 			(unsigned) new->mode,
1091433d6423SLionel Sambuc 			(unsigned) new->uid, (unsigned) new->gid);
1092433d6423SLionel Sambuc 		if (vflag) fprintf(stderr, "mkfifo %s\n", new->path);
1093433d6423SLionel Sambuc 		break;
1094433d6423SLionel Sambuc 	case F_LINK:
1095433d6423SLionel Sambuc 		fprintf(difffp, " -> ");
1096433d6423SLionel Sambuc 		checkdiff();
1097433d6423SLionel Sambuc 		(void) print_name(difffp, new->link);
1098433d6423SLionel Sambuc 		checkdiff();
1099433d6423SLionel Sambuc 		fputc('\n', difffp);
1100433d6423SLionel Sambuc 		if (vflag) {
1101433d6423SLionel Sambuc 			fprintf(stderr, "ln -s %s %s\n", new->link, new->path);
1102433d6423SLionel Sambuc 		}
1103433d6423SLionel Sambuc 		break;
1104433d6423SLionel Sambuc 	}
1105433d6423SLionel Sambuc 	checkdiff();
1106433d6423SLionel Sambuc }
1107433d6423SLionel Sambuc 
mkdifferences(void)1108433d6423SLionel Sambuc void mkdifferences(void)
1109433d6423SLionel Sambuc {
1110433d6423SLionel Sambuc 	entry_t *remote;
1111433d6423SLionel Sambuc 	entry_t *local;
1112433d6423SLionel Sambuc 
1113433d6423SLionel Sambuc 	remote= readstate();
1114433d6423SLionel Sambuc 	local= traverse();
1115433d6423SLionel Sambuc 
1116433d6423SLionel Sambuc 	while (remote != nil || local != nil) {
1117433d6423SLionel Sambuc 		switch (compare(remote, local)) {
1118433d6423SLionel Sambuc 		case DELETE:
1119433d6423SLionel Sambuc 			/* Remove the remote file. */
1120433d6423SLionel Sambuc 			delete(remote);
1121433d6423SLionel Sambuc 			remote->ignore= 1;
1122433d6423SLionel Sambuc 			remote= readstate();
1123433d6423SLionel Sambuc 			break;
1124433d6423SLionel Sambuc 		case REPLACE:
1125433d6423SLionel Sambuc 			/* Replace the remote file with the local one. */
1126433d6423SLionel Sambuc 			if (remote->type == F_FILE && local->type == F_FILE
1127433d6423SLionel Sambuc 							&& !local->linked) {
1128433d6423SLionel Sambuc 				/* Don't overwrite, remove first. */
1129433d6423SLionel Sambuc 				delete(remote);
1130433d6423SLionel Sambuc 			}
1131433d6423SLionel Sambuc 			/*FALL THROUGH*/
1132433d6423SLionel Sambuc 		case COPY:
1133433d6423SLionel Sambuc 			/* Overwrite the remote file with the local one. */
1134433d6423SLionel Sambuc 			add(remote, local);
1135433d6423SLionel Sambuc 			remote->ignore= 1;
1136433d6423SLionel Sambuc 			goto skip2;
1137433d6423SLionel Sambuc 		case SIMILAR:
1138433d6423SLionel Sambuc 			/* About the same, but the attributes need changing. */
1139433d6423SLionel Sambuc 			change_modes(remote, local);
1140433d6423SLionel Sambuc 			goto skip2;
1141433d6423SLionel Sambuc 		case EQUAL:
1142433d6423SLionel Sambuc 		skip2:
1143433d6423SLionel Sambuc 			/* Skip two files. */
1144433d6423SLionel Sambuc 			remote= readstate();
1145433d6423SLionel Sambuc 			local= traverse();
1146433d6423SLionel Sambuc 			break;
1147433d6423SLionel Sambuc 		case ADD:
1148433d6423SLionel Sambuc 			/* Add the local file. */
1149433d6423SLionel Sambuc 			add(nil, local);
1150433d6423SLionel Sambuc 			local= traverse();
1151433d6423SLionel Sambuc 			break;
1152433d6423SLionel Sambuc 		}
1153433d6423SLionel Sambuc 	}
1154433d6423SLionel Sambuc 	fprintf(difffp, "end\n");
1155433d6423SLionel Sambuc 	fflush(difffp);
1156433d6423SLionel Sambuc 	checkdiff();
1157433d6423SLionel Sambuc }
1158433d6423SLionel Sambuc 
apply_remove(pathname_t * pp)1159433d6423SLionel Sambuc void apply_remove(pathname_t *pp)
1160433d6423SLionel Sambuc /* Remove an obsolete file. */
1161433d6423SLionel Sambuc {
1162433d6423SLionel Sambuc 	struct stat st;
1163433d6423SLionel Sambuc 
1164433d6423SLionel Sambuc 	if (lstat(path_name(pp), &st) < 0) {
1165433d6423SLionel Sambuc 		if (errno != ENOENT) report(path_name(pp));
1166433d6423SLionel Sambuc 		return;
1167433d6423SLionel Sambuc 	}
1168433d6423SLionel Sambuc 
1169433d6423SLionel Sambuc 	if (S_ISDIR(st.st_mode)) {
1170433d6423SLionel Sambuc 		/* Recursively delete directories. */
1171433d6423SLionel Sambuc 		size_t len;
1172433d6423SLionel Sambuc 		namelist_t *entries;
1173433d6423SLionel Sambuc 
1174433d6423SLionel Sambuc 		if ((entries= collect(path_name(pp))) == nil && errno != 0) {
1175433d6423SLionel Sambuc 			report(path_name(pp));
1176433d6423SLionel Sambuc 			return;
1177433d6423SLionel Sambuc 		}
1178433d6423SLionel Sambuc 		len= path_length(pp);
1179433d6423SLionel Sambuc 
1180433d6423SLionel Sambuc 		while (entries != nil) {
1181433d6423SLionel Sambuc 			path_add(pp, pop_name(&entries));
1182433d6423SLionel Sambuc 			apply_remove(pp);
1183433d6423SLionel Sambuc 			path_trunc(pp, len);
1184433d6423SLionel Sambuc 		}
1185433d6423SLionel Sambuc 		if (rmdir(path_name(pp)) < 0) {
1186433d6423SLionel Sambuc 			report(path_name(pp));
1187433d6423SLionel Sambuc 			return;
1188433d6423SLionel Sambuc 		}
1189433d6423SLionel Sambuc 		if (vflag) fprintf(stderr, "rmdir %s\n", path_name(pp));
1190433d6423SLionel Sambuc 	} else {
1191433d6423SLionel Sambuc 		/* Some other type of file. */
1192433d6423SLionel Sambuc 		if (unlink(path_name(pp)) < 0) {
1193433d6423SLionel Sambuc 			report(path_name(pp));
1194433d6423SLionel Sambuc 			return;
1195433d6423SLionel Sambuc 		}
1196433d6423SLionel Sambuc 		if (vflag) fprintf(stderr, "rm %s\n", path_name(pp));
1197433d6423SLionel Sambuc 	}
1198433d6423SLionel Sambuc }
1199433d6423SLionel Sambuc 
apply_mkold(const char * file,const char * err)1200433d6423SLionel Sambuc void apply_mkold(const char *file, const char *err)
1201433d6423SLionel Sambuc /* Make a file very old.  (An error occurred when it was added.) */
1202433d6423SLionel Sambuc {
1203433d6423SLionel Sambuc 	struct utimbuf utb;
1204433d6423SLionel Sambuc 
1205433d6423SLionel Sambuc 	utb.actime= utb.modtime= 0;
1206433d6423SLionel Sambuc 	if (utime(file, &utb) < 0) {
1207433d6423SLionel Sambuc 		report(file);
1208433d6423SLionel Sambuc 		return;
1209433d6423SLionel Sambuc 	}
1210433d6423SLionel Sambuc 	fprintf(stderr, "made %s look old", file);
1211*d0055759SDavid van Moolenbroek 	if (err != nil)
1212*d0055759SDavid van Moolenbroek 		fprintf(stderr, " due to a remote problem: %s\n", err);
1213*d0055759SDavid van Moolenbroek 	else
1214*d0055759SDavid van Moolenbroek 		fprintf(stderr, "\n");
1215433d6423SLionel Sambuc }
1216433d6423SLionel Sambuc 
apply_chmod(const char * file,mode_t mode,uid_t uid,gid_t gid,int talk)1217433d6423SLionel Sambuc void apply_chmod(const char *file, mode_t mode, uid_t uid, gid_t gid, int talk)
1218433d6423SLionel Sambuc /* Change mode and ownership. */
1219433d6423SLionel Sambuc {
1220433d6423SLionel Sambuc 	struct stat st;
1221433d6423SLionel Sambuc 
1222433d6423SLionel Sambuc 	if (lstat(file, &st) < 0) {
1223433d6423SLionel Sambuc 		report(file);
1224433d6423SLionel Sambuc 		return;
1225433d6423SLionel Sambuc 	}
1226433d6423SLionel Sambuc 	if ((st.st_mode & 07777) != mode) {
1227433d6423SLionel Sambuc 		if (chmod(file, mode) < 0) {
1228433d6423SLionel Sambuc 			report(file);
1229433d6423SLionel Sambuc 			return;
1230433d6423SLionel Sambuc 		}
1231433d6423SLionel Sambuc 		if (vflag && talk) {
1232433d6423SLionel Sambuc 			fprintf(stderr, "chmod %03o %s\n",
1233433d6423SLionel Sambuc 						(unsigned) mode, file);
1234433d6423SLionel Sambuc 		}
1235433d6423SLionel Sambuc 	}
1236433d6423SLionel Sambuc 	if (st.st_uid != uid || st.st_gid != gid) {
1237433d6423SLionel Sambuc 		if (chown(file, uid, gid) < 0) {
1238433d6423SLionel Sambuc 			if (errno != EPERM) report(file);
1239433d6423SLionel Sambuc 			return;
1240433d6423SLionel Sambuc 		}
1241433d6423SLionel Sambuc 		if (vflag && talk) {
1242433d6423SLionel Sambuc 			fprintf(stderr, "chown %u:%u %s\n",
1243433d6423SLionel Sambuc 				(unsigned) uid, (unsigned) gid, file);
1244433d6423SLionel Sambuc 		}
1245433d6423SLionel Sambuc 	}
1246433d6423SLionel Sambuc }
1247433d6423SLionel Sambuc 
apply_add(pathname_t * pp,entry_t * entry)1248433d6423SLionel Sambuc void apply_add(pathname_t *pp, entry_t *entry)
1249433d6423SLionel Sambuc /* Add or replace a file. */
1250433d6423SLionel Sambuc {
1251433d6423SLionel Sambuc 	const char *file;
1252433d6423SLionel Sambuc 	off_t size;
1253433d6423SLionel Sambuc 	int f;
1254433d6423SLionel Sambuc 	unsigned char buf[1024 << sizeof(int)];
1255433d6423SLionel Sambuc 	unsigned char *p;
1256433d6423SLionel Sambuc 	int c;
1257433d6423SLionel Sambuc 	int dirty;
1258433d6423SLionel Sambuc 	struct stat st;
1259433d6423SLionel Sambuc 	struct utimbuf utb;
1260433d6423SLionel Sambuc 
1261433d6423SLionel Sambuc 	if (entry->ignore) return;
1262433d6423SLionel Sambuc 
1263433d6423SLionel Sambuc 	if (lstat(path_name(pp), &st) >= 0 && (entry->type != F_FILE
1264433d6423SLionel Sambuc 					|| !S_ISREG(st.st_mode))) {
1265433d6423SLionel Sambuc 		apply_remove(pp);
1266433d6423SLionel Sambuc 	}
1267433d6423SLionel Sambuc 
1268433d6423SLionel Sambuc 	file= path_name(pp);
1269433d6423SLionel Sambuc 
1270433d6423SLionel Sambuc 	switch (entry->type) {
1271433d6423SLionel Sambuc 	case F_DIR:
1272433d6423SLionel Sambuc 		if (mkdir(file, entry->mode) < 0) {
1273433d6423SLionel Sambuc 			report(file);
1274433d6423SLionel Sambuc 			return;
1275433d6423SLionel Sambuc 		}
1276433d6423SLionel Sambuc 		if (vflag) fprintf(stderr, "mkdir %s\n", file);
1277433d6423SLionel Sambuc 		break;
1278433d6423SLionel Sambuc 	case F_FILE:
1279433d6423SLionel Sambuc 		size= entry->size;
1280433d6423SLionel Sambuc 
1281433d6423SLionel Sambuc 		f= -1;
1282433d6423SLionel Sambuc 		st.st_mode= 0;
1283433d6423SLionel Sambuc 		if (lstat(file, &st) < 0 || S_ISREG(st.st_mode)) {
1284433d6423SLionel Sambuc 			f= open(file, O_WRONLY | O_CREAT | O_TRUNC,
1285433d6423SLionel Sambuc 						entry->mode);
1286433d6423SLionel Sambuc 			if (f < 0) {
1287433d6423SLionel Sambuc 				(void) chmod(file, entry->mode | 0200);
1288433d6423SLionel Sambuc 				f= open(file, O_WRONLY | O_CREAT | O_TRUNC,
1289433d6423SLionel Sambuc 						entry->mode);
1290433d6423SLionel Sambuc 			}
1291433d6423SLionel Sambuc 			if (f < 0) {
1292433d6423SLionel Sambuc 				(void) unlink(file);
1293433d6423SLionel Sambuc 				f= open(file, O_WRONLY | O_CREAT | O_TRUNC,
1294433d6423SLionel Sambuc 						entry->mode);
1295433d6423SLionel Sambuc 			}
1296433d6423SLionel Sambuc 			if (f < 0) report(file);
1297433d6423SLionel Sambuc 		}
1298433d6423SLionel Sambuc 		dirty= (f >= 0);
1299433d6423SLionel Sambuc 		p= buf;
1300433d6423SLionel Sambuc 		while (size > 0 && (c= getc(difffp)) != EOF) {
1301433d6423SLionel Sambuc 			size--;
1302433d6423SLionel Sambuc 			*p++= c;
1303433d6423SLionel Sambuc 			if (p == arraylimit(buf) || size == 0) {
1304433d6423SLionel Sambuc 				if (f >= 0 && write(f, buf, p - buf) < 0) {
1305433d6423SLionel Sambuc 					report(file);
1306433d6423SLionel Sambuc 					close(f);
1307433d6423SLionel Sambuc 					f= -1;
1308433d6423SLionel Sambuc 				}
1309433d6423SLionel Sambuc 				p= buf;
1310433d6423SLionel Sambuc 			}
1311433d6423SLionel Sambuc 		}
1312433d6423SLionel Sambuc 		if (size > 0) {
1313433d6423SLionel Sambuc 			if (ferror(difffp)) report(diff_file);
1314433d6423SLionel Sambuc 			if (feof(difffp)) {
1315433d6423SLionel Sambuc 				fprintf(stderr, "remspec: %s: premature EOF\n",
1316433d6423SLionel Sambuc 					diff_file);
1317433d6423SLionel Sambuc 			}
1318433d6423SLionel Sambuc 			if (dirty) apply_mkold(file, nil);
1319433d6423SLionel Sambuc 			exit(1);
1320433d6423SLionel Sambuc 		}
1321433d6423SLionel Sambuc 		if (f < 0) {
1322433d6423SLionel Sambuc 			if (dirty) apply_mkold(file, nil);
1323433d6423SLionel Sambuc 			return;
1324433d6423SLionel Sambuc 		}
1325433d6423SLionel Sambuc 		close(f);
1326433d6423SLionel Sambuc 		if (vflag) {
1327433d6423SLionel Sambuc 			fprintf(stderr, st.st_mode == 0 ? "add %s\n"
1328433d6423SLionel Sambuc 				: entry->mtime >= st.st_mtime
1329433d6423SLionel Sambuc 					? "update %s\n" : "restore %s\n", file);
1330433d6423SLionel Sambuc 		}
1331433d6423SLionel Sambuc 		utb.actime= time(nil);
1332433d6423SLionel Sambuc 		utb.modtime= entry->mtime;
1333433d6423SLionel Sambuc 		if (utime(file, &utb) < 0) report(file);
1334433d6423SLionel Sambuc 		break;
1335433d6423SLionel Sambuc 	case F_BLK:
1336433d6423SLionel Sambuc 		if (mknod(file, S_IFBLK | entry->mode, entry->rdev) < 0) {
1337433d6423SLionel Sambuc 			report(file);
1338433d6423SLionel Sambuc 			return;
1339433d6423SLionel Sambuc 		}
1340433d6423SLionel Sambuc 		if (vflag) {
1341433d6423SLionel Sambuc 			fprintf(stderr, "mknod %s b %d %d\n", file,
1342433d6423SLionel Sambuc 				major(entry->rdev), minor(entry->rdev));
1343433d6423SLionel Sambuc 		}
1344433d6423SLionel Sambuc 		break;
1345433d6423SLionel Sambuc 	case F_CHR:
1346433d6423SLionel Sambuc 		if (mknod(file, S_IFCHR | entry->mode, entry->rdev) < 0) {
1347433d6423SLionel Sambuc 			report(file);
1348433d6423SLionel Sambuc 			return;
1349433d6423SLionel Sambuc 		}
1350433d6423SLionel Sambuc 		if (vflag) {
1351433d6423SLionel Sambuc 			fprintf(stderr, "mknod %s c %d %d\n", file,
1352433d6423SLionel Sambuc 				major(entry->rdev), minor(entry->rdev));
1353433d6423SLionel Sambuc 		}
1354433d6423SLionel Sambuc 		break;
1355433d6423SLionel Sambuc 	case F_PIPE:
1356433d6423SLionel Sambuc 		if (mknod(file, S_IFIFO | entry->mode, 0) < 0) {
1357433d6423SLionel Sambuc 			report(file);
1358433d6423SLionel Sambuc 			return;
1359433d6423SLionel Sambuc 		}
1360433d6423SLionel Sambuc 		if (vflag) fprintf(stderr, "mknod %s p\n", file);
1361433d6423SLionel Sambuc 		break;
1362433d6423SLionel Sambuc 	case F_LINK:
1363433d6423SLionel Sambuc 		if (symlink(entry->link, file) < 0) {
1364433d6423SLionel Sambuc 			report(file);
1365433d6423SLionel Sambuc 			return;
1366433d6423SLionel Sambuc 		}
1367433d6423SLionel Sambuc 		if (vflag) fprintf(stderr, "ln -s %s %s\n", entry->link, file);
1368433d6423SLionel Sambuc 		return;
1369433d6423SLionel Sambuc 	}
1370433d6423SLionel Sambuc 	apply_chmod(file, entry->mode, entry->uid, entry->gid, 0);
1371433d6423SLionel Sambuc }
1372433d6423SLionel Sambuc 
apply_link(const char * file,pathname_t * pp)1373433d6423SLionel Sambuc void apply_link(const char *file, pathname_t *pp)
1374433d6423SLionel Sambuc /* Hard link *pp to file. */
1375433d6423SLionel Sambuc {
1376433d6423SLionel Sambuc 	struct stat st1, st2;
1377433d6423SLionel Sambuc 
1378433d6423SLionel Sambuc 	if (lstat(file, &st1) < 0) {
1379433d6423SLionel Sambuc 		report(file);
1380433d6423SLionel Sambuc 		return;
1381433d6423SLionel Sambuc 	}
1382433d6423SLionel Sambuc 	if (lstat(path_name(pp), &st2) >= 0) {
1383433d6423SLionel Sambuc 		if (st1.st_ino == st2.st_ino && st1.st_dev == st2.st_dev)
1384433d6423SLionel Sambuc 			return;
1385433d6423SLionel Sambuc 		apply_remove(pp);
1386433d6423SLionel Sambuc 		if (lstat(path_name(pp), &st2) >= 0) return;
1387433d6423SLionel Sambuc 	}
1388433d6423SLionel Sambuc 	if (link(file, path_name(pp)) < 0) {
1389433d6423SLionel Sambuc 		fprintf(stderr, "remsync: ln %s %s: %s\n", file, path_name(pp),
1390433d6423SLionel Sambuc 			strerror(errno));
1391433d6423SLionel Sambuc 		excode= 1;
1392433d6423SLionel Sambuc 		return;
1393433d6423SLionel Sambuc 	}
1394433d6423SLionel Sambuc 	if (vflag) fprintf(stderr, "ln %s %s\n", file, path_name(pp));
1395433d6423SLionel Sambuc }
1396433d6423SLionel Sambuc 
diff_syntax(const char * line)1397433d6423SLionel Sambuc void diff_syntax(const char *line)
1398433d6423SLionel Sambuc {
1399433d6423SLionel Sambuc 	fprintf(stderr, "remsync: %s: syntax error on this line: %s\n",
1400433d6423SLionel Sambuc 		diff_file, line);
1401433d6423SLionel Sambuc 	exit(1);
1402433d6423SLionel Sambuc }
1403433d6423SLionel Sambuc 
apply_differences(void)1404433d6423SLionel Sambuc void apply_differences(void)
1405433d6423SLionel Sambuc /* Update a tree to a list of differences derived from a remote tree. */
1406433d6423SLionel Sambuc {
1407433d6423SLionel Sambuc 	char *line;
1408433d6423SLionel Sambuc 	char **argv;
1409433d6423SLionel Sambuc 	size_t argc;
1410433d6423SLionel Sambuc 	pathname_t path, link;
1411433d6423SLionel Sambuc 	size_t trunc;
1412433d6423SLionel Sambuc 
1413433d6423SLionel Sambuc 	path_init(&path);
1414433d6423SLionel Sambuc 	path_init(&link);
1415433d6423SLionel Sambuc 	path_add(&path, tree);
1416433d6423SLionel Sambuc 	path_add(&link, tree);
1417433d6423SLionel Sambuc 	trunc= path_length(&path);
1418433d6423SLionel Sambuc 
1419433d6423SLionel Sambuc 	while (!feof(difffp) && (line= read1line(difffp)) != nil) {
1420433d6423SLionel Sambuc 		splitline(line, &argv, &argc);
1421433d6423SLionel Sambuc 		if (argc == 0) diff_syntax(line);
1422433d6423SLionel Sambuc 
1423433d6423SLionel Sambuc 		path_trunc(&path, trunc);
1424433d6423SLionel Sambuc 
1425433d6423SLionel Sambuc 		if (strcmp(argv[0], "add") == 0) {
1426433d6423SLionel Sambuc 			entry_t entry;
1427433d6423SLionel Sambuc 
1428433d6423SLionel Sambuc 			if (argc < 3) diff_syntax(line);
1429433d6423SLionel Sambuc 			path_add(&path, argv[1]);
1430433d6423SLionel Sambuc 			entry.ignore= (strcmp(argv[2], "ignore") == 0);
1431433d6423SLionel Sambuc 			if (!entry.ignore && !getattributes(&entry,
1432433d6423SLionel Sambuc 							argc - 2, argv + 2))
1433433d6423SLionel Sambuc 				diff_syntax(line);
1434433d6423SLionel Sambuc 			apply_add(&path, &entry);
1435433d6423SLionel Sambuc 		} else
1436433d6423SLionel Sambuc 		if (strcmp(argv[0], "rm") == 0) {
1437433d6423SLionel Sambuc 			if (argc != 2) diff_syntax(line);
1438433d6423SLionel Sambuc 			path_add(&path, argv[1]);
1439433d6423SLionel Sambuc 			apply_remove(&path);
1440433d6423SLionel Sambuc 		} else
1441433d6423SLionel Sambuc 		if (strcmp(argv[0], "ln") == 0) {
1442433d6423SLionel Sambuc 			if (argc != 3) diff_syntax(line);
1443433d6423SLionel Sambuc 			path_trunc(&link, trunc);
1444433d6423SLionel Sambuc 			path_add(&link, argv[1]);
1445433d6423SLionel Sambuc 			path_add(&path, argv[2]);
1446433d6423SLionel Sambuc 			apply_link(path_name(&link), &path);
1447433d6423SLionel Sambuc 		} else
1448433d6423SLionel Sambuc 		if (strcmp(argv[0], "chmod") == 0) {
1449433d6423SLionel Sambuc 			if (argc != 5) diff_syntax(line);
1450433d6423SLionel Sambuc 			path_add(&path, argv[1]);
1451433d6423SLionel Sambuc 			apply_chmod(path_name(&path),
1452433d6423SLionel Sambuc 				strtoul(argv[2], nil, 010),
1453433d6423SLionel Sambuc 				strtoul(argv[3], nil, 10),
1454433d6423SLionel Sambuc 				strtoul(argv[4], nil, 10),
1455433d6423SLionel Sambuc 				1);
1456433d6423SLionel Sambuc 		} else
1457433d6423SLionel Sambuc 		if (strcmp(argv[0], "old") == 0) {
1458433d6423SLionel Sambuc 			if (argc != 3) diff_syntax(line);
1459433d6423SLionel Sambuc 			path_add(&path, argv[1]);
1460433d6423SLionel Sambuc 			apply_mkold(path_name(&path), argv[2]);
1461433d6423SLionel Sambuc 		} else
1462433d6423SLionel Sambuc 		if (strcmp(argv[0], "end") == 0) {
1463433d6423SLionel Sambuc 			if (argc != 1) diff_syntax(line);
1464433d6423SLionel Sambuc 			break;
1465433d6423SLionel Sambuc 		} else {
1466433d6423SLionel Sambuc 			diff_syntax(line);
1467433d6423SLionel Sambuc 		}
1468433d6423SLionel Sambuc 	}
1469433d6423SLionel Sambuc 	checkdiff();
1470433d6423SLionel Sambuc }
1471433d6423SLionel Sambuc 
usage(void)1472433d6423SLionel Sambuc void usage(void)
1473433d6423SLionel Sambuc {
1474433d6423SLionel Sambuc     fprintf(stderr, "Usage: remsync -sxv tree [state-file]\n");
1475433d6423SLionel Sambuc     fprintf(stderr, "       remsync -duxvD tree [state-file [diff-file]]\n");
1476433d6423SLionel Sambuc     fprintf(stderr, "       remsync [-xv] tree [diff-file]\n");
1477433d6423SLionel Sambuc     exit(1);
1478433d6423SLionel Sambuc }
1479433d6423SLionel Sambuc 
main(int argc,char ** argv)1480433d6423SLionel Sambuc int main(int argc, char **argv)
1481433d6423SLionel Sambuc {
1482433d6423SLionel Sambuc 	int i;
1483433d6423SLionel Sambuc 
1484433d6423SLionel Sambuc 	for (i= 1; i < argc && argv[i][0] == '-'; i++) {
1485433d6423SLionel Sambuc 		char *p= argv[i] + 1;
1486433d6423SLionel Sambuc 
1487433d6423SLionel Sambuc 		if (p[0] == '-' && p[1] == 0) { i++; break; }
1488433d6423SLionel Sambuc 
1489433d6423SLionel Sambuc 		while (*p != 0) {
1490433d6423SLionel Sambuc 			switch (*p++) {
1491433d6423SLionel Sambuc 			case 's':	sflag= 1; break;
1492433d6423SLionel Sambuc 			case 'd':	dflag= 1; break;
1493433d6423SLionel Sambuc 			case 'u':	uflag= 1; break;
1494433d6423SLionel Sambuc 			case 'x':	xflag= 1; break;
1495433d6423SLionel Sambuc 			case 'D':	Dflag= 1; break;
1496433d6423SLionel Sambuc 			case 'v':	vflag= 1; break;
1497433d6423SLionel Sambuc 			default:	usage();
1498433d6423SLionel Sambuc 			}
1499433d6423SLionel Sambuc 		}
1500433d6423SLionel Sambuc 	}
1501433d6423SLionel Sambuc 	if (sflag && dflag) usage();
1502433d6423SLionel Sambuc 	if (sflag && uflag) usage();
1503433d6423SLionel Sambuc 	if (!sflag && !dflag && uflag) usage();
1504433d6423SLionel Sambuc 	if (!dflag && Dflag) usage();
1505433d6423SLionel Sambuc 
1506433d6423SLionel Sambuc 	if (i == argc) usage();
1507433d6423SLionel Sambuc 	tree= argv[i++];
1508433d6423SLionel Sambuc 
1509433d6423SLionel Sambuc 	if (sflag) {
1510433d6423SLionel Sambuc 		/* Make a state file. */
1511433d6423SLionel Sambuc 		state_file= i < argc ? argv[i++] : "-";
1512433d6423SLionel Sambuc 		if (i != argc) usage();
1513433d6423SLionel Sambuc 
1514433d6423SLionel Sambuc 		statefp= stdout;
1515433d6423SLionel Sambuc 		if (strcmp(state_file, "-") != 0) {
1516433d6423SLionel Sambuc 			if ((statefp= fopen(state_file, "w")) == nil)
1517433d6423SLionel Sambuc 				fatal(state_file);
1518433d6423SLionel Sambuc 		}
1519433d6423SLionel Sambuc 		mkstatefile();
1520433d6423SLionel Sambuc 	} else
1521433d6423SLionel Sambuc 	if (dflag) {
1522433d6423SLionel Sambuc 		/* Make a file of differences. */
1523433d6423SLionel Sambuc 		state_file= i < argc ? argv[i++] : "-";
1524433d6423SLionel Sambuc 
1525433d6423SLionel Sambuc 		diff_file= i < argc ? argv[i++] : "-";
1526433d6423SLionel Sambuc 		if (i != argc) usage();
1527433d6423SLionel Sambuc 
1528433d6423SLionel Sambuc 		statefp= stdin;
1529433d6423SLionel Sambuc 		if (strcmp(state_file, "-") != 0) {
1530433d6423SLionel Sambuc 			if ((statefp= fopen(state_file, "r")) == nil)
1531433d6423SLionel Sambuc 				fatal(state_file);
1532433d6423SLionel Sambuc 		}
1533433d6423SLionel Sambuc 
1534433d6423SLionel Sambuc 		difffp= stdout;
1535433d6423SLionel Sambuc 		if (strcmp(diff_file, "-") != 0) {
1536433d6423SLionel Sambuc 			if ((difffp= fopen(diff_file, "w")) == nil)
1537433d6423SLionel Sambuc 				fatal(diff_file);
1538433d6423SLionel Sambuc 		}
1539433d6423SLionel Sambuc 		mkdifferences();
1540433d6423SLionel Sambuc 	} else {
1541433d6423SLionel Sambuc 		/* Apply a file of differences. */
1542433d6423SLionel Sambuc 		diff_file= i < argc ? argv[i++] : "-";
1543433d6423SLionel Sambuc 		if (i != argc) usage();
1544433d6423SLionel Sambuc 
1545433d6423SLionel Sambuc 		difffp= stdin;
1546433d6423SLionel Sambuc 		if (strcmp(diff_file, "-") != 0) {
1547433d6423SLionel Sambuc 			if ((difffp= fopen(diff_file, "r")) == nil)
1548433d6423SLionel Sambuc 				fatal(diff_file);
1549433d6423SLionel Sambuc 		}
1550433d6423SLionel Sambuc 		apply_differences();
1551433d6423SLionel Sambuc 	}
1552433d6423SLionel Sambuc 	exit(excode);
1553433d6423SLionel Sambuc }
1554