 8c434cf24f
			
		
	
	
		8c434cf24f
		
	
	
	
	
		
			
			. rename CANCEL to ORDER_CANCEL so the CANCEL enum name doesn't clash with a minix header file. also endianness test fix.
		
			
				
	
	
		
			1473 lines
		
	
	
		
			32 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			1473 lines
		
	
	
		
			32 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*	synctree 4.16 - Synchronise file tree.		Author: Kees J. Bot
 | |
|  *								5 Apr 1989
 | |
|  * SYNOPSYS
 | |
|  *	synctree [-iuf] [[user1@machine1:]dir1 [[user2@]machine2:]dir2
 | |
|  *
 | |
|  * Dir2 will then be synchronized to dir1 with respect to the flags.
 | |
|  * The flags mean:
 | |
|  *	-i  Be interactive on files other than directories too.
 | |
|  *	-u  Only install files that are newer, i.e. that need an update.
 | |
|  *	-f  Force.  Don't ask for confirmation, all answers are 'yes'.
 | |
|  *
 | |
|  * Hitting return lets synctree use its proposed answer.  Hitting CTRL-D is
 | |
|  * like typing return to all questions that follow.
 | |
|  *
 | |
|  * If either of the directories to be synchronized contains the file ".backup"
 | |
|  * then it is a backup directory.  The file ".backup" in this directory is
 | |
|  * an array of mode information indexed on inode number.
 | |
|  *
 | |
|  * 89/04/05, Kees J. Bot - Birth of tree synchronizing program.
 | |
|  * 92/02/02		 - General overhaul, rcp(1) like syntax.
 | |
|  */
 | |
| 
 | |
| #define nil 0
 | |
| #include <sys/types.h>
 | |
| #include <stddef.h>
 | |
| #include <stdio.h>
 | |
| #include <sys/stat.h>
 | |
| #include <utime.h>
 | |
| #include <string.h>
 | |
| #include <signal.h>
 | |
| #include <dirent.h>
 | |
| #include <errno.h>
 | |
| #include <fcntl.h>
 | |
| #include <stdlib.h>
 | |
| #include <unistd.h>
 | |
| #include <time.h>
 | |
| #include <sys/wait.h>
 | |
| 
 | |
| #define USE_SHADOWING 0
 | |
| 
 | |
| #ifndef PATH_MAX
 | |
| 
 | |
| #define PATH_MAX	1024
 | |
| #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
 | |
| 
 | |
| #define NUMBYTES     4	/* Any number fits in this many bytes. */
 | |
| #define CHUNK     4096	/* Transfer files in this size chunks. */
 | |
| 
 | |
| static int install= 0;	/* Install files, do not delete, update if newer. */
 | |
| static int interact= 0;	/* Ask permission to install too. */
 | |
| static int force= 0;	/* Force trees to be completely equal. */
 | |
| static int backup= 0;	/* This tree is for backup. */
 | |
| 
 | |
| static char SYNCNAME[]	= "synctree";
 | |
| static char SLAVENAME[]	= "==SLAVE==";
 | |
| static char MASTERNAME[]= "==MASTER==";
 | |
| 
 | |
| 
 | |
| static char BACKUP[] = ".backup";	/* Backup filemodes. */
 | |
| static int filemodes;			/* Filemodes fildes. */
 | |
| 
 | |
| static int chan[2]= { 0, 1 };	/* In and output channel to opposite side. */
 | |
| 
 | |
| #define BUCKSIZE (1+NUMBYTES+CHUNK)
 | |
| static char bucket[BUCKSIZE];	/* Put a lot of things here before sending. */
 | |
| static char *buckp= bucket;	/* Fill pointer. */
 | |
| static int buckn= 0;		/* # bytes in bucket. */
 | |
| 
 | |
| enum orders {	/* What back breaking labour should the slave perform? */
 | |
| 	ENTER= 128,	/* Make ready to process contents of directory. */
 | |
| 	ADVANCE,	/* Determine next pathname and report it back. */
 | |
| 	CAT,		/* Send contents of file. */
 | |
| 	MORE,		/* Send more file contents. */
 | |
| 	ORDER_CANCEL,		/* Current pathname is not installed, remove as link. */
 | |
| 	DIE,		/* Die with exit(0); */
 | |
| 	DIE_BAD,	/* exit(1); */
 | |
| 	POSITIVE,	/* Ask a yes/no question expecting yes. */
 | |
| 	NEGATIVE,	/* Same expecting no. */
 | |
| 	PASS_YES,	/* Pass this to the master will you. */
 | |
| 	PASS_NO		/* Same here. */
 | |
| };
 | |
| 
 | |
| #ifdef DEBUG
 | |
| char *ORDERS[]= {
 | |
| 	"ENTER", "ADVANCE", "CAT", "MORE", "CANCEL", "DIE", "DIE_BAD",
 | |
| 	"POSITIVE", "NEGATIVE", "PASS_YES", "PASS_NO"
 | |
| };
 | |
| #endif
 | |
| 
 | |
| enum answers {
 | |
| 	PATH= 128,	/* Report pathname, and stat(2) info. */
 | |
| 	LINK,		/* Report linkname, pathname, and stat(2) info. */
 | |
| 	DATA,		/* Contents of file follow. */
 | |
| 	NODATA,		/* Can't read file. */
 | |
| 	DONE,		/* Nothing more to advance to. */
 | |
| 	SYMLINK,	/* Report symlinkname, pathname, and stat(2) info. */
 | |
| 	YES, NO		/* Result of an ASK. */
 | |
| };
 | |
| 
 | |
| #ifdef DEBUG
 | |
| char *ANSWERS[]= {
 | |
| 	"PATH", "LINK", "DATA", "NODATA", "DONE", "SYMLINK", "YES", "NO"
 | |
| };
 | |
| 
 | |
| #define DPRINTF(format, arg)	fprintf(stderr, format, arg0, arg)
 | |
| #else
 | |
| #define DPRINTF(format, arg)
 | |
| #endif
 | |
| 
 | |
| struct mode {
 | |
| 	unsigned short	md_mode;
 | |
| 	unsigned short	md_uid;
 | |
| 	unsigned short	md_gid;
 | |
| 	unsigned short	md_rdev;
 | |
| 	unsigned short	md_devsiz;
 | |
| };
 | |
| 
 | |
| static char *arg0;	/* basename(argv[0]) */
 | |
| static int ex= 0;	/* exit status. */
 | |
| 
 | |
| static void because()
 | |
| {
 | |
| 	fprintf(stderr, ": %s\n", strerror(errno));
 | |
| 	ex= 1;
 | |
| }
 | |
| 
 | |
| static void perr(label) char *label;
 | |
| {
 | |
| 	fprintf(stderr, "%s: %s: %s\n", arg0, label, strerror(errno));
 | |
| 	ex= 1;
 | |
| }
 | |
| 
 | |
| static void perrx(label) char *label;
 | |
| {
 | |
| 	perr(label);
 | |
| 	exit(1);
 | |
| }
 | |
| 
 | |
| #if S_HIDDEN
 | |
| /* Support for per achitecture hidden files. */
 | |
| static int transparent= 0;
 | |
| 
 | |
| static void isvisible(name) char *name;
 | |
| {
 | |
| 	char *p= name + strlen(name);
 | |
| 
 | |
| 	while (p > name && *--p == '/') {}
 | |
| 
 | |
| 	if (p > name && *p == '@' && p[-1] != '/') transparent= 1;
 | |
| }
 | |
| #else
 | |
| #define transparent	0
 | |
| #define isvisible(name)	((void) 0)
 | |
| #endif
 | |
| 
 | |
| static void isbackup(slave) int slave;
 | |
| {
 | |
| 	if ((filemodes= open(BACKUP, slave ? O_RDONLY : O_RDWR)) >= 0)
 | |
| 		backup= 1;
 | |
| 	else {
 | |
| 		if (errno != ENOENT) perrx(BACKUP);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static char path[PATH_MAX+1];	/* Holds pathname of file being worked on. */
 | |
| static char lnkpth[PATH_MAX+1];	/* (Sym)link to path. */
 | |
| static char *linkpath;		/* What path is, or should be linked to. */
 | |
| static struct stat st;		/* Corresponding stat(2) info. */
 | |
| static char Spath[PATH_MAX+1];	/* Slave is looking at this. */
 | |
| static char Slnkpth[PATH_MAX+1];/* (Sym)link to Spath. */
 | |
| static char *Slinkpath=nil;	/* Either nil or Slnkpth. */
 | |
| static struct stat Sst;		/* Slave's stat(2). */
 | |
| 
 | |
| static char *addpath(p, n) char *p, *n;
 | |
| /* Add a name to the path, return pointer to end. */
 | |
| {
 | |
| 	if (p - path + 1 + strlen(n) > PATH_MAX) {
 | |
| 		*p= 0;
 | |
| 		fprintf(stderr, "%s: %s/%s is too long.\n", arg0, path, n);
 | |
| 		fprintf(stderr, "%s: Unable to continue.\n", arg0);
 | |
| 		exit(1);
 | |
| 	}
 | |
| 	if (p == path+1 && path[0] == '.') p= path;
 | |
| 
 | |
| 	if (p > path) *p++ = '/';
 | |
| 
 | |
| 	while (*n != 0) *p++ = *n++;
 | |
| 	*p= 0;
 | |
| 	return p;
 | |
| }
 | |
| 
 | |
| struct entry {	/* A directory entry. */
 | |
| 	struct entry	*next;	/* Next entry in same directory */
 | |
| 	struct entry	*dir;	/* It is part of this directory */
 | |
| 	struct entry	*con;	/* If a dir, its contents */
 | |
| 	char		*name;	/* Name of this dir entry */
 | |
| };
 | |
| 
 | |
| static struct entry *E= nil;		/* File being processed. */
 | |
| 
 | |
| static void setpath(e) struct entry *e;
 | |
| /* Set path leading to e. */
 | |
| {
 | |
| 	static char *pend;
 | |
| 
 | |
| 	if (e == nil)
 | |
| 		pend= path;
 | |
| 	else {
 | |
| 		setpath(e->dir);
 | |
| 		pend= addpath(pend, e->name);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static void sort(ae) struct entry **ae;
 | |
| /* This is either a stable mergesort, or thermal noise, I'm no longer sure.
 | |
|  * It must be called like this: if (L!=nil && L->next!=nil) sort(&L);
 | |
|  */
 | |
| {
 | |
| 	/* static */ struct entry *e1, **mid;  /* Need not be local */
 | |
| 	struct entry *e2;
 | |
| 
 | |
| 	e1= *(mid= &(*ae)->next);
 | |
| 	do {
 | |
| 		if ((e1= e1->next) == nil) break;
 | |
| 		mid= &(*mid)->next;
 | |
| 	} while ((e1= e1->next) != nil);
 | |
| 
 | |
| 	e2= *mid;
 | |
| 	*mid= nil;
 | |
| 
 | |
| 	if ((*ae)->next != nil) sort(ae);
 | |
| 	if (e2->next != nil) sort(&e2);
 | |
| 
 | |
| 	e1= *ae;
 | |
| 	for (;;) {
 | |
| 		if (strcmp(e1->name, e2->name)<=0) {
 | |
| 			if ((e1= *(ae= &e1->next)) == nil) {
 | |
| 				*ae= e2;
 | |
| 				break;
 | |
| 			}
 | |
| 		} else {
 | |
| 			*ae= e2;
 | |
| 			e2= *(ae= &e2->next);
 | |
| 			*ae= e1;
 | |
| 			if (e2 == nil) break;
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static void enter()
 | |
| /* Collect directory entries of E. */
 | |
| {
 | |
| 	struct entry **last= &E->con, *new;
 | |
| 	struct dirent *e;
 | |
| 	DIR *d;
 | |
| 
 | |
| 	if ((d= opendir(path)) == nil) {
 | |
| 		fprintf(stderr, "%s: Can't read dir %s\n", arg0, path);
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	while ((e= readdir(d)) != nil) {
 | |
| 		if (e->d_name[0] == '.' && (e->d_name[1] == 0
 | |
| 			|| (e->d_name[1] == '.' && e->d_name[2] == 0)
 | |
| 		)) continue;
 | |
| 
 | |
| 		new= (struct entry *) malloc(sizeof(*new));
 | |
| 
 | |
| 		new->next= nil;
 | |
| 		new->dir= E;
 | |
| 		new->con= nil;
 | |
| 		new->name= (char *) malloc(strlen(e->d_name) + 1);
 | |
| 		strcpy(new->name, e->d_name);
 | |
| 		*last= new;
 | |
| 		last= &new->next;
 | |
| 	}
 | |
| 	closedir(d);
 | |
| 	if (E->con != nil && E->con->next != nil) sort(&E->con);
 | |
| }
 | |
| 
 | |
| #define arraysize(a)	(sizeof(a) / sizeof((a)[0]))
 | |
| #define arraylimit(a)	((a) + arraysize(a))
 | |
| 
 | |
| static char *link_islink(struct stat *stp, const char *file)
 | |
| {
 | |
|     /* Tell if a file, which stat(2) information in '*stp', has been seen
 | |
|      * earlier by this function under a different name.  If not return a
 | |
|      * null pointer with errno set to ENOENT, otherwise return the name of
 | |
|      * the link.  Return a null pointer with an error code in errno for any
 | |
|      * error, using E2BIG for a too long file name.
 | |
|      *
 | |
|      * Use link_islink(nil, nil) to reset all bookkeeping.
 | |
|      *
 | |
|      * Call for a file twice to delete it from the store.
 | |
|      */
 | |
| 
 | |
|     typedef struct link {	/* In-memory link store. */
 | |
| 	struct link	*next;		/* Hash chain on inode number. */
 | |
| 	ino_t		ino;		/* File's inode number. */
 | |
| 	off_t		off;		/* Offset to more info in temp file. */
 | |
|     } link_t;
 | |
|     typedef struct dlink {	/* On-disk link store. */
 | |
| 	dev_t		dev;		/* Device number. */
 | |
| 	char		file[PATH_MAX];	/* Name of earlier seen link. */
 | |
|     } dlink_t;
 | |
|     static link_t *links[256];		/* Hash list of known links. */
 | |
|     static int tfd= -1;			/* Temp file for file name storage. */
 | |
|     static dlink_t dlink;
 | |
|     link_t *lp, **plp;
 | |
|     size_t len;
 | |
|     off_t off;
 | |
| 
 | |
|     if (file == nil) {
 | |
| 	/* Reset everything. */
 | |
| 	for (plp= links; plp < arraylimit(links); plp++) {
 | |
| 	    while ((lp= *plp) != nil) {
 | |
| 		*plp= lp->next;
 | |
| 		free(lp);
 | |
| 	    }
 | |
| 	}
 | |
| 	if (tfd != -1) close(tfd);
 | |
| 	tfd= -1;
 | |
| 	return nil;
 | |
|     }
 | |
| 
 | |
|     /* The file must be a non-directory with more than one link. */
 | |
|     if (S_ISDIR(stp->st_mode) || stp->st_nlink <= 1) {
 | |
| 	errno= ENOENT;
 | |
| 	return nil;
 | |
|     }
 | |
| 
 | |
|     plp= &links[stp->st_ino % arraysize(links)];
 | |
| 
 | |
|     while ((lp= *plp) != nil) {
 | |
| 	if (lp->ino == stp->st_ino) {
 | |
| 	    /* May have seen this link before.  Get it and check. */
 | |
| 	    if (lseek(tfd, lp->off, SEEK_SET) == -1) return nil;
 | |
| 	    if (read(tfd, &dlink, sizeof(dlink)) < 0) return nil;
 | |
| 
 | |
| 	    /* Only need to check the device number. */
 | |
| 	    if (dlink.dev == stp->st_dev) {
 | |
| 		if (strcmp(file, dlink.file) == 0) {
 | |
| 		    /* Called twice.  Forget about this link. */
 | |
| 		    *plp= lp->next;
 | |
| 		    free(lp);
 | |
| 		    errno= ENOENT;
 | |
| 		    return nil;
 | |
| 		}
 | |
| 
 | |
| 		/* Return the name of the earlier link. */
 | |
| 		return dlink.file;
 | |
| 	    }
 | |
| 	}
 | |
| 	plp= &lp->next;
 | |
|     }
 | |
| 
 | |
|     /* First time I see this link.  Add it to the store. */
 | |
|     if (tfd == -1) {
 | |
| 	for (;;) {
 | |
| 	    char *tmp;
 | |
| 
 | |
| 	    tmp= tmpnam(nil);
 | |
| 	    tfd= open(tmp, O_RDWR|O_CREAT|O_EXCL, 0600);
 | |
| 	    if (tfd < 0) {
 | |
| 		if (errno != EEXIST) return nil;
 | |
| 	    } else {
 | |
| 		(void) unlink(tmp);
 | |
| 		break;
 | |
| 	    }
 | |
| 	}
 | |
|     }
 | |
|     if ((len= strlen(file)) >= PATH_MAX) {
 | |
| 	errno= E2BIG;
 | |
| 	return nil;
 | |
|     }
 | |
| 
 | |
|     dlink.dev= stp->st_dev;
 | |
|     strcpy(dlink.file, file);
 | |
|     len += offsetof(dlink_t, file) + 1;
 | |
|     if ((off= lseek(tfd, 0, SEEK_END)) == -1) return nil;
 | |
|     if (write(tfd, &dlink, len) != len) return nil;
 | |
| 
 | |
|     if ((lp= malloc(sizeof(*lp))) == nil) return nil;
 | |
|     lp->next= nil;
 | |
|     lp->ino= stp->st_ino;
 | |
|     lp->off= off;
 | |
|     *plp= lp;
 | |
|     errno= ENOENT;
 | |
|     return nil;
 | |
| }
 | |
| 
 | |
| #define cancellink()	((void) islink())
 | |
| 
 | |
| static char *islink()
 | |
| /* Returns the name of the file path is linked to.  If no such link can be
 | |
|  * found, then path is added to the list and nil is returned.  If all the
 | |
|  * links of a file have been seen, then it is removed from the list.
 | |
|  * Directories are not seen as linkable.
 | |
|  */
 | |
| {
 | |
| 	char *name;
 | |
| 
 | |
| 	name= link_islink(&st, path);
 | |
| 	if (name == nil && errno != ENOENT) perrx(path);
 | |
| 	return name;
 | |
| }
 | |
| 
 | |
| static void setstat(ino, stp) ino_t ino; struct stat *stp;
 | |
| /* Set backup status info, we know that backup is true. */
 | |
| {
 | |
| 	struct mode md;
 | |
| 
 | |
| 	md.md_mode = stp->st_mode;
 | |
| 	md.md_uid = stp->st_uid;
 | |
| 	md.md_gid = stp->st_gid;
 | |
| 	md.md_rdev = stp->st_rdev;
 | |
| 	md.md_devsiz = stp->st_size / 1024;
 | |
| 
 | |
| 	if (lseek(filemodes, (off_t) sizeof(md) * (off_t) ino, 0) == -1
 | |
| 		|| write(filemodes, (char *) &md, sizeof(md)) != sizeof(md)
 | |
| 	) perrx(BACKUP);
 | |
| }
 | |
| 
 | |
| static int getstat(name, stp) char *name; struct stat *stp;
 | |
| /* Get status information of file name, skipping some files.  Backup info
 | |
|  * is inserted as needed.
 | |
|  */
 | |
| {
 | |
| 	errno= 0;
 | |
| 
 | |
| 	if (strcmp(name, BACKUP) == 0) return -1;
 | |
| 
 | |
| 	if (lstat(name, stp) < 0) return -1;
 | |
| 
 | |
| 	if (stp->st_mode == S_IFREG && stp->st_mtime == 0) return -1;
 | |
| 
 | |
| 	if (backup) {
 | |
| 		struct mode md;
 | |
| 
 | |
| 		if (lseek(filemodes,
 | |
| 			(off_t) sizeof(md) * (off_t) stp->st_ino, 0) == -1
 | |
| 		    || read(filemodes, (char *) &md, sizeof(md)) != sizeof(md)
 | |
| 		    || md.md_mode == 0
 | |
| 		) {
 | |
| 			errno= 0;
 | |
| 			setstat(stp->st_ino, stp);
 | |
| 		} else {
 | |
| 			stp->st_mode = md.md_mode;
 | |
| 			stp->st_uid = md.md_uid;
 | |
| 			stp->st_gid = md.md_gid;
 | |
| 			stp->st_rdev = md.md_rdev;
 | |
| 			if (S_ISBLK(stp->st_mode))
 | |
| 				stp->st_size= (off_t) md.md_devsiz * 1024;
 | |
| 		}
 | |
| 	}
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int advance()
 | |
| /* Determine next pathname, return true on success. */
 | |
| {
 | |
| 	for (;;) {
 | |
| 		if (E==nil) {	/* First call, enter root dir. */
 | |
| 			E= (struct entry *) malloc(sizeof(*E));
 | |
| 			E->dir= nil;
 | |
| 			E->con= nil;
 | |
| 			E->next= nil;
 | |
| 			E->name= (char *) malloc(3);
 | |
| 			strcpy(E->name, transparent ? ".@" : ".");
 | |
| 		} else
 | |
| 		if (E->con != nil)	/* Dir's files must be processed. */
 | |
| 			E= E->con;
 | |
| 		else {
 | |
| 			for (;;) {
 | |
| 				/* Remove E from it's parents list, then
 | |
| 				 * try next entry, if none, go to parent dir.
 | |
| 				 */
 | |
| 				struct entry *junk= E, *parent= E->dir;
 | |
| 
 | |
| 				if (parent != nil) parent->con= E->next;
 | |
| 				E= E->next;
 | |
| 				free(junk->name);
 | |
| 				free(junk);
 | |
| 
 | |
| 				if (E != nil) break;
 | |
| 
 | |
| 				if ((E= parent) == nil) return 0;
 | |
| 			}
 | |
| 		}
 | |
| 		setpath(E);
 | |
| 		if (getstat(path, &st) == 0) {
 | |
| 			if (S_ISLNK(st.st_mode)) {
 | |
| 				int n;
 | |
| 
 | |
| 				if ((n= readlink(path, lnkpth, PATH_MAX)) >= 0)
 | |
| 				{
 | |
| 					lnkpth[n]= 0;
 | |
| 					break;
 | |
| 				}
 | |
| 			} else {
 | |
| 				break;
 | |
| 			}
 | |
| 		}
 | |
| 		if (errno != 0 && errno != ENOENT) perr(path);
 | |
| 	}
 | |
| 
 | |
| 	linkpath= islink();
 | |
| 	DPRINTF("%s: path = %s\n", path);
 | |
| 	return 1;
 | |
| }
 | |
| 
 | |
| static enum orders request()
 | |
| /* Slave reads command sent by master. */
 | |
| {
 | |
| 	static char buf[64], *bp;
 | |
| 	static int n= 0;
 | |
| 	int req;
 | |
| 
 | |
| 	for (;;) {
 | |
| 		if (n == 0) {
 | |
| 			if ((n= read(chan[0], buf, (int) sizeof(buf))) <= 0) {
 | |
| 				if (n < 0) perrx("request()");
 | |
| 				/* Master died, try to report it then follow. */
 | |
| 				fprintf(stderr,
 | |
| 					"%s: Master died prematurely.\n", arg0);
 | |
| 				exit(1);
 | |
| 			}
 | |
| 			bp= buf;
 | |
| 		}
 | |
| 		req= *bp++ & 0xFF;
 | |
| 		n--;
 | |
| 		if (req >= (int) ENTER) break;
 | |
| 
 | |
| 		/* Master using slave to print to stdout: */
 | |
| 		putchar(req);
 | |
| 	}
 | |
| 	DPRINTF("%s: request() == %s\n", ORDERS[req - (int) ENTER]);
 | |
| 
 | |
| 	return (enum orders) req;
 | |
| }
 | |
| 
 | |
| static void report()
 | |
| {
 | |
| 	int r;
 | |
| 
 | |
| 	DPRINTF("%s: reporting now!\n", 0);
 | |
| 
 | |
| 	buckp= bucket;
 | |
| 
 | |
| 	while (buckn > 0) {
 | |
| 		r = buckn;
 | |
| 		if (r > (512 << sizeof(char*))) r= (512 << sizeof(char*));
 | |
| 
 | |
| 		if ((r= write(chan[1], buckp, r)) < 0) perrx("report()");
 | |
| 
 | |
| 		buckp += r;
 | |
| 		buckn -= r;
 | |
| 	}
 | |
| 	buckp= bucket;
 | |
| 	buckn= 0;
 | |
| }
 | |
| 
 | |
| static void inform(a) enum answers a;
 | |
| /* Slave replies to master. */
 | |
| {
 | |
| 	DPRINTF("%s: inform(%s)\n", ANSWERS[(int) a - (int) PATH]);
 | |
| 
 | |
| 	*buckp++ = (int) a;
 | |
| 	buckn++;
 | |
| }
 | |
| 
 | |
| #define wwrite(buf, n)	(memcpy(buckp, (buf), (n)), buckp+= (n), buckn+= (n))
 | |
| 
 | |
| static void sendnum(n) long n;
 | |
| /* Send number from least to most significant byte. */
 | |
| {
 | |
| #if BYTE_ORDER == LITTLE_ENDIAN
 | |
| 	wwrite((char *) &n, sizeof(n));
 | |
| #else
 | |
| 	char buf[NUMBYTES];
 | |
| 
 | |
| 	buf[0]= (char) (n >>  0);
 | |
| 	buf[1]= (char) (n >>  8);
 | |
| 	buf[2]= (char) (n >> 16);
 | |
| 	buf[3]= (char) (n >> 24);
 | |
| 	wwrite(buf, sizeof(buf));
 | |
| #endif
 | |
| }
 | |
| 
 | |
| static void send(buf, n) char *buf; int n;
 | |
| /* Slave sends size and contents of buf. */
 | |
| {
 | |
| 	sendnum((long) n);
 | |
| 	if (n > 0) wwrite(buf, (size_t) n);
 | |
| }
 | |
| 
 | |
| static void sendstat(stp) struct stat *stp;
 | |
| {
 | |
| 	sendnum((long) stp->st_mode);
 | |
| 	sendnum((long) stp->st_uid);
 | |
| 	sendnum((long) stp->st_gid);
 | |
| 	sendnum((long) stp->st_rdev);
 | |
| 	sendnum((long) stp->st_size);
 | |
| 	sendnum((long) stp->st_mtime);
 | |
| }
 | |
| 
 | |
| static int ask();
 | |
| 
 | |
| static void slave()
 | |
| /* Carry out orders from the master, such as transmitting path names.
 | |
|  * Note that the slave uses path, not Spath, the master uses Spath.
 | |
|  */
 | |
| {
 | |
| 	int f, n;
 | |
| 	char buf[CHUNK];
 | |
| 	enum { run, done, die } state= run;
 | |
| 
 | |
| 	do {
 | |
| 		switch (request()) {
 | |
| 		case ENTER:
 | |
| 			enter();
 | |
| 			break;
 | |
| 		case ADVANCE:
 | |
| 			if (!advance() || state == done) {
 | |
| 				inform(DONE);
 | |
| 				state= done;
 | |
| 			} else {
 | |
| 				if (linkpath!=nil) {
 | |
| 					inform(LINK);
 | |
| 					send(linkpath, strlen(linkpath) + 1);
 | |
| 				} else
 | |
| 				if (S_ISLNK(st.st_mode)) {
 | |
| 					inform(SYMLINK);
 | |
| 					send(lnkpth, strlen(lnkpth) + 1);
 | |
| 				} else {
 | |
| 					inform(PATH);
 | |
| 				}
 | |
| 				send(path, strlen(path) + 1);
 | |
| 				sendstat(&st);
 | |
| 			}
 | |
| 			break;
 | |
| 		case CAT:
 | |
| 			if ((f= open(path, O_RDONLY))<0) {
 | |
| 				fprintf(stderr, "%s: Can't open %s",
 | |
| 					arg0, path);
 | |
| 				because();
 | |
| 				inform(NODATA);
 | |
| 				break;
 | |
| 			}
 | |
| 			inform(DATA);
 | |
| 			do {
 | |
| 				n= read(f, buf, sizeof(buf));
 | |
| 				if (n < 0) perr(path);
 | |
| 				send(buf, n);
 | |
| 				if (n > 0) report();
 | |
| 			} while (n > 0);
 | |
| 			close(f);
 | |
| 			break;
 | |
| 		case ORDER_CANCEL:
 | |
| 			cancellink();
 | |
| 			break;
 | |
| 		case DIE_BAD:
 | |
| 			ex= 1;
 | |
| 			/*FALL THROUGH*/
 | |
| 		case DIE:
 | |
| 			state= die;
 | |
| 			break;
 | |
| 		case POSITIVE:
 | |
| 			inform(ask('y') ? YES : NO);
 | |
| 			break;
 | |
| 		case NEGATIVE:
 | |
| 			inform(ask('n') ? YES : NO);
 | |
| 			break;
 | |
| 		case PASS_YES:
 | |
| 			inform(YES);
 | |
| 			break;
 | |
| 		case PASS_NO:
 | |
| 			inform(NO);
 | |
| 			break;
 | |
| 		default:
 | |
| 			fprintf(stderr, "%s: strange request\n", arg0);
 | |
| 			exit(1);
 | |
| 		}
 | |
| 		report();
 | |
| 	} while (state != die);
 | |
| }
 | |
| 
 | |
| static int execute(argv) char **argv;
 | |
| /* Execute a command and return its success or failure. */
 | |
| {
 | |
| 	int pid, r, status;
 | |
| 
 | |
| 	if ((pid= fork())<0) {
 | |
| 		perr("fork()");
 | |
| 		return 0;
 | |
| 	}
 | |
| 	if (pid == 0) {
 | |
| 		execvp(argv[0], argv);
 | |
| 		perrx(argv[0]);
 | |
| 	}
 | |
| 	while ((r= wait(&status)) != pid) {
 | |
| 		if (r < 0) {
 | |
| 			perr(argv[0]);
 | |
| 			return 0;
 | |
| 		}
 | |
| 	}
 | |
| 	return status == 0;
 | |
| }
 | |
| 
 | |
| static int removedir(dir) char *dir;
 | |
| /* Remove a directory and its contents. */
 | |
| {
 | |
| 	static char *argv[] = { "rm", "-r", nil, nil };
 | |
| 
 | |
| 	printf("(rm -r %s)\n", dir);
 | |
| 
 | |
| 	argv[2]= dir;
 | |
| 	return execute(argv);
 | |
| }
 | |
| 
 | |
| static void order(o) enum orders o;
 | |
| /* Master tells slave what to do. */
 | |
| {
 | |
| 	char c= (char) o;
 | |
| 
 | |
| 	DPRINTF("%s: order(%s)\n", ORDERS[o - (int) ENTER]);
 | |
| 
 | |
| 	if (write(chan[1], &c, 1) != 1) perrx("order()");
 | |
| }
 | |
| 
 | |
| static void rread(buf, n) char *buf; int n;
 | |
| /* Master gets buf of size n from slave, doing multiple reads if needed. */
 | |
| {
 | |
| 	int r;
 | |
| 
 | |
| 	while (n > 0) {
 | |
| 		if (buckn == 0) {
 | |
| 			switch (buckn= read(chan[0], bucket, BUCKSIZE)) {
 | |
| 			case -1:
 | |
| 				perrx("reply channel from slave");
 | |
| 			case  0:
 | |
| 				fprintf(stderr,
 | |
| 					"%s: slave died prematurely.\n",
 | |
| 					arg0);
 | |
| 				exit(1);
 | |
| 			}
 | |
| 			buckp= bucket;
 | |
| 		}
 | |
| 		r= n < buckn ? n : buckn;
 | |
| 		memcpy(buf, buckp, r);
 | |
| 		buckp+= r;
 | |
| 		buckn-= r;
 | |
| 		buf+= r;
 | |
| 		n-= r;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static enum answers answer()
 | |
| /* Master reads slave's reply. */
 | |
| {
 | |
| 	char c;
 | |
| 	int a;
 | |
| 
 | |
| 	rread(&c, 1);
 | |
| 	a= c & 0xFF;
 | |
| 
 | |
| 	DPRINTF("%s: answer() == %s\n", ANSWERS[a - (int) PATH]);
 | |
| 
 | |
| 	return (enum answers) a;
 | |
| }
 | |
| 
 | |
| static long recnum()
 | |
| /* Read number as pack of bytes from least to most significant.  The data
 | |
|  * is on the wire in little-endian format.  (Mostly run on PC's).
 | |
|  */
 | |
| {
 | |
| #if BYTE_ORDER == LITTLE_ENDIAN
 | |
| 	long n;
 | |
| 
 | |
| 	rread((char *) &n, (int) sizeof(n));
 | |
| 	return n;
 | |
| #else
 | |
| 	unsigned char buf[NUMBYTES];
 | |
| 
 | |
| 	rread(buf, sizeof(buf));
 | |
| 	return	buf[0]
 | |
| 		| ((unsigned) buf[1] << 8)
 | |
| 		| ((unsigned long) buf[2] << 16)
 | |
| 		| ((unsigned long) buf[3] << 24);
 | |
| #endif
 | |
| }
 | |
| 
 | |
| static int receive(buf, max) char *buf; int max;
 | |
| /* Master get's data from slave, by first reading size, then data. */
 | |
| {
 | |
| 	int n;
 | |
| 
 | |
| 	n= recnum();
 | |
| 	if (n > max) {
 | |
| 		fprintf(stderr, "%s: panic: Can't read %d bytes\n", arg0, n);
 | |
| 		exit(1);
 | |
| 	}
 | |
| 	if (n > 0) rread(buf, n);
 | |
| 	return n;
 | |
| }
 | |
| 
 | |
| static void recstat(stp) struct stat *stp;
 | |
| {
 | |
| 	stp->st_mode= recnum();
 | |
| 	stp->st_uid= recnum();
 | |
| 	stp->st_gid= recnum();
 | |
| 	stp->st_rdev= recnum();
 | |
| 	stp->st_size= recnum();
 | |
| 	stp->st_mtime= recnum();
 | |
| }
 | |
| 
 | |
| static int key()
 | |
| {
 | |
| 	int c;
 | |
| 	static int tty= -1;
 | |
| 
 | |
| 	if (tty < 0) tty= isatty(0);
 | |
| 
 | |
| 	if (feof(stdin) || (c= getchar()) == EOF) {
 | |
| 		c= '\n';
 | |
| 		if (tty) putchar('\n');
 | |
| 	}
 | |
| 
 | |
| 	if (!tty) putchar(c);
 | |
| 
 | |
| 	return c;
 | |
| }
 | |
| 
 | |
| static int ask(def) int def;
 | |
| /* Ask for a yes or no, anything else means choose def. */
 | |
| {
 | |
| 	int y, c;
 | |
| 
 | |
| 	if (chan[0] == 0) {
 | |
| 		/* I'm running remote, tell the slave to ask. */
 | |
| 		fflush(stdout);
 | |
| 		order(def == 'y' ? POSITIVE : NEGATIVE);
 | |
| 		return answer() == YES;
 | |
| 	}
 | |
| 
 | |
| 	printf("? (%c) ", def);
 | |
| 	fflush(stdout);
 | |
| 
 | |
| 	do c= key(); while (c == ' ' || c == '\t');
 | |
| 
 | |
| 	y= c;
 | |
| 
 | |
| 	while (c != '\n') c= key();
 | |
| 
 | |
| 	if (y != 'y' && y != 'Y' && y != 'n' && y != 'N') y= def;
 | |
| 
 | |
| 	return y == 'y' || y == 'Y';
 | |
| }
 | |
| 
 | |
| static void setmodes(silent) int silent;
 | |
| {
 | |
| 	struct stat st;
 | |
| 	int change= 0;
 | |
| 	struct utimbuf tms;
 | |
| 
 | |
| 	errno= 0;
 | |
| 	getstat(Spath, &st);
 | |
| 	if (backup && silent) {
 | |
| 		setstat(st.st_ino, &Sst);
 | |
| 		getstat(Spath, &st);
 | |
| 	}
 | |
| 
 | |
| 	if (S_ISLNK(st.st_mode)) return;
 | |
| 
 | |
| 	if (errno == 0 && st.st_mode != Sst.st_mode) {
 | |
| 		if (!backup) chmod(Spath, Sst.st_mode & 07777);
 | |
| 		change= 1;
 | |
| 	}
 | |
| 	if (errno == 0
 | |
| 		&& (st.st_uid != Sst.st_uid || st.st_gid != Sst.st_gid)
 | |
| 		&& (backup || geteuid() == 0)
 | |
| 	) {
 | |
| 		errno= 0;
 | |
| 		if (!backup) chown(Spath, Sst.st_uid, Sst.st_gid);
 | |
| 		change= 1;
 | |
| 	}
 | |
| 
 | |
| 	if (backup && !silent) setstat(st.st_ino, &Sst);
 | |
| 
 | |
| 	if (errno == 0 && S_ISREG(Sst.st_mode) && st.st_mtime != Sst.st_mtime) {
 | |
| 		time(&tms.actime);
 | |
| 		tms.modtime= Sst.st_mtime;
 | |
| 		errno= 0;
 | |
| 		utime(Spath, &tms);
 | |
| 		change= 1;
 | |
| 	}
 | |
| 	if (errno != 0) {
 | |
| 		fprintf(stderr, "%s: Can't set modes of %s", arg0, Spath);
 | |
| 		because();
 | |
| 	} else
 | |
| 	if (change && !silent) {
 | |
| 		printf("Mode changed of %s\n", Spath);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static void makeold()
 | |
| {
 | |
| 	static struct utimbuf tms= { 0, 0 };
 | |
| 
 | |
| 	if (utime(Spath, &tms) < 0) {
 | |
| 		if (errno != ENOENT) {
 | |
| 			fprintf(stderr,
 | |
| 				"%s: can't make %s look old", arg0, Spath);
 | |
| 			because();
 | |
| 		}
 | |
| 	} else {
 | |
| 		fprintf(stderr, "%s: made %s look old.\n", arg0, Spath);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static int busy= 0;
 | |
| 
 | |
| static void bail_out(sig) int sig;
 | |
| {
 | |
| 	signal(sig, SIG_IGN);
 | |
| 
 | |
| 	fprintf(stderr, "%s: Exiting after signal %d\n", arg0, sig);
 | |
| 
 | |
| 	if (busy) {
 | |
| 		fprintf(stderr, "%s: was installing %s\n", arg0, Spath);
 | |
| 		makeold();
 | |
| 	}
 | |
| 	order(DIE_BAD);
 | |
| 
 | |
| 	exit(sig);
 | |
| }
 | |
| 
 | |
| static int makenode(name, mode, addr, size)
 | |
| 	char *name; int mode; dev_t addr; off_t size;
 | |
| {
 | |
| 	int r;
 | |
| 
 | |
| 	if (!backup) {
 | |
| 		r= mknod(name, mode, addr);
 | |
| 	} else {
 | |
| 		if ((r= creat(name, 0644)) >= 0) close(r);
 | |
| 	}
 | |
| 	return r;
 | |
| }
 | |
| 
 | |
| static void add(update) int update;
 | |
| /* Add Spath to the filesystem. */
 | |
| {
 | |
| 	int f, n;
 | |
| 	char buf[CHUNK];
 | |
| 	int forced_update= force && update;
 | |
| 
 | |
| 	if (Slinkpath != nil && !S_ISLNK(Sst.st_mode)) {
 | |
| 		if (interact && !update) {
 | |
| 			printf("Link %s to %s", Spath, Slinkpath);
 | |
| 			if (!ask('n')) return;
 | |
| 		}
 | |
| 		if (link(Slinkpath, Spath) >= 0) {
 | |
| 			printf("Linked %s to %s\n", Spath, Slinkpath);
 | |
| 			return;
 | |
| 		} else {
 | |
| 			fprintf(stderr,
 | |
| 				"%s: Can't link %s to %s",
 | |
| 				arg0, Slinkpath, Spath);
 | |
| 			because();
 | |
| 			/* Try to install instead. */
 | |
| 		}
 | |
| 	}
 | |
| 	switch (Sst.st_mode & S_IFMT) {
 | |
| 	case S_IFDIR:
 | |
| 		if (!force) {
 | |
| 			printf("Add dir %s", Spath);
 | |
| 			if (!ask('n')) return;
 | |
| 		}
 | |
| 		if (mkdir(Spath, backup ? 0755 : Sst.st_mode) < 0) {
 | |
| 			perr(Spath);
 | |
| 			return;
 | |
| 		}
 | |
| 		printf("Directory %s created.\n", Spath);
 | |
| 		order(ENTER);
 | |
| 		break;
 | |
| 	case S_IFBLK:
 | |
| 	case S_IFCHR:
 | |
| 	case S_IFIFO:
 | |
| 		if (interact && !update) {
 | |
| 			printf("Create special file %s", Spath);
 | |
| 			if (!ask('n')) { order(ORDER_CANCEL); return; }
 | |
| 		}
 | |
| 		if (makenode(Spath, Sst.st_mode, Sst.st_rdev, Sst.st_size)<0) {
 | |
| 			fprintf(stderr,
 | |
| 				"%s: Can't create special file %s",
 | |
| 				arg0, Spath);
 | |
| 			because();
 | |
| 			return;
 | |
| 		}
 | |
| 		printf("Special file %s created\n", Spath);
 | |
| 		break;
 | |
| #ifdef S_IFLNK
 | |
| 	case S_IFLNK:
 | |
| 		if (interact && !update) {
 | |
| 			printf("Install %s -> %s", Spath, Slnkpth);
 | |
| 			if (!ask('n')) { order(ORDER_CANCEL); return; }
 | |
| 		}
 | |
| 		if (symlink(Slnkpth, Spath) < 0) {
 | |
| 			fprintf(stderr, "%s: Can't create symlink %s",
 | |
| 				arg0, Spath);
 | |
| 			because();
 | |
| 			return;
 | |
| 		}
 | |
| 		printf("%s %s -> %s\n",
 | |
| 			forced_update ? "Updated:  " : "Installed:",
 | |
| 			Spath, Slnkpth);
 | |
| 		break;
 | |
| #endif
 | |
| 	case S_IFREG:
 | |
| 		if (interact && !update) {
 | |
| 			printf("Install %s", Spath);
 | |
| 			if (!ask('n')) { order(ORDER_CANCEL); return; }
 | |
| 		}
 | |
| 		order(CAT);
 | |
| 		if (answer() != DATA) return;
 | |
| 
 | |
| 		busy= 1;
 | |
| 		if ((f= creat(Spath, backup ? 0644 : Sst.st_mode&07777)) < 0) {
 | |
| 			busy= 0;
 | |
| 			fprintf(stderr, "%s: Can't create %s", arg0, Spath);
 | |
| 			because();
 | |
| 		}
 | |
| 
 | |
| 		while ((n= receive(buf, sizeof(buf)))>0) {
 | |
| 			if (f >= 0 && write(f, buf, n) != n) {
 | |
| 				fprintf(stderr, "%s: Write error on %s",
 | |
| 					arg0, Spath);
 | |
| 				because();
 | |
| 				close(f); f= -1;
 | |
| 			}
 | |
| 		}
 | |
| 		if (n < 0) {
 | |
| 			fprintf(stderr, "%s: Slave read err on %s\n",
 | |
| 				arg0, Spath);
 | |
| 		}
 | |
| 		if (f >= 0) close(f);
 | |
| 		if (n < 0 || f < 0) { makeold(); busy= 0; return; }
 | |
| 		busy= 0;
 | |
| 		printf("%s %s\n",
 | |
| 			forced_update ?
 | |
| 				Sst.st_mtime < st.st_mtime ? "Restored: " :
 | |
| 					"Updated:  " :
 | |
| 				"Installed:",
 | |
| 			Spath
 | |
| 		);
 | |
| 		break;
 | |
| 	default:
 | |
| 		fprintf(stderr,
 | |
| 			"%s: Won't add file with strange mode %05o: %s\n",
 | |
| 			arg0, Sst.st_mode, Spath);
 | |
| 		order(ORDER_CANCEL);
 | |
| 		return;
 | |
| 	}
 | |
| 	setmodes(1);
 | |
| }
 | |
| 
 | |
| static int delete(update) int update;
 | |
| /* Delete path. */
 | |
| {
 | |
| 	int forced_update= force && update;
 | |
| 
 | |
| 	if (S_ISDIR(st.st_mode)) {
 | |
| 		if (install) return 0;
 | |
| 		if (!force) {
 | |
| 			printf("Delete dir %s", path);
 | |
| 			if (!ask('n')) return 0;
 | |
| 		}
 | |
| 		if (!removedir(path)) { ex= 1; return 0; }
 | |
| 		if (!forced_update) printf("Directory %s deleted.\n", path);
 | |
| 		return 1;
 | |
| 	}
 | |
| 
 | |
| 	if (install && !update) return 0;
 | |
| 
 | |
| 	if (!force) {
 | |
| 		printf("Delete %s", path);
 | |
| 		if (!ask((interact && !update) ? 'n' : 'y')) return 0;
 | |
| 	}
 | |
| 
 | |
| 	if (unlink(path)<0) {
 | |
| 		fprintf(stderr, "Can't delete %s", path);
 | |
| 		because();
 | |
| 		return 0;
 | |
| 	}
 | |
| 	cancellink();
 | |
| 	if (!forced_update) printf("Deleted:   %s\n", path);
 | |
| 	return 1;
 | |
| }
 | |
| 
 | |
| static int different()
 | |
| /* Return true iff path and Spath are different. */
 | |
| {
 | |
| 	if (! ( (linkpath == nil && Slinkpath == nil)
 | |
| 		|| (linkpath != nil && Slinkpath != nil
 | |
| 			&& strcmp(linkpath, Slinkpath) == 0)
 | |
| 	)) {
 | |
| 		linkpath= Slinkpath;
 | |
| 		return 1;
 | |
| 	}
 | |
| 
 | |
| 	if ((st.st_mode & S_IFMT) != (Sst.st_mode & S_IFMT)) return 1;
 | |
| 
 | |
| 	switch (st.st_mode & S_IFMT) {
 | |
| 	case S_IFDIR:
 | |
| 		return 0;
 | |
| 	case S_IFBLK:
 | |
| 	case S_IFCHR:
 | |
| 		return st.st_rdev != Sst.st_rdev;
 | |
| 	case S_IFREG:
 | |
| 		if (install) return Sst.st_mtime > st.st_mtime;
 | |
| 		return st.st_size != Sst.st_size
 | |
| 			|| st.st_mtime != Sst.st_mtime;
 | |
| 	case S_IFIFO:	return 0;
 | |
| #ifdef S_IFLNK
 | |
| 	case S_IFLNK:	return strcmp(lnkpth, Slnkpth) != 0;
 | |
| #endif
 | |
| 	default:	return 1;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static void compare()
 | |
| /* See if path and Spath are same. */
 | |
| {
 | |
| 	if (different()) {
 | |
| 		if (!force) {
 | |
| 			printf("%sing %s (delete + add)\n",
 | |
| 				Sst.st_mtime < st.st_mtime ? "Restor" : "Updat",
 | |
| 				path);
 | |
| 		}
 | |
| 		if (delete(1)) add(1);
 | |
| 	} else {
 | |
| 		if (!install) setmodes(0);
 | |
| 
 | |
| 		if (S_ISDIR(st.st_mode)) {
 | |
| 			order(ENTER);
 | |
| 			enter();
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static int done= 0, Sdone= 0;
 | |
| 
 | |
| static enum action { ADD, COMPARE, DELETE } action()
 | |
| /* Look at path's of master and slave, compare them alphabetically to see
 | |
|  * who is ahead of who, then tell what is to be done.
 | |
|  */
 | |
| {
 | |
| 	int c;
 | |
| 	char *Sp, *p;
 | |
| 
 | |
| 	if (done) return ADD;		/* Slave still has names. */
 | |
| 	if (Sdone) return DELETE;	/* Master has too many names. */
 | |
| 
 | |
| 	/* Compare paths.  Let "a/a" come before "a.a". */
 | |
| 	Sp= Spath;
 | |
| 	p= path;
 | |
| 	while (*Sp == *p && *Sp != 0) { Sp++; p++; }
 | |
| 	if (*Sp == '/') return ADD;
 | |
| 	if (*p == '/') return DELETE;
 | |
| 	return (c= strcmp(Sp, p)) == 0 ? COMPARE : c < 0 ? ADD : DELETE;
 | |
| }
 | |
| 
 | |
| static void master()
 | |
| /* Synchronise file tree to that of its slave. */
 | |
| {
 | |
| 	enum action a= COMPARE;	/* Trick first advances. */
 | |
| 
 | |
| 	umask(backup ? 0022 : 0000);
 | |
| 
 | |
| 	signal(SIGPIPE, SIG_IGN);
 | |
| 	signal(SIGHUP, bail_out);
 | |
| 	signal(SIGINT, bail_out);
 | |
| 	signal(SIGTERM, bail_out);
 | |
| 
 | |
| 	while (!done || !Sdone) {
 | |
| 		if (!Sdone && (a == ADD || a == COMPARE)) {
 | |
| 			/* Slave advances. */
 | |
| 			order(ADVANCE);
 | |
| 			switch (answer()) {
 | |
| 			case PATH:
 | |
| 				Slinkpath= nil;
 | |
| 				receive(Spath, sizeof(Spath));
 | |
| 				recstat(&Sst);
 | |
| 				break;
 | |
| 			case LINK:
 | |
| 				receive(Slnkpth, sizeof(Slnkpth));
 | |
| 				Slinkpath= Slnkpth;
 | |
| 				receive(Spath, sizeof(Spath));
 | |
| 				recstat(&Sst);
 | |
| 				break;
 | |
| 			case SYMLINK:
 | |
| 				Slinkpath= nil;
 | |
| 				receive(Slnkpth, sizeof(Slnkpth));
 | |
| 				receive(Spath, sizeof(Spath));
 | |
| 				recstat(&Sst);
 | |
| 				break;
 | |
| 			case DONE:
 | |
| 				Sdone= 1;
 | |
| 				break;
 | |
| 			default:
 | |
| 				fprintf(stderr,
 | |
| 					"%s: Strange answer from slave.\n",
 | |
| 					arg0);
 | |
| 				exit(1);
 | |
| 			}
 | |
| 		}
 | |
| 		if (!done && (a == COMPARE || a == DELETE)) {
 | |
| 			/* Master advances. */
 | |
| 			if (!advance()) done= 1;
 | |
| 		}
 | |
| 
 | |
| 		if (done && Sdone) break;
 | |
| 
 | |
| 		switch (a= action()) {
 | |
| 		case ADD:	/* Spath exists, path doesn't, add? */
 | |
| 			add(0);
 | |
| 			break;
 | |
| 		case COMPARE:	/* They both exist, are they the same? */
 | |
| 			compare();
 | |
| 			break;
 | |
| 		case DELETE:	/* path exists, Spath doesn't, delete? */
 | |
| 			delete(0);
 | |
| 		}
 | |
| 		fflush(stdout);	/* Don't keep user in suspense. */
 | |
| 	}
 | |
| 	order(ex == 0 ? DIE : DIE_BAD);
 | |
| }
 | |
| 
 | |
| static void mediator()
 | |
| /* Sits at the local machine and passes orders from master to slave, both
 | |
|  * on remote machines.  Only diagnostics and questions are handled.
 | |
|  */
 | |
| {
 | |
| 	enum orders req;
 | |
| 
 | |
| 	for (;;) {
 | |
| 		switch (req= request()) {
 | |
| 		case DIE_BAD:
 | |
| 			ex= 1;
 | |
| 			/*FALL THROUGH*/
 | |
| 		case DIE:
 | |
| 			order(DIE);
 | |
| 			return;
 | |
| 		case POSITIVE:
 | |
| 			order(ask('y') ? PASS_YES : PASS_NO);
 | |
| 			break;
 | |
| 		case NEGATIVE:
 | |
| 			order(ask('n') ? PASS_YES : PASS_NO);
 | |
| 			break;
 | |
| 		default:
 | |
| 			order(req);
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| #define P_EXIT		1	/* Make sure process doesn't return. */
 | |
| #define P_SHADOW	2	/* Always use exec on 68000. */
 | |
| 
 | |
| static void startprocess(proc, machine, path, p_flags)
 | |
| 	void (*proc)(); char *machine, *path; int p_flags;
 | |
| {
 | |
| 	char *argv[10], **argp= argv;
 | |
| 	char flags[10], *pfl= flags;
 | |
| 
 | |
| 	if (machine != nil) {
 | |
| 		char *u= machine, *m;
 | |
| 
 | |
| 		*argp++ = "rsh";
 | |
| 		if ((m= strchr(machine, '@')) != nil) {
 | |
| 			*m++ = 0;
 | |
| 			*argp++ = "-l";
 | |
| 			*argp++ = u;
 | |
| 			machine= m;
 | |
| 		}
 | |
| 		*argp++ = machine;
 | |
| 	} else
 | |
| 	/* Without this check it would run like a pig on an non MMU 68000: */
 | |
| 	if (!(USE_SHADOWING && p_flags & P_SHADOW)) {
 | |
| 		if (chdir(path) < 0) {
 | |
| 			if (proc != master || errno != ENOENT
 | |
| 						|| mkdir(path, 0700) < 0)
 | |
| 				perrx(path);
 | |
| 			if (chdir(path) < 0) perrx(path);
 | |
| 			printf("Destination directory %s created\n", path);
 | |
| 		}
 | |
| 		isvisible(path);
 | |
| 		isbackup(proc == slave);
 | |
| 		(*proc)();
 | |
| 		if (p_flags & P_EXIT) exit(ex);
 | |
| 		return;
 | |
| 	}
 | |
| 	*argp++ = SYNCNAME;
 | |
| 	*pfl++ = '-';
 | |
| 	if (interact) *pfl++ = 'i';
 | |
| 	if (install) *pfl++ = 'u';
 | |
| 	if (force) *pfl++ = 'f';
 | |
| 	*pfl= 0;
 | |
| 	*argp++ = flags;
 | |
| 	*argp++ = proc == slave ? SLAVENAME : MASTERNAME;
 | |
| 	*argp++ = path;
 | |
| 	*argp++ = nil;
 | |
| #ifdef DEBUG
 | |
| 	fprintf(stderr, "execlp(");
 | |
| 	for (argp= argv; *argp != nil; argp++) fprintf(stderr, "%s, ", *argp);
 | |
| 	fprintf(stderr, "nil);\n");
 | |
| #endif
 | |
| 	execvp(argv[0], argv);
 | |
| 	perrx(argv[0]);
 | |
| }
 | |
| 
 | |
| void splitcolon(path, amach, adir) char *path, **amach, **adir;
 | |
| {
 | |
| 	char *dir= path;
 | |
| 
 | |
| 	for (;;) {
 | |
| 		if (*dir == ':') {
 | |
| 			*dir++ = 0;
 | |
| 			*amach= path;
 | |
| 			*adir= dir;
 | |
| 			break;
 | |
| 		}
 | |
| 		if (*dir == 0 || *dir == '/') {
 | |
| 			*amach= nil;
 | |
| 			*adir= path;
 | |
| 			break;
 | |
| 		}
 | |
| 		dir++;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static void Usage()
 | |
| {
 | |
| 	fprintf(stderr,
 | |
| 	    "Usage: %s [-iuf] [[user@]machine:]dir1 [[user@]machine:]dir2\n",
 | |
| 		arg0);
 | |
| 	exit(1);
 | |
| }
 | |
| 
 | |
| main(argc, argv) int argc; char **argv;
 | |
| {
 | |
| 	char *s_mach, *s_dir;
 | |
| 	char *m_mach, *m_dir;
 | |
| 	int m2s[2], s2m[2], m2m[2];
 | |
| 	int s_pid= 0, m_pid= 0;
 | |
| 	int r;
 | |
| 
 | |
| 	if ((arg0= strrchr(argv[0], '/')) == nil) arg0= argv[0]; else arg0++;
 | |
| 
 | |
| 	while (argc>1 && argv[1][0] == '-') {
 | |
| 		char *f= argv[1]+1;
 | |
| 
 | |
| 		while (*f != 0) {
 | |
| 			switch (*f++) {
 | |
| 			case 'i':	interact= 1; break;
 | |
| 			case 'u':	install= 1; break;
 | |
| 			case 'f':	force= 1; break;
 | |
| 			default:	Usage();
 | |
| 			}
 | |
| 		}
 | |
| 		argc--;
 | |
| 		argv++;
 | |
| 	}
 | |
| 
 | |
| 	if (argc != 3) Usage();
 | |
| 
 | |
| 	if (strcmp(argv[1], SLAVENAME) == 0) {
 | |
| 		arg0= "Slave";
 | |
| 		splitcolon(argv[2], &s_mach, &s_dir);
 | |
| 		startprocess(slave, s_mach, s_dir, P_EXIT);
 | |
| 	} else
 | |
| 	if (strcmp(argv[1], MASTERNAME) == 0) {
 | |
| 		arg0= "Master";
 | |
| 		splitcolon(argv[2], &m_mach, &m_dir);
 | |
| 		startprocess(master, m_mach, m_dir, P_EXIT);
 | |
| 	}
 | |
| 
 | |
| 	splitcolon(argv[1], &s_mach, &s_dir);
 | |
| 	splitcolon(argv[2], &m_mach, &m_dir);
 | |
| 
 | |
| 	/* How difficult can plumbing be? */
 | |
| 	if (pipe(m2s) < 0 || pipe(s2m) < 0) perrx("pipe()");
 | |
| 
 | |
| 	if (m_mach == nil) {
 | |
| 		/* synctree [machine:]dir1 dir2 */
 | |
| 		switch (s_pid= fork()) {
 | |
| 		case -1:
 | |
| 			perrx("fork()");
 | |
| 		case 0:
 | |
| 			dup2(m2s[0], 0); close(m2s[0]); close(m2s[1]);
 | |
| 			dup2(s2m[1], 1); close(s2m[0]); close(s2m[1]);
 | |
| 			arg0= "Slave";
 | |
| 			startprocess(slave, s_mach, s_dir, P_EXIT|P_SHADOW);
 | |
| 		}
 | |
| 		chan[0]= s2m[0]; close(s2m[1]);
 | |
| 		chan[1]= m2s[1]; close(m2s[0]);
 | |
| 		startprocess(master, m_mach, m_dir, 0);
 | |
| 	} else
 | |
| 	if (s_mach == nil) {
 | |
| 		/* synctree dir1 machine:dir2 */
 | |
| 		switch (m_pid= fork()) {
 | |
| 		case -1:
 | |
| 			perrx("fork()");
 | |
| 		case 0:
 | |
| 			dup2(s2m[0], 0); close(s2m[0]); close(s2m[1]);
 | |
| 			dup2(m2s[1], 1); close(m2s[0]); close(m2s[1]);
 | |
| 			arg0= "Master";
 | |
| 			startprocess(master, m_mach, m_dir, P_EXIT|P_SHADOW);
 | |
| 		}
 | |
| 		chan[0]= m2s[0]; close(m2s[1]);
 | |
| 		chan[1]= s2m[1]; close(s2m[0]);
 | |
| 		startprocess(slave, s_mach, s_dir, 0);
 | |
| 	} else {
 | |
| 		/* synctree machine1:dir1 machine2:dir2 */
 | |
| 		if (pipe(m2m) < 0) perrx(pipe);
 | |
| 
 | |
| 		switch (s_pid= fork()) {
 | |
| 		case -1:
 | |
| 			perrx("fork()");
 | |
| 		case 0:
 | |
| 			dup2(m2s[0], 0); close(m2s[0]); close(m2s[1]);
 | |
| 			dup2(s2m[1], 1); close(s2m[0]); close(s2m[1]);
 | |
| 			close(m2m[0]); close(m2m[1]);
 | |
| 			arg0= "Slave";
 | |
| 			startprocess(slave, s_mach, s_dir, P_EXIT|P_SHADOW);
 | |
| 		}
 | |
| 
 | |
| 		switch (m_pid= fork()) {
 | |
| 		case -1:
 | |
| 			perrx("fork()");
 | |
| 		case 0:
 | |
| 			dup2(s2m[0], 0); close(s2m[0]); close(s2m[1]);
 | |
| 			close(m2s[0]); close(m2s[1]);
 | |
| 			dup2(m2m[1], 1); close(m2m[0]); close(m2m[1]);
 | |
| 			arg0= "Master";
 | |
| 			startprocess(master, m_mach, m_dir, P_EXIT|P_SHADOW);
 | |
| 		}
 | |
| 		close(s2m[0]); close(s2m[1]);
 | |
| 		chan[0]= m2m[0]; close(m2m[1]);
 | |
| 		chan[1]= m2s[1]; close(m2s[0]);
 | |
| 		mediator();
 | |
| 	}
 | |
| 	close(chan[0]);
 | |
| 	close(chan[1]);
 | |
| 
 | |
| 	alarm(15); /* Don't wait(2) forever. */
 | |
| 
 | |
| 	while (s_pid != 0 || m_pid != 0) {
 | |
| 		if ((r= wait((int *) nil)) < 0) perrx("wait()");
 | |
| 		if (r == s_pid) s_pid= 0;
 | |
| 		if (r == m_pid) m_pid= 0;
 | |
| 	}
 | |
| 	exit(ex);
 | |
| }
 |