1552 lines
		
	
	
		
			34 KiB
		
	
	
	
		
			C
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			1552 lines
		
	
	
		
			34 KiB
		
	
	
	
		
			C
		
	
	
		
			Executable File
		
	
	
	
	
| /*	remsync 1.5 - remotely synchronize file trees	Author: Kees J. Bot
 | |
|  *								10 Jun 1994
 | |
|  */
 | |
| #define nil 0
 | |
| #include <sys/types.h>
 | |
| #include <sys/stat.h>
 | |
| #include <stdio.h>
 | |
| #include <stdlib.h>
 | |
| #include <stdarg.h>
 | |
| #include <string.h>
 | |
| #include <dirent.h>
 | |
| #include <unistd.h>
 | |
| #include <fcntl.h>
 | |
| #include <errno.h>
 | |
| #include <limits.h>
 | |
| #include <time.h>
 | |
| #include <utime.h>
 | |
| 
 | |
| #define arraysize(a)	(sizeof(a) / sizeof((a)[0]))
 | |
| #define arraylimit(a)	((a) + arraysize(a))
 | |
| 
 | |
| #ifndef major
 | |
| #define major(dev)	((int) ((dev) >> 8))
 | |
| #define minor(dev)	((int) ((dev) & 0xFF))
 | |
| #endif
 | |
| 
 | |
| #ifndef S_ISLNK
 | |
| /* There were no symlinks in medieval times. */
 | |
| #define S_ISLNK(mode)			(0)
 | |
| #define lstat				stat
 | |
| #define symlink(path1, path2)		(errno= ENOSYS, -1)
 | |
| #define readlink(path, buf, len)	(errno= ENOSYS, -1)
 | |
| #endif
 | |
| 
 | |
| int sflag;		/* Make state file. */
 | |
| int dflag;		/* Make list of differences. */
 | |
| int uflag;		/* Only update files with newer versions. */
 | |
| int xflag;		/* Do not cross device boundaries. */
 | |
| int Dflag;		/* Debug: Readable differences, no file contents. */
 | |
| int vflag;		/* Verbose. */
 | |
| 
 | |
| #define NO_DEVICE	(-1)
 | |
| dev_t xdev= NO_DEVICE;	/* The device that you should stay within. */
 | |
| 
 | |
| int excode= 0;		/* Exit(excode); */
 | |
| 
 | |
| #define BASE_INDENT	2	/* State file basic indent. */
 | |
| 
 | |
| void report(const char *label)
 | |
| {
 | |
| 	fprintf(stderr, "remsync: %s: %s\n", label, strerror(errno));
 | |
| 	excode= 1;
 | |
| }
 | |
| 
 | |
| void fatal(const char *label)
 | |
| {
 | |
| 	report(label);
 | |
| 	exit(1);
 | |
| }
 | |
| 
 | |
| void *allocate(void *mem, size_t size)
 | |
| {
 | |
| 	if ((mem= mem == nil ? malloc(size) : realloc(mem, size)) == nil) {
 | |
| 		fprintf(stderr, "remsync: Out of memory: %s\n",
 | |
| 			strerror(errno));
 | |
| 		exit(1);
 | |
| 	}
 | |
| 	return mem;
 | |
| }
 | |
| 
 | |
| void deallocate(void *mem)
 | |
| {
 | |
| 	if (mem != nil) free(mem);
 | |
| }
 | |
| 
 | |
| /* One needs to slowly forget two sets of objects: for the code that reads
 | |
|  * the state file, and for the code that traverses trees.
 | |
|  */
 | |
| int keep;
 | |
| #define KEEP_STATE	0
 | |
| #define KEEP_TRAVERSE	1
 | |
| 
 | |
| void forget(void *mem)
 | |
| /* Some objects must be deleted in time, but not just yet. */
 | |
| {
 | |
| 	static void *death_row[2][50];
 | |
| 	static void **dp[2]= { death_row[0], death_row[1] };
 | |
| 
 | |
| 	deallocate(*dp[keep]);
 | |
| 	*dp[keep]++= mem;
 | |
| 	if (dp[keep] == arraylimit(death_row[keep])) dp[keep]= death_row[keep];
 | |
| }
 | |
| 
 | |
| char *copystr(const char *s)
 | |
| {
 | |
| 	char *c= allocate(nil, (strlen(s) + 1) * sizeof(c[0]));
 | |
| 	strcpy(c, s);
 | |
| 	return c;
 | |
| }
 | |
| 
 | |
| typedef struct pathname {
 | |
| 	char		*path;	/* The actual pathname. */
 | |
| 	size_t		idx;	/* Index for the terminating null byte. */
 | |
| 	size_t		lim;	/* Actual length of the path array. */
 | |
| } pathname_t;
 | |
| 
 | |
| void path_init(pathname_t *pp)
 | |
| /* Initialize a pathname to the null string. */
 | |
| {
 | |
| 	pp->path= allocate(nil, (pp->lim= 16) * sizeof(pp->path[0]));
 | |
| 	pp->path[pp->idx= 0]= 0;
 | |
| }
 | |
| 
 | |
| void path_add(pathname_t *pp, const char *name)
 | |
| /* Add a component to a pathname. */
 | |
| {
 | |
| 	size_t lim;
 | |
| 	char *p;
 | |
| 	int slash;
 | |
| 
 | |
| 	lim= pp->idx + strlen(name) + 2;
 | |
| 
 | |
| 	if (lim > pp->lim) {
 | |
| 		pp->lim= lim + lim/2;	/* add an extra 50% growing space. */
 | |
| 		pp->path= allocate(pp->path, pp->lim * sizeof(pp->path[0]));
 | |
| 	}
 | |
| 
 | |
| 	p= pp->path + pp->idx;
 | |
| 	slash= (pp->idx > 0);
 | |
| 	if (pp->idx == 1 && p[-1] == '/') p--;
 | |
| 
 | |
| 	while (*name != 0) {
 | |
| 		if (*name == '/') {
 | |
| 			slash= 1;
 | |
| 		} else {
 | |
| 			if (slash) { *p++ = '/'; slash= 0; }
 | |
| 			*p++= *name;
 | |
| 		}
 | |
| 		name++;
 | |
| 	}
 | |
| 	if (slash && p == pp->path) *p++= '/';
 | |
| 	*p = 0;
 | |
| 	pp->idx= p - pp->path;
 | |
| }
 | |
| 
 | |
| void path_trunc(pathname_t *pp, size_t didx)
 | |
| /* Delete part of a pathname to a remembered length. */
 | |
| {
 | |
| 	pp->path[pp->idx= didx]= 0;
 | |
| }
 | |
| 
 | |
| #if kept_for_comments_only
 | |
| 
 | |
| const char *path_name(const pathname_t *pp)
 | |
| /* Return the actual name as a char array. */
 | |
| {
 | |
| 	return pp->path;
 | |
| }
 | |
| 
 | |
| size_t path_length(const pathname_t *pp)
 | |
| /* The length of the pathname. */
 | |
| {
 | |
| 	return pp->idx;
 | |
| }
 | |
| 
 | |
| void path_drop(pathname_t *pp)
 | |
| /* Release the storage occupied by the pathname. */
 | |
| {
 | |
| 	free(pp->path);
 | |
| }
 | |
| #endif
 | |
| 
 | |
| #define path_name(pp)		((const char *) (pp)->path)
 | |
| #define path_length(pp)		((pp)->idx)
 | |
| #define path_drop(pp)		free((void *) (pp)->path)
 | |
| 
 | |
| typedef struct namelist {	/* Obviously a list of names. */
 | |
| 	struct namelist	*next;
 | |
| 	char		*name;
 | |
| } namelist_t;
 | |
| 
 | |
| char *rdlink(const char *link, off_t size)
 | |
| /* Look where "link" points. */
 | |
| {
 | |
| 	static char *path= nil;
 | |
| 	static size_t len= 0;
 | |
| 	size_t n;
 | |
| 
 | |
| 	if (len <= size) {
 | |
| 		path= allocate(path, (len= size * 2) * sizeof(path[0]));
 | |
| 	}
 | |
| 	if ((n= readlink(link, path, len)) == -1) return nil;
 | |
| 	path[n]= 0;
 | |
| 	return path;
 | |
| }
 | |
| 
 | |
| void sort(namelist_t **anl)
 | |
| /* A stable mergesort disguised as line noise.  Must be called like this:
 | |
|  *	if (L!=nil && L->next!=nil) sort(&L);
 | |
|  */
 | |
| {
 | |
| 	/* static */ namelist_t *nl1, **mid;  /* Need not be local */
 | |
| 	namelist_t *nl2;
 | |
| 
 | |
| 	nl1= *(mid= &(*anl)->next);
 | |
| 	do {
 | |
| 		if ((nl1= nl1->next) == nil) break;
 | |
| 		mid= &(*mid)->next;
 | |
| 	} while ((nl1= nl1->next) != nil);
 | |
| 
 | |
| 	nl2= *mid;
 | |
| 	*mid= nil;
 | |
| 
 | |
| 	if ((*anl)->next != nil) sort(anl);
 | |
| 	if (nl2->next != nil) sort(&nl2);
 | |
| 
 | |
| 	nl1= *anl;
 | |
| 	for (;;) {
 | |
| 		if (strcmp(nl1->name, nl2->name)<=0) {
 | |
| 			if ((nl1= *(anl= &nl1->next)) == nil) {
 | |
| 				*anl= nl2;
 | |
| 				break;
 | |
| 			}
 | |
| 		} else {
 | |
| 			*anl= nl2;
 | |
| 			nl2= *(anl= &nl2->next);
 | |
| 			*anl= nl1;
 | |
| 			if (nl2 == nil) break;
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| namelist_t *collect(const char *dir)
 | |
| /* Return a sorted list of directory entries.  Returns null with errno != 0
 | |
|  * on error.
 | |
|  */
 | |
| {
 | |
| 	namelist_t *names, **pn= &names;
 | |
| 	DIR *dp;
 | |
| 	struct dirent *entry;
 | |
| 
 | |
| 	if ((dp= opendir(dir)) == nil) return nil;
 | |
| 
 | |
| 	while ((entry= readdir(dp)) != nil) {
 | |
| 		if (entry->d_name[0] == '.'
 | |
| 			&& (entry->d_name[1] == 0
 | |
| 				|| (entry->d_name[1] == '.'
 | |
| 					&& entry->d_name[2] == 0))) {
 | |
| 			continue;
 | |
| 		}
 | |
| 		*pn= allocate(nil, sizeof(**pn));
 | |
| 		(*pn)->name= copystr(entry->d_name);
 | |
| 		pn= &(*pn)->next;
 | |
| 	}
 | |
| 	closedir(dp);
 | |
| 	*pn= nil;
 | |
| 	errno= 0;
 | |
| 	if (names != nil && names->next != nil) sort(&names);
 | |
| 	return names;
 | |
| }
 | |
| 
 | |
| char *pop_name(namelist_t **names)
 | |
| /* Return one name of a name list. */
 | |
| {
 | |
| 	char *name;
 | |
| 	namelist_t *junk;
 | |
| 
 | |
| 	junk= *names;
 | |
| 	*names= junk->next;
 | |
| 	name= junk->name;
 | |
| 	deallocate(junk);
 | |
| 	forget(name);
 | |
| 	return name;
 | |
| }
 | |
| 
 | |
| typedef enum filetype {		/* The files we know about. */
 | |
| 	F_DIR,
 | |
| 	F_FILE,
 | |
| 	F_BLK,
 | |
| 	F_CHR,
 | |
| 	F_PIPE,
 | |
| 	F_LINK
 | |
| } filetype_t;
 | |
| 
 | |
| typedef struct entry {		/* One file. */
 | |
| 	int		depth;		/* Depth in directory tree. */
 | |
| 	const char	*name;		/* Name of entry. */
 | |
| 	const char	*path;		/* Path name. */
 | |
| 	int		ignore;		/* Ignore this entry (errno number.) */
 | |
| 	unsigned long	fake_ino;	/* Fake inode number for hard links. */
 | |
| 	int		linked;		/* Is the file hard linked? */
 | |
| 	int		lastlink;	/* Is it the last link? */
 | |
| 	char		*link;		/* Where a (sym)link points to. */
 | |
| 	filetype_t	type;
 | |
| 	mode_t		mode;		/* Not unlike those in struct stat. */
 | |
| 	uid_t		uid;
 | |
| 	gid_t		gid;
 | |
| 	off_t		size;
 | |
| 	time_t		mtime;
 | |
| 	dev_t		rdev;
 | |
| } entry_t;
 | |
| 
 | |
| void linked(entry_t *entry, struct stat *stp)
 | |
| /* Return an "inode number" if a file could have links (link count > 1).
 | |
|  * Also return a path to the first link if you see the file again.
 | |
|  */
 | |
| {
 | |
| 	static unsigned long new_fake_ino= 0;
 | |
| 	static struct links {
 | |
| 		struct links	*next;
 | |
| 		char		*path;
 | |
| 		ino_t		ino;
 | |
| 		dev_t		dev;
 | |
| 		nlink_t		nlink;
 | |
| 		unsigned long	fake_ino;
 | |
| 	} *links[1024];
 | |
| 	struct links **plp, *lp;
 | |
| 
 | |
| 	entry->linked= entry->lastlink= 0;
 | |
| 	entry->fake_ino= 0;
 | |
| 	entry->link= nil;
 | |
| 
 | |
| 	if (S_ISDIR(stp->st_mode) || stp->st_nlink < 2) return;
 | |
| 
 | |
| 	plp= &links[stp->st_ino % arraysize(links)];
 | |
| 	while ((lp= *plp) != nil && (lp->ino != stp->st_ino
 | |
| 				|| lp->dev != stp->st_dev)) plp= &lp->next;
 | |
| 
 | |
| 	if (lp == nil) {
 | |
| 		/* New file, store it with a new fake inode number. */
 | |
| 		*plp= lp= allocate(nil, sizeof(*lp));
 | |
| 		lp->next= nil;
 | |
| 		lp->path= copystr(entry->path);
 | |
| 		lp->ino= stp->st_ino;
 | |
| 		lp->dev= stp->st_dev;
 | |
| 		lp->nlink= stp->st_nlink;
 | |
| 		lp->fake_ino= ++new_fake_ino;
 | |
| 	} else {
 | |
| 		entry->link= lp->path;
 | |
| 		entry->linked= 1;
 | |
| 	}
 | |
| 	entry->fake_ino= lp->fake_ino;
 | |
| 
 | |
| 	if (--lp->nlink == 0) {
 | |
| 		/* No need to remember this one, no more links coming. */
 | |
| 		*plp= lp->next;
 | |
| 		forget(lp->path);
 | |
| 		deallocate(lp);
 | |
| 		entry->lastlink= 1;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| char *tree;		/* Tree to work on. */
 | |
| FILE *statefp;		/* State file. */
 | |
| char *state_file;
 | |
| FILE *difffp;		/* File of differences. */
 | |
| char *diff_file;
 | |
| 
 | |
| entry_t *traverse(void)
 | |
| /* Get one name from the directory tree. */
 | |
| {
 | |
| 	static int depth;
 | |
| 	static pathname_t path;
 | |
| 	static entry_t entry;
 | |
| 	static namelist_t **entries;
 | |
| 	static size_t *trunc;
 | |
| 	static size_t deep;
 | |
| 	static namelist_t *newentries;
 | |
| 	struct stat st;
 | |
| 
 | |
| recurse:
 | |
| 	keep= KEEP_TRAVERSE;
 | |
| 
 | |
| 	if (deep == 0) {
 | |
| 		/* Initialize for the root of the tree. */
 | |
| 		path_init(&path);
 | |
| 		path_add(&path, tree);
 | |
| 		entries= allocate(nil, 1 * sizeof(entries[0]));
 | |
| 		entries[0]= allocate(nil, sizeof(*entries[0]));
 | |
| 		entries[0]->next= nil;
 | |
| 		entries[0]->name= copystr("/");
 | |
| 		trunc= allocate(nil, 1 * sizeof(trunc[0]));
 | |
| 		trunc[0]= path_length(&path);
 | |
| 		deep= 1;
 | |
| 	} else
 | |
| 	if (newentries != nil) {
 | |
| 		/* Last entry was a directory, need to go down. */
 | |
| 		if (entry.ignore) {
 | |
| 			/* Ouch, it is to be ignored! */
 | |
| 			while (newentries != nil) (void) pop_name(&newentries);
 | |
| 			goto recurse;
 | |
| 		}
 | |
| 		if (++depth == deep) {
 | |
| 			deep++;
 | |
| 			entries= allocate(entries, deep * sizeof(entries[0]));
 | |
| 			trunc= allocate(trunc, deep * sizeof(trunc[0]));
 | |
| 		}
 | |
| 		entries[depth]= newentries;
 | |
| 		newentries= nil;
 | |
| 		trunc[depth]= path_length(&path);
 | |
| 	} else {
 | |
| 		/* Pop up out of emptied directories. */
 | |
| 		while (entries[depth] == nil) {
 | |
| 			if (depth == 0) return nil;	/* Back at the root. */
 | |
| 
 | |
| 			/* Go up one level. */
 | |
| 			depth--;
 | |
| 		}
 | |
| 	}
 | |
| 	entry.name= pop_name(&entries[depth]);
 | |
| 	path_trunc(&path, trunc[depth]);
 | |
| 	path_add(&path, entry.name);
 | |
| 	if (depth == 0) {
 | |
| 		entry.path= "/";
 | |
| 	} else {
 | |
| 		entry.path= path_name(&path) + trunc[0];
 | |
| 		if (entry.path[0] == '/') entry.path++;
 | |
| 	}
 | |
| 	entry.depth= depth;
 | |
| 	entry.ignore= 0;
 | |
| 
 | |
| 	if (lstat(path_name(&path), &st) < 0) {
 | |
| 		if (depth == 0 || errno != ENOENT) {
 | |
| 			/* Something wrong with this entry, complain about
 | |
| 			 * it and ignore it further.
 | |
| 			 */
 | |
| 			entry.ignore= errno;
 | |
| 			report(path_name(&path));
 | |
| 			return &entry;
 | |
| 		} else {
 | |
| 			/* Entry strangely nonexistent; simply continue. */
 | |
| 			goto recurse;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	/* Don't cross mountpoints if -x is set. */
 | |
| 	if (xflag) {
 | |
| 		if (xdev == NO_DEVICE) xdev= st.st_dev;
 | |
| 		if (st.st_dev != xdev) {
 | |
| 			/* Ignore the mountpoint. */
 | |
| 			entry.ignore= EXDEV;
 | |
| 			return &entry;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	entry.mode= st.st_mode & 07777;
 | |
| 	entry.uid= st.st_uid;
 | |
| 	entry.gid= st.st_gid;
 | |
| 	entry.size= st.st_size;
 | |
| 	entry.mtime= st.st_mtime;
 | |
| 	entry.rdev= st.st_rdev;
 | |
| 
 | |
| 	linked(&entry, &st);
 | |
| 
 | |
| 	if (S_ISDIR(st.st_mode)) {
 | |
| 		/* A directory. */
 | |
| 		entry.type= F_DIR;
 | |
| 
 | |
| 		/* Gather directory entries for the next traverse. */
 | |
| 		if ((newentries= collect(path_name(&path))) == nil
 | |
| 							&& errno != 0) {
 | |
| 			entry.ignore= errno;
 | |
| 			report(path_name(&path));
 | |
| 		}
 | |
| 	} else
 | |
| 	if (S_ISREG(st.st_mode)) {
 | |
| 		/* A plain file. */
 | |
| 		entry.type= F_FILE;
 | |
| 	} else
 | |
| 	if (S_ISBLK(st.st_mode)) {
 | |
| 		/* A block special file. */
 | |
| 		entry.type= F_BLK;
 | |
| 	} else
 | |
| 	if (S_ISCHR(st.st_mode)) {
 | |
| 		/* A character special file. */
 | |
| 		entry.type= F_CHR;
 | |
| 	} else
 | |
| 	if (S_ISFIFO(st.st_mode)) {
 | |
| 		/* A named pipe. */
 | |
| 		entry.type= F_PIPE;
 | |
| 	} else
 | |
| 	if (S_ISLNK(st.st_mode)) {
 | |
| 		/* A symbolic link. */
 | |
| 		entry.type= F_LINK;
 | |
| 		if ((entry.link= rdlink(path_name(&path), st.st_size)) == nil) {
 | |
| 			entry.ignore= errno;
 | |
| 			report(path_name(&path));
 | |
| 		}
 | |
| 	} else {
 | |
| 		/* Unknown type of file. */
 | |
| 		entry.ignore= EINVAL;
 | |
| 	}
 | |
| 	return &entry;
 | |
| }
 | |
| 
 | |
| void checkstate(void)
 | |
| {
 | |
| 	if (ferror(statefp)) fatal(state_file);
 | |
| }
 | |
| 
 | |
| void indent(int depth)
 | |
| /* Provide indentation to show directory depth. */
 | |
| {
 | |
| 	int n= BASE_INDENT * (depth - 1);
 | |
| 
 | |
| 	while (n >= 8) {
 | |
| 		if (putc('\t', statefp) == EOF) checkstate();
 | |
| 		n-= 8;
 | |
| 	}
 | |
| 	while (n > 0) {
 | |
| 		if (putc(' ', statefp) == EOF) checkstate();
 | |
| 		n--;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| int print_name(FILE *fp, const char *name)
 | |
| /* Encode a name. */
 | |
| {
 | |
| 	const char *p;
 | |
| 	int c;
 | |
| 
 | |
| 	for (p= name; (c= (unsigned char) *p) != 0; p++) {
 | |
| 		if (c <= ' ' || c == '\\') {
 | |
| 			fprintf(fp, "\\%03o", c);
 | |
| 			if (ferror(fp)) return 0;
 | |
| 		} else {
 | |
| 			if (putc(c, fp) == EOF) return 0;
 | |
| 		}
 | |
| 	}
 | |
| 	return 1;
 | |
| }
 | |
| 
 | |
| void mkstatefile(void)
 | |
| /* Make a state file out of the directory tree. */
 | |
| {
 | |
| 	entry_t *entry;
 | |
| 
 | |
| 	while ((entry= traverse()) != nil) {
 | |
| 		indent(entry->depth);
 | |
| 		if (!print_name(statefp, entry->name)) checkstate();
 | |
| 
 | |
| 		if (entry->ignore) {
 | |
| 			fprintf(statefp, "\tignore (%s)\n",
 | |
| 				strerror(entry->ignore));
 | |
| 			checkstate();
 | |
| 			continue;
 | |
| 		}
 | |
| 
 | |
| 		switch (entry->type) {
 | |
| 		case F_DIR:
 | |
| 			fprintf(statefp, "\td%03o %u %u",
 | |
| 				(unsigned) entry->mode,
 | |
| 				(unsigned) entry->uid, (unsigned) entry->gid);
 | |
| 			break;
 | |
| 		case F_FILE:
 | |
| 			fprintf(statefp, "\t%03o %u %u %lu %lu",
 | |
| 				(unsigned) entry->mode,
 | |
| 				(unsigned) entry->uid, (unsigned) entry->gid,
 | |
| 				(unsigned long) entry->size,
 | |
| 				(unsigned long) entry->mtime);
 | |
| 			break;
 | |
| 		case F_BLK:
 | |
| 		case F_CHR:
 | |
| 			fprintf(statefp, "\t%c%03o %u %u %x",
 | |
| 				entry->type == F_BLK ? 'b' : 'c',
 | |
| 				(unsigned) entry->mode,
 | |
| 				(unsigned) entry->uid, (unsigned) entry->gid,
 | |
| 				(unsigned) entry->rdev);
 | |
| 			break;
 | |
| 		case F_PIPE:
 | |
| 			fprintf(statefp, "\tp%03o %u %u",
 | |
| 				(unsigned) entry->mode,
 | |
| 				(unsigned) entry->uid, (unsigned) entry->gid);
 | |
| 			break;
 | |
| 		case F_LINK:
 | |
| 			fprintf(statefp, "\t-> ");
 | |
| 			checkstate();
 | |
| 			(void) print_name(statefp, entry->link);
 | |
| 			break;
 | |
| 		}
 | |
| 		checkstate();
 | |
| 		if (entry->fake_ino != 0)
 | |
| 			fprintf(statefp, " %lu", entry->fake_ino);
 | |
| 		if (entry->lastlink)
 | |
| 			fprintf(statefp, " last");
 | |
| 		if (fputc('\n', statefp) == EOF) checkstate();
 | |
| 	}
 | |
| 	fflush(statefp);
 | |
| 	checkstate();
 | |
| }
 | |
| 
 | |
| char *read1line(FILE *fp)
 | |
| /* Read one line from a file.  Return null on EOF or error. */
 | |
| {
 | |
| 	static char *line;
 | |
| 	static size_t len;
 | |
| 	size_t idx;
 | |
| 	int c;
 | |
| 
 | |
| 	if (len == 0) line= allocate(nil, (len= 16) * sizeof(line[0]));
 | |
| 
 | |
| 	idx= 0;
 | |
| 	while ((c= getc(fp)) != EOF && c != '\n') {
 | |
| 		if (c < '\t') {
 | |
| 			/* Control characters are not possible. */
 | |
| 			fprintf(stderr,
 | |
| 				"remsync: control character in data file!\n");
 | |
| 			exit(1);
 | |
| 		}
 | |
| 		line[idx++]= c;
 | |
| 		if (idx == len) {
 | |
| 			line= allocate(line, (len*= 2) * sizeof(line[0]));
 | |
| 		}
 | |
| 	}
 | |
| 	if (c == EOF) {
 | |
| 		if (ferror(fp)) return nil;
 | |
| 		if (idx == 0) return nil;
 | |
| 	}
 | |
| 	line[idx]= 0;
 | |
| 	return line;
 | |
| }
 | |
| 
 | |
| void getword(char **pline, char **parg, size_t *plen)
 | |
| /* Get one word from a line, interpret octal escapes. */
 | |
| {
 | |
| 	char *line= *pline;
 | |
| 	char *arg= *parg;
 | |
| 	size_t len= *plen;
 | |
| 	int i;
 | |
| 	int c;
 | |
| 	size_t idx;
 | |
| 
 | |
| 	idx= 0;
 | |
| 	while ((c= *line) != 0 && c != ' ' && c != '\t') {
 | |
| 		line++;
 | |
| 		if (c == '\\') {
 | |
| 			c= 0;
 | |
| 			for (i= 0; i < 3; i++) {
 | |
| 				if ((unsigned) (*line - '0') >= 010) break;
 | |
| 				c= (c << 3) | (*line - '0');
 | |
| 				line++;
 | |
| 			}
 | |
| 		}
 | |
| 		arg[idx++]= c;
 | |
| 		if (idx == len) arg= allocate(arg, (len*= 2) * sizeof(arg[0]));
 | |
| 	}
 | |
| 	arg[idx]= 0;
 | |
| 	*pline= line;
 | |
| 	*parg= arg;
 | |
| 	*plen= len;
 | |
| }
 | |
| 
 | |
| void splitline(char *line, char ***pargv, size_t *pargc)
 | |
| /* Split a line into an array of words. */
 | |
| {
 | |
| 	static char **argv;
 | |
| 	static size_t *lenv;
 | |
| 	static size_t len;
 | |
| 	size_t idx;
 | |
| 
 | |
| 	idx= 0;
 | |
| 	for (;;) {
 | |
| 		while (*line == ' ' || *line == '\t') line++;
 | |
| 
 | |
| 		if (*line == 0) break;
 | |
| 
 | |
| 		if (idx == len) {
 | |
| 			len++;
 | |
| 			argv= allocate(argv, len * sizeof(argv[0]));
 | |
| 			lenv= allocate(lenv, len * sizeof(lenv[0]));
 | |
| 			argv[idx]= allocate(nil, 16 * sizeof(argv[idx][0]));
 | |
| 			lenv[idx]= 16;
 | |
| 		}
 | |
| 		getword(&line, &argv[idx], &lenv[idx]);
 | |
| 		idx++;
 | |
| 	}
 | |
| 	*pargv= argv;
 | |
| 	*pargc= idx;
 | |
| }
 | |
| 
 | |
| int getattributes(entry_t *entry, int argc, char **argv)
 | |
| /* Convert state or difference file info into file attributes. */
 | |
| {
 | |
| 	int i;
 | |
| 	int attr;
 | |
| #define A_MODE1		0x01	/* Some of these attributes follow the name */
 | |
| #define A_MODE		0x02
 | |
| #define	A_OWNER		0x04
 | |
| #define A_SIZETIME	0x08
 | |
| #define A_DEV		0x10
 | |
| #define A_LINK		0x20
 | |
| 
 | |
| 	switch (argv[0][0]) {
 | |
| 	case 'd':
 | |
| 		/* Directory. */
 | |
| 		entry->type= F_DIR;
 | |
| 		attr= A_MODE1 | A_OWNER;
 | |
| 		break;
 | |
| 	case 'b':
 | |
| 		/* Block device. */
 | |
| 		entry->type= F_BLK;
 | |
| 		attr= A_MODE1 | A_OWNER | A_DEV;
 | |
| 		break;
 | |
| 	case 'c':
 | |
| 		/* Character device. */
 | |
| 		entry->type= F_CHR;
 | |
| 		attr= A_MODE1 | A_OWNER | A_DEV;
 | |
| 		break;
 | |
| 	case 'p':
 | |
| 		/* Named pipe. */
 | |
| 		entry->type= F_PIPE;
 | |
| 		attr= A_MODE1 | A_OWNER;
 | |
| 		break;
 | |
| 	case '-':
 | |
| 		/* Symlink. */
 | |
| 		entry->type= F_LINK;
 | |
| 		attr= A_LINK;
 | |
| 		break;
 | |
| 	default:
 | |
| 		/* Normal file. */
 | |
| 		entry->type= F_FILE;
 | |
| 		attr= A_MODE | A_OWNER | A_SIZETIME;
 | |
| 	}
 | |
| 
 | |
| 	if (attr & (A_MODE | A_MODE1)) {
 | |
| 		entry->mode= strtoul(argv[0] + (attr & A_MODE1), nil, 010);
 | |
| 	}
 | |
| 	i= 1;
 | |
| 	if (attr & A_OWNER) {
 | |
| 		if (i + 2 > argc) return 0;
 | |
| 		entry->uid= strtoul(argv[i++], nil, 10);
 | |
| 		entry->gid= strtoul(argv[i++], nil, 10);
 | |
| 	}
 | |
| 	if (attr & A_SIZETIME) {
 | |
| 		if (i + 2 > argc) return 0;
 | |
| 		entry->size= strtoul(argv[i++], nil, 10);
 | |
| 		entry->mtime= strtoul(argv[i++], nil, 10);
 | |
| 	}
 | |
| 	if (attr & A_DEV) {
 | |
| 		if (i + 1 > argc) return 0;
 | |
| 		entry->rdev= strtoul(argv[i++], nil, 0x10);
 | |
| 	}
 | |
| 	if (attr & A_LINK) {
 | |
| 		if (i + 1 > argc) return 0;
 | |
| 		entry->link= argv[i++];
 | |
| 	}
 | |
| 	entry->linked= entry->lastlink= 0;
 | |
| 	if (i < argc) {
 | |
| 		/* It has a fake inode number, so it is a hard link. */
 | |
| 		static struct links {	/* List of hard links. */
 | |
| 			struct links	*next;
 | |
| 			unsigned long	fake_ino;
 | |
| 			char		*path;
 | |
| 		} *links[1024];
 | |
| 		struct links **plp, *lp;
 | |
| 		unsigned long fake_ino;
 | |
| 
 | |
| 		fake_ino= strtoul(argv[i++], nil, 10);
 | |
| 
 | |
| 		plp= &links[fake_ino % arraysize(links)];
 | |
| 		while ((lp= *plp) != nil && lp->fake_ino != fake_ino)
 | |
| 			plp= &lp->next;
 | |
| 
 | |
| 		if (lp == nil) {
 | |
| 			/* New link. */
 | |
| 			*plp= lp= allocate(nil, sizeof(*lp));
 | |
| 			lp->next= nil;
 | |
| 			lp->fake_ino= fake_ino;
 | |
| 			lp->path= copystr(entry->path);
 | |
| 		} else {
 | |
| 			/* Linked to. */
 | |
| 			entry->link= lp->path;
 | |
| 			entry->linked= 1;
 | |
| 		}
 | |
| 
 | |
| 		if (i < argc) {
 | |
| 			if (strcmp(argv[i++], "last") != 0) return 0;
 | |
| 
 | |
| 			/* Last hard link of a file. */
 | |
| 			forget(lp->path);
 | |
| 			*plp= lp->next;
 | |
| 			deallocate(lp);
 | |
| 			entry->lastlink= 1;
 | |
| 		}
 | |
| 	}
 | |
| 	if (i != argc) return 0;
 | |
| 	return 1;
 | |
| }
 | |
| 
 | |
| void state_syntax(off_t line)
 | |
| {
 | |
| 	fprintf(stderr, "remsync: %s: syntax error on line %lu\n",
 | |
| 		state_file, (unsigned long) line);
 | |
| 	exit(1);
 | |
| }
 | |
| 
 | |
| entry_t *readstate(void)
 | |
| /* Read one entry from the state file. */
 | |
| {
 | |
| 	static entry_t entry;
 | |
| 	static pathname_t path;
 | |
| 	static size_t *trunc;
 | |
| 	static size_t trunc_len;
 | |
| 	static base_indent;
 | |
| 	char *line;
 | |
| 	char **argv;
 | |
| 	size_t argc;
 | |
| 	static off_t lineno;
 | |
| 	int indent, depth;
 | |
| 
 | |
| recurse:
 | |
| 	keep= KEEP_STATE;
 | |
| 
 | |
| 	if (feof(statefp) || (line= read1line(statefp)) == nil) {
 | |
| 		checkstate();
 | |
| 		return nil;
 | |
| 	}
 | |
| 	lineno++;
 | |
| 
 | |
| 	/* How far is this entry indented? */
 | |
| 	indent= 0;
 | |
| 	while (*line != 0) {
 | |
| 		if (*line == ' ') indent++;
 | |
| 		else
 | |
| 		if (*line == '\t') indent= (indent + 8) & ~7;
 | |
| 		else
 | |
| 			break;
 | |
| 		line++;
 | |
| 	}
 | |
| 	if (indent > 0 && base_indent == 0) base_indent= indent;
 | |
| 	depth= (base_indent == 0 ? 0 : indent / base_indent) + 1;
 | |
| 
 | |
| 	if (entry.ignore && depth > entry.depth) {
 | |
| 		/* If the old directory is ignored, then so are its entries. */
 | |
| 		goto recurse;
 | |
| 	}
 | |
| 	entry.depth= depth;
 | |
| 
 | |
| 	splitline(line, &argv, &argc);
 | |
| 	if (argc < 2) state_syntax(lineno);
 | |
| 
 | |
| 	if (trunc == nil) {
 | |
| 		/* The root of the tree, initialize path. */
 | |
| 		if (argv[0][0] != '/') state_syntax(lineno);
 | |
| 		path_init(&path);
 | |
| 		path_add(&path, "/");
 | |
| 		trunc= allocate(nil, (trunc_len= 16) * sizeof(trunc[0]));
 | |
| 
 | |
| 		/* The root has depth 0. */
 | |
| 		entry.depth= 0;
 | |
| 		trunc[0]= 0;
 | |
| 	} else {
 | |
| 		if (entry.depth > trunc_len) {
 | |
| 			trunc= allocate(trunc,
 | |
| 					(trunc_len*= 2) * sizeof(trunc[0]));
 | |
| 		}
 | |
| 		path_trunc(&path, trunc[entry.depth - 1]);
 | |
| 		path_add(&path, argv[0]);
 | |
| 		trunc[entry.depth]= path_length(&path);
 | |
| 	}
 | |
| 
 | |
| 	entry.path= path_name(&path);
 | |
| 	entry.name= argv[0];
 | |
| 	entry.link= nil;
 | |
| 	if ((entry.ignore= strcmp(argv[1], "ignore") == 0)) {
 | |
| 		return &entry;
 | |
| 	}
 | |
| 	if (!getattributes(&entry, argc - 1, argv + 1)) state_syntax(lineno);
 | |
| 	return &entry;
 | |
| }
 | |
| 
 | |
| void checkdiff(void)
 | |
| {
 | |
| 	if (ferror(difffp)) fatal(diff_file);
 | |
| }
 | |
| 
 | |
| enum { DELETE, REPLACE, COPY, SIMILAR, EQUAL, ADD }
 | |
| compare(entry_t *remote, entry_t *local)
 | |
| /* Compare the local and remote entries and tell what need to be done. */
 | |
| {
 | |
| 	int cmp;
 | |
| 
 | |
| 	/* Surplus entries? */
 | |
| 	if (local == nil) return DELETE;
 | |
| 	if (remote == nil) return ADD;
 | |
| 
 | |
| 	/* Extra directory entries? */
 | |
| 	if (remote->depth > local->depth) return DELETE;
 | |
| 	if (local->depth > remote->depth) return ADD;
 | |
| 
 | |
| 	/* Compare names. */
 | |
| 	cmp= strcmp(remote->name, local->name);
 | |
| 	if (cmp < 0) return DELETE;
 | |
| 	if (cmp > 0) return ADD;
 | |
| 
 | |
| 	/* The files have the same name.  Ignore one, ignore the other. */
 | |
| 	if (remote->ignore || local->ignore) {
 | |
| 		remote->ignore= local->ignore= 1;
 | |
| 		return EQUAL;
 | |
| 	}
 | |
| 
 | |
| 	/* Reasons for replacement? */
 | |
| 	if (remote->type != local->type) return REPLACE;
 | |
| 
 | |
| 	/* Should be hard linked to the same file. */
 | |
| 	if (remote->linked || local->linked) {
 | |
| 		if (!remote->linked || !local->linked) return REPLACE;
 | |
| 		if (strcmp(remote->link, local->link) != 0) return REPLACE;
 | |
| 	}
 | |
| 
 | |
| 	switch (remote->type) {
 | |
| 	case F_FILE:
 | |
| 		if (uflag) {
 | |
| 			if (remote->mtime < local->mtime) return COPY;
 | |
| 		} else {
 | |
| 			if (remote->size != local->size
 | |
| 					|| remote->mtime != local->mtime)
 | |
| 				return COPY;
 | |
| 		}
 | |
| 		goto check_modes;
 | |
| 	case F_BLK:
 | |
| 	case F_CHR:
 | |
| 		if (remote->rdev != local->rdev) return REPLACE;
 | |
| 		goto check_modes;
 | |
| 	case F_DIR:
 | |
| 	case F_PIPE:
 | |
| 	check_modes:
 | |
| 		if (remote->mode != local->mode
 | |
| 			|| remote->uid != local->uid
 | |
| 			|| remote->gid != local->gid) return SIMILAR;
 | |
| 		break;
 | |
| 	case F_LINK:
 | |
| 		if (strcmp(remote->link, local->link) != 0) return REPLACE;
 | |
| 		break;
 | |
| 	}
 | |
| 	return EQUAL;
 | |
| }
 | |
| 
 | |
| void delete(entry_t *old)
 | |
| /* Emit an instruction to remove an entry. */
 | |
| {
 | |
| 	if (old->ignore) return;
 | |
| 	if (uflag) return;
 | |
| 
 | |
| 	fprintf(difffp, "rm ");
 | |
| 	checkdiff();
 | |
| 	if (!print_name(difffp, old->path)) checkdiff();
 | |
| 	if (putc('\n', difffp) == EOF) checkdiff();
 | |
| 	if (vflag) fprintf(stderr, "rm %s\n", old->path);
 | |
| }
 | |
| 
 | |
| void change_modes(entry_t *old, entry_t *new)
 | |
| /* Emit an instruction to change the attributes of an entry. */
 | |
| {
 | |
| 	if (new->ignore) return;
 | |
| 
 | |
| 	fprintf(difffp, "chmod ");
 | |
| 	checkdiff();
 | |
| 	if (!print_name(difffp, new->path)) checkdiff();
 | |
| 	fprintf(difffp, " %03o %u %u\n",
 | |
| 		(unsigned) new->mode,
 | |
| 		(unsigned) new->uid, (unsigned) new->gid);
 | |
| 	checkdiff();
 | |
| 	if (vflag && old->mode != new->mode) {
 | |
| 		fprintf(stderr, "chmod %s %03o %u %u\n",
 | |
| 			new->path,
 | |
| 			(unsigned) new->mode,
 | |
| 			(unsigned) new->uid, (unsigned) new->gid);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| int cat(int f, off_t size)
 | |
| /* Include the contents of a file in the differences file. */
 | |
| {
 | |
| 	ssize_t n;
 | |
| 	unsigned char buf[1024 << sizeof(int)];
 | |
| 	unsigned char *p;
 | |
| 	int c;
 | |
| 
 | |
| 	if (Dflag) return 1;	/* Debug: Don't need the file contents. */
 | |
| 
 | |
| 	while ((n= read(f, buf, sizeof(buf))) > 0) {
 | |
| 		p= buf;
 | |
| 		do {
 | |
| 			if (size == 0) {
 | |
| 				/* File suddenly larger. */
 | |
| 				errno= EINVAL;
 | |
| 				return 0;
 | |
| 			}
 | |
| 			c= *p++;
 | |
| 			if (putc(c, difffp) == EOF) checkdiff();
 | |
| 			size--;
 | |
| 		} while (--n != 0);
 | |
| 	}
 | |
| 	if (size > 0) {
 | |
| 		int err= errno;
 | |
| 
 | |
| 		/* File somehow shrunk, pad it out. */
 | |
| 		do {
 | |
| 			if (putc(0, difffp) == EOF) checkdiff();
 | |
| 		} while (--size != 0);
 | |
| 		errno= n == 0 ? EINVAL : err;
 | |
| 		n= -1;
 | |
| 	}
 | |
| 	return n == 0;
 | |
| }
 | |
| 
 | |
| void add(entry_t *old, entry_t *new)
 | |
| /* Emit an instruction to add an entry. */
 | |
| {
 | |
| 	pathname_t file;
 | |
| 	int f;
 | |
| 
 | |
| 	if (new->ignore) return;
 | |
| 
 | |
| 	if (new->linked) {
 | |
| 		/* This file is to be a hard link to an existing file. */
 | |
| 		fprintf(difffp, "ln ");
 | |
| 		checkdiff();
 | |
| 		if (!print_name(difffp, new->link)) checkdiff();
 | |
| 		if (fputc(' ', difffp) == EOF) checkdiff();
 | |
| 		if (!print_name(difffp, new->path)) checkdiff();
 | |
| 		if (fputc('\n', difffp) == EOF) checkdiff();
 | |
| 		if (vflag) {
 | |
| 			fprintf(stderr, "ln %s %s\n", new->link, new->path);
 | |
| 		}
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	/* Add some other type of file. */
 | |
| 	fprintf(difffp, "add ");
 | |
| 	checkdiff();
 | |
| 	if (!print_name(difffp, new->path)) checkdiff();
 | |
| 
 | |
| 	switch (new->type) {
 | |
| 	case F_DIR:
 | |
| 		fprintf(difffp, " d%03o %u %u\n",
 | |
| 			(unsigned) new->mode,
 | |
| 			(unsigned) new->uid, (unsigned) new->gid);
 | |
| 		if (vflag) fprintf(stderr, "mkdir %s\n", new->path);
 | |
| 		break;
 | |
| 	case F_FILE:
 | |
| 		path_init(&file);
 | |
| 		path_add(&file, tree);
 | |
| 		path_add(&file, new->path);
 | |
| 		if ((f= open(path_name(&file), O_RDONLY)) < 0) {
 | |
| 			report(path_name(&file));
 | |
| 			path_drop(&file);
 | |
| 			fprintf(difffp, " ignore\n");
 | |
| 			break;
 | |
| 		}
 | |
| 		fprintf(difffp, " %03o %u %u %lu %lu\n",
 | |
| 			(unsigned) new->mode,
 | |
| 			(unsigned) new->uid, (unsigned) new->gid,
 | |
| 			(unsigned long) new->size,
 | |
| 			(unsigned long) new->mtime);
 | |
| 		checkdiff();
 | |
| 		if (!cat(f, new->size)) {
 | |
| 			int err= errno;
 | |
| 			report(path_name(&file));
 | |
| 			fprintf(difffp, "old ");
 | |
| 			checkdiff();
 | |
| 			print_name(difffp, err == EINVAL
 | |
| 				? "File changed when copied" : strerror(err));
 | |
| 			fputc('\n', difffp);
 | |
| 			checkdiff();
 | |
| 		} else {
 | |
| 			if (vflag) {
 | |
| 				fprintf(stderr, "%s %s\n",
 | |
| 					old == nil ? "add" :
 | |
| 						old->mtime > new->mtime ?
 | |
| 							"restore" : "update",
 | |
| 					new->path);
 | |
| 			}
 | |
| 		}
 | |
| 		close(f);
 | |
| 		path_drop(&file);
 | |
| 		break;
 | |
| 	case F_BLK:
 | |
| 	case F_CHR:
 | |
| 		fprintf(difffp, " %c%03o %u %u %lx\n",
 | |
| 			new->type == F_BLK ? 'b' : 'c',
 | |
| 			(unsigned) new->mode,
 | |
| 			(unsigned) new->uid, (unsigned) new->gid,
 | |
| 			(unsigned long) new->rdev);
 | |
| 		if (vflag) fprintf(stderr, "mknod %s\n", new->path);
 | |
| 		break;
 | |
| 	case F_PIPE:
 | |
| 		fprintf(difffp, " p%03o %u %u\n",
 | |
| 			(unsigned) new->mode,
 | |
| 			(unsigned) new->uid, (unsigned) new->gid);
 | |
| 		if (vflag) fprintf(stderr, "mkfifo %s\n", new->path);
 | |
| 		break;
 | |
| 	case F_LINK:
 | |
| 		fprintf(difffp, " -> ");
 | |
| 		checkdiff();
 | |
| 		(void) print_name(difffp, new->link);
 | |
| 		checkdiff();
 | |
| 		fputc('\n', difffp);
 | |
| 		if (vflag) {
 | |
| 			fprintf(stderr, "ln -s %s %s\n", new->link, new->path);
 | |
| 		}
 | |
| 		break;
 | |
| 	}
 | |
| 	checkdiff();
 | |
| }
 | |
| 
 | |
| void mkdifferences(void)
 | |
| {
 | |
| 	entry_t *remote;
 | |
| 	entry_t *local;
 | |
| 
 | |
| 	remote= readstate();
 | |
| 	local= traverse();
 | |
| 
 | |
| 	while (remote != nil || local != nil) {
 | |
| 		switch (compare(remote, local)) {
 | |
| 		case DELETE:
 | |
| 			/* Remove the remote file. */
 | |
| 			delete(remote);
 | |
| 			remote->ignore= 1;
 | |
| 			remote= readstate();
 | |
| 			break;
 | |
| 		case REPLACE:
 | |
| 			/* Replace the remote file with the local one. */
 | |
| 			if (remote->type == F_FILE && local->type == F_FILE
 | |
| 							&& !local->linked) {
 | |
| 				/* Don't overwrite, remove first. */
 | |
| 				delete(remote);
 | |
| 			}
 | |
| 			/*FALL THROUGH*/
 | |
| 		case COPY:
 | |
| 			/* Overwrite the remote file with the local one. */
 | |
| 			add(remote, local);
 | |
| 			remote->ignore= 1;
 | |
| 			goto skip2;
 | |
| 		case SIMILAR:
 | |
| 			/* About the same, but the attributes need changing. */
 | |
| 			change_modes(remote, local);
 | |
| 			goto skip2;
 | |
| 		case EQUAL:
 | |
| 		skip2:
 | |
| 			/* Skip two files. */
 | |
| 			remote= readstate();
 | |
| 			local= traverse();
 | |
| 			break;
 | |
| 		case ADD:
 | |
| 			/* Add the local file. */
 | |
| 			add(nil, local);
 | |
| 			local= traverse();
 | |
| 			break;
 | |
| 		}
 | |
| 	}
 | |
| 	fprintf(difffp, "end\n");
 | |
| 	fflush(difffp);
 | |
| 	checkdiff();
 | |
| }
 | |
| 
 | |
| void apply_remove(pathname_t *pp)
 | |
| /* Remove an obsolete file. */
 | |
| {
 | |
| 	struct stat st;
 | |
| 
 | |
| 	if (lstat(path_name(pp), &st) < 0) {
 | |
| 		if (errno != ENOENT) report(path_name(pp));
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	if (S_ISDIR(st.st_mode)) {
 | |
| 		/* Recursively delete directories. */
 | |
| 		size_t len;
 | |
| 		namelist_t *entries;
 | |
| 
 | |
| 		if ((entries= collect(path_name(pp))) == nil && errno != 0) {
 | |
| 			report(path_name(pp));
 | |
| 			return;
 | |
| 		}
 | |
| 		len= path_length(pp);
 | |
| 
 | |
| 		while (entries != nil) {
 | |
| 			path_add(pp, pop_name(&entries));
 | |
| 			apply_remove(pp);
 | |
| 			path_trunc(pp, len);
 | |
| 		}
 | |
| 		if (rmdir(path_name(pp)) < 0) {
 | |
| 			report(path_name(pp));
 | |
| 			return;
 | |
| 		}
 | |
| 		if (vflag) fprintf(stderr, "rmdir %s\n", path_name(pp));
 | |
| 	} else {
 | |
| 		/* Some other type of file. */
 | |
| 		if (unlink(path_name(pp)) < 0) {
 | |
| 			report(path_name(pp));
 | |
| 			return;
 | |
| 		}
 | |
| 		if (vflag) fprintf(stderr, "rm %s\n", path_name(pp));
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void apply_mkold(const char *file, const char *err)
 | |
| /* Make a file very old.  (An error occurred when it was added.) */
 | |
| {
 | |
| 	struct utimbuf utb;
 | |
| 
 | |
| 	utb.actime= utb.modtime= 0;
 | |
| 	if (utime(file, &utb) < 0) {
 | |
| 		report(file);
 | |
| 		return;
 | |
| 	}
 | |
| 	fprintf(stderr, "made %s look old", file);
 | |
| 	fprintf(stderr, err == nil ? "\n" : " due to a remote problem: %s\n",
 | |
| 								err);
 | |
| }
 | |
| 
 | |
| void apply_chmod(const char *file, mode_t mode, uid_t uid, gid_t gid, int talk)
 | |
| /* Change mode and ownership. */
 | |
| {
 | |
| 	struct stat st;
 | |
| 
 | |
| 	if (lstat(file, &st) < 0) {
 | |
| 		report(file);
 | |
| 		return;
 | |
| 	}
 | |
| 	if ((st.st_mode & 07777) != mode) {
 | |
| 		if (chmod(file, mode) < 0) {
 | |
| 			report(file);
 | |
| 			return;
 | |
| 		}
 | |
| 		if (vflag && talk) {
 | |
| 			fprintf(stderr, "chmod %03o %s\n",
 | |
| 						(unsigned) mode, file);
 | |
| 		}
 | |
| 	}
 | |
| 	if (st.st_uid != uid || st.st_gid != gid) {
 | |
| 		if (chown(file, uid, gid) < 0) {
 | |
| 			if (errno != EPERM) report(file);
 | |
| 			return;
 | |
| 		}
 | |
| 		if (vflag && talk) {
 | |
| 			fprintf(stderr, "chown %u:%u %s\n",
 | |
| 				(unsigned) uid, (unsigned) gid, file);
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void apply_add(pathname_t *pp, entry_t *entry)
 | |
| /* Add or replace a file. */
 | |
| {
 | |
| 	const char *file;
 | |
| 	off_t size;
 | |
| 	int f;
 | |
| 	unsigned char buf[1024 << sizeof(int)];
 | |
| 	unsigned char *p;
 | |
| 	int c;
 | |
| 	int dirty;
 | |
| 	struct stat st;
 | |
| 	struct utimbuf utb;
 | |
| 
 | |
| 	if (entry->ignore) return;
 | |
| 
 | |
| 	if (lstat(path_name(pp), &st) >= 0 && (entry->type != F_FILE
 | |
| 					|| !S_ISREG(st.st_mode))) {
 | |
| 		apply_remove(pp);
 | |
| 	}
 | |
| 
 | |
| 	file= path_name(pp);
 | |
| 
 | |
| 	switch (entry->type) {
 | |
| 	case F_DIR:
 | |
| 		if (mkdir(file, entry->mode) < 0) {
 | |
| 			report(file);
 | |
| 			return;
 | |
| 		}
 | |
| 		if (vflag) fprintf(stderr, "mkdir %s\n", file);
 | |
| 		break;
 | |
| 	case F_FILE:
 | |
| 		size= entry->size;
 | |
| 
 | |
| 		f= -1;
 | |
| 		st.st_mode= 0;
 | |
| 		if (lstat(file, &st) < 0 || S_ISREG(st.st_mode)) {
 | |
| 			f= open(file, O_WRONLY | O_CREAT | O_TRUNC,
 | |
| 						entry->mode);
 | |
| 			if (f < 0) {
 | |
| 				(void) chmod(file, entry->mode | 0200);
 | |
| 				f= open(file, O_WRONLY | O_CREAT | O_TRUNC,
 | |
| 						entry->mode);
 | |
| 			}
 | |
| 			if (f < 0) {
 | |
| 				(void) unlink(file);
 | |
| 				f= open(file, O_WRONLY | O_CREAT | O_TRUNC,
 | |
| 						entry->mode);
 | |
| 			}
 | |
| 			if (f < 0) report(file);
 | |
| 		}
 | |
| 		dirty= (f >= 0);
 | |
| 		p= buf;
 | |
| 		while (size > 0 && (c= getc(difffp)) != EOF) {
 | |
| 			size--;
 | |
| 			*p++= c;
 | |
| 			if (p == arraylimit(buf) || size == 0) {
 | |
| 				if (f >= 0 && write(f, buf, p - buf) < 0) {
 | |
| 					report(file);
 | |
| 					close(f);
 | |
| 					f= -1;
 | |
| 				}
 | |
| 				p= buf;
 | |
| 			}
 | |
| 		}
 | |
| 		if (size > 0) {
 | |
| 			if (ferror(difffp)) report(diff_file);
 | |
| 			if (feof(difffp)) {
 | |
| 				fprintf(stderr, "remspec: %s: premature EOF\n",
 | |
| 					diff_file);
 | |
| 			}
 | |
| 			if (dirty) apply_mkold(file, nil);
 | |
| 			exit(1);
 | |
| 		}
 | |
| 		if (f < 0) {
 | |
| 			if (dirty) apply_mkold(file, nil);
 | |
| 			return;
 | |
| 		}
 | |
| 		close(f);
 | |
| 		if (vflag) {
 | |
| 			fprintf(stderr, st.st_mode == 0 ? "add %s\n"
 | |
| 				: entry->mtime >= st.st_mtime
 | |
| 					? "update %s\n" : "restore %s\n", file);
 | |
| 		}
 | |
| 		utb.actime= time(nil);
 | |
| 		utb.modtime= entry->mtime;
 | |
| 		if (utime(file, &utb) < 0) report(file);
 | |
| 		break;
 | |
| 	case F_BLK:
 | |
| 		if (mknod(file, S_IFBLK | entry->mode, entry->rdev) < 0) {
 | |
| 			report(file);
 | |
| 			return;
 | |
| 		}
 | |
| 		if (vflag) {
 | |
| 			fprintf(stderr, "mknod %s b %d %d\n", file,
 | |
| 				major(entry->rdev), minor(entry->rdev));
 | |
| 		}
 | |
| 		break;
 | |
| 	case F_CHR:
 | |
| 		if (mknod(file, S_IFCHR | entry->mode, entry->rdev) < 0) {
 | |
| 			report(file);
 | |
| 			return;
 | |
| 		}
 | |
| 		if (vflag) {
 | |
| 			fprintf(stderr, "mknod %s c %d %d\n", file,
 | |
| 				major(entry->rdev), minor(entry->rdev));
 | |
| 		}
 | |
| 		break;
 | |
| 	case F_PIPE:
 | |
| 		if (mknod(file, S_IFIFO | entry->mode, 0) < 0) {
 | |
| 			report(file);
 | |
| 			return;
 | |
| 		}
 | |
| 		if (vflag) fprintf(stderr, "mknod %s p\n", file);
 | |
| 		break;
 | |
| 	case F_LINK:
 | |
| 		if (symlink(entry->link, file) < 0) {
 | |
| 			report(file);
 | |
| 			return;
 | |
| 		}
 | |
| 		if (vflag) fprintf(stderr, "ln -s %s %s\n", entry->link, file);
 | |
| 		return;
 | |
| 	}
 | |
| 	apply_chmod(file, entry->mode, entry->uid, entry->gid, 0);
 | |
| }
 | |
| 
 | |
| void apply_link(const char *file, pathname_t *pp)
 | |
| /* Hard link *pp to file. */
 | |
| {
 | |
| 	struct stat st1, st2;
 | |
| 
 | |
| 	if (lstat(file, &st1) < 0) {
 | |
| 		report(file);
 | |
| 		return;
 | |
| 	}
 | |
| 	if (lstat(path_name(pp), &st2) >= 0) {
 | |
| 		if (st1.st_ino == st2.st_ino && st1.st_dev == st2.st_dev)
 | |
| 			return;
 | |
| 		apply_remove(pp);
 | |
| 		if (lstat(path_name(pp), &st2) >= 0) return;
 | |
| 	}
 | |
| 	if (link(file, path_name(pp)) < 0) {
 | |
| 		fprintf(stderr, "remsync: ln %s %s: %s\n", file, path_name(pp),
 | |
| 			strerror(errno));
 | |
| 		excode= 1;
 | |
| 		return;
 | |
| 	}
 | |
| 	if (vflag) fprintf(stderr, "ln %s %s\n", file, path_name(pp));
 | |
| }
 | |
| 
 | |
| void diff_syntax(const char *line)
 | |
| {
 | |
| 	fprintf(stderr, "remsync: %s: syntax error on this line: %s\n",
 | |
| 		diff_file, line);
 | |
| 	exit(1);
 | |
| }
 | |
| 
 | |
| void apply_differences(void)
 | |
| /* Update a tree to a list of differences derived from a remote tree. */
 | |
| {
 | |
| 	char *line;
 | |
| 	char **argv;
 | |
| 	size_t argc;
 | |
| 	pathname_t path, link;
 | |
| 	size_t trunc;
 | |
| 
 | |
| 	path_init(&path);
 | |
| 	path_init(&link);
 | |
| 	path_add(&path, tree);
 | |
| 	path_add(&link, tree);
 | |
| 	trunc= path_length(&path);
 | |
| 
 | |
| 	while (!feof(difffp) && (line= read1line(difffp)) != nil) {
 | |
| 		splitline(line, &argv, &argc);
 | |
| 		if (argc == 0) diff_syntax(line);
 | |
| 
 | |
| 		path_trunc(&path, trunc);
 | |
| 
 | |
| 		if (strcmp(argv[0], "add") == 0) {
 | |
| 			entry_t entry;
 | |
| 
 | |
| 			if (argc < 3) diff_syntax(line);
 | |
| 			path_add(&path, argv[1]);
 | |
| 			entry.ignore= (strcmp(argv[2], "ignore") == 0);
 | |
| 			if (!entry.ignore && !getattributes(&entry,
 | |
| 							argc - 2, argv + 2))
 | |
| 				diff_syntax(line);
 | |
| 			apply_add(&path, &entry);
 | |
| 		} else
 | |
| 		if (strcmp(argv[0], "rm") == 0) {
 | |
| 			if (argc != 2) diff_syntax(line);
 | |
| 			path_add(&path, argv[1]);
 | |
| 			apply_remove(&path);
 | |
| 		} else
 | |
| 		if (strcmp(argv[0], "ln") == 0) {
 | |
| 			if (argc != 3) diff_syntax(line);
 | |
| 			path_trunc(&link, trunc);
 | |
| 			path_add(&link, argv[1]);
 | |
| 			path_add(&path, argv[2]);
 | |
| 			apply_link(path_name(&link), &path);
 | |
| 		} else
 | |
| 		if (strcmp(argv[0], "chmod") == 0) {
 | |
| 			if (argc != 5) diff_syntax(line);
 | |
| 			path_add(&path, argv[1]);
 | |
| 			apply_chmod(path_name(&path),
 | |
| 				strtoul(argv[2], nil, 010),
 | |
| 				strtoul(argv[3], nil, 10),
 | |
| 				strtoul(argv[4], nil, 10),
 | |
| 				1);
 | |
| 		} else
 | |
| 		if (strcmp(argv[0], "old") == 0) {
 | |
| 			if (argc != 3) diff_syntax(line);
 | |
| 			path_add(&path, argv[1]);
 | |
| 			apply_mkold(path_name(&path), argv[2]);
 | |
| 		} else
 | |
| 		if (strcmp(argv[0], "end") == 0) {
 | |
| 			if (argc != 1) diff_syntax(line);
 | |
| 			break;
 | |
| 		} else {
 | |
| 			diff_syntax(line);
 | |
| 		}
 | |
| 	}
 | |
| 	checkdiff();
 | |
| }
 | |
| 
 | |
| void usage(void)
 | |
| {
 | |
|     fprintf(stderr, "Usage: remsync -sxv tree [state-file]\n");
 | |
|     fprintf(stderr, "       remsync -duxvD tree [state-file [diff-file]]\n");
 | |
|     fprintf(stderr, "       remsync [-xv] tree [diff-file]\n");
 | |
|     exit(1);
 | |
| }
 | |
| 
 | |
| int main(int argc, char **argv)
 | |
| {
 | |
| 	int i;
 | |
| 
 | |
| 	for (i= 1; i < argc && argv[i][0] == '-'; i++) {
 | |
| 		char *p= argv[i] + 1;
 | |
| 
 | |
| 		if (p[0] == '-' && p[1] == 0) { i++; break; }
 | |
| 
 | |
| 		while (*p != 0) {
 | |
| 			switch (*p++) {
 | |
| 			case 's':	sflag= 1; break;
 | |
| 			case 'd':	dflag= 1; break;
 | |
| 			case 'u':	uflag= 1; break;
 | |
| 			case 'x':	xflag= 1; break;
 | |
| 			case 'D':	Dflag= 1; break;
 | |
| 			case 'v':	vflag= 1; break;
 | |
| 			default:	usage();
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	if (sflag && dflag) usage();
 | |
| 	if (sflag && uflag) usage();
 | |
| 	if (!sflag && !dflag && uflag) usage();
 | |
| 	if (!dflag && Dflag) usage();
 | |
| 
 | |
| 	if (i == argc) usage();
 | |
| 	tree= argv[i++];
 | |
| 
 | |
| 	if (sflag) {
 | |
| 		/* Make a state file. */
 | |
| 		state_file= i < argc ? argv[i++] : "-";
 | |
| 		if (i != argc) usage();
 | |
| 
 | |
| 		statefp= stdout;
 | |
| 		if (strcmp(state_file, "-") != 0) {
 | |
| 			if ((statefp= fopen(state_file, "w")) == nil)
 | |
| 				fatal(state_file);
 | |
| 		}
 | |
| 		mkstatefile();
 | |
| 	} else
 | |
| 	if (dflag) {
 | |
| 		/* Make a file of differences. */
 | |
| 		state_file= i < argc ? argv[i++] : "-";
 | |
| 
 | |
| 		diff_file= i < argc ? argv[i++] : "-";
 | |
| 		if (i != argc) usage();
 | |
| 
 | |
| 		statefp= stdin;
 | |
| 		if (strcmp(state_file, "-") != 0) {
 | |
| 			if ((statefp= fopen(state_file, "r")) == nil)
 | |
| 				fatal(state_file);
 | |
| 		}
 | |
| 
 | |
| 		difffp= stdout;
 | |
| 		if (strcmp(diff_file, "-") != 0) {
 | |
| 			if ((difffp= fopen(diff_file, "w")) == nil)
 | |
| 				fatal(diff_file);
 | |
| 		}
 | |
| 		mkdifferences();
 | |
| 	} else {
 | |
| 		/* Apply a file of differences. */
 | |
| 		diff_file= i < argc ? argv[i++] : "-";
 | |
| 		if (i != argc) usage();
 | |
| 
 | |
| 		difffp= stdin;
 | |
| 		if (strcmp(diff_file, "-") != 0) {
 | |
| 			if ((difffp= fopen(diff_file, "r")) == nil)
 | |
| 				fatal(diff_file);
 | |
| 		}
 | |
| 		apply_differences();
 | |
| 	}
 | |
| 	exit(excode);
 | |
| }
 | 
