/*
 * Copyright status: public domain.  See the end of this block comment.
 *
 * compare - compare file hierarchies
 *
 * Compilation notes:
 *	-DNO_PROGNAME
 *		For use on systems without __progname: provides its own
 *		__progname, obtained from argv[0].
 *	-DNO_NETWORK
 *		Completely suppress all network-interface code (and
 *		renders the next two defines irrelevant).  This is
 *		intended for systems with broken networking API
 *		support, but it can also be used on a normal system to
 *		produce a binary without networking functionality.
 *		(This is not to say that such a binary cannot be used
 *		for a network compare, just that compare itself will
 *		not handle the networking.)  This also disables -trace,
 *		because the implementation uses some networking calls.
 *	-DNO_GETINFO
 *		Provide getnameinfo() and getaddrinfo() implementations
 *		which just call on inet_aton, gethostbyname, etc.
 *		Intended for use on pre-IPv6 machines.  The provided
 *		versions are just barely sufficient for use here; they
 *		are not general-purpose implementations (for example,
 *		they don't bother implementing some facilities not used
 *		by compare).  By default, this also turns on
 *		-DNO_SOCKADDR_STORAGE (see -DHAS_SOCKADDR_STORAGE).
 *	-DNO_SOCKADDR_STORAGE
 *		For systems without struct sockaddr_storage.  This is
 *		the default if -DNO_GETINFO is used.
 *	-DHAS_SOCKADDR_STORAGE
 *		For systems with struct sockaddr_storage.  This is the
 *		default if -DNO_GETINFO is not used.
 *	-DNO_INET_ATON
 *		Provide a rudimentary inet_aton() that uses inet_addr
 *		(and thus doesn't work right for 255.255.255.255).
 *		Intended for use with -DNO_GETINFO on _very_ old
 *		systems.
 *	-DBROKEN_ARPA_INET_H
 *		For systems whose <arpa/inet.h> is broken and produces
 *		errors when it's included without <netinet/in.h> being
 *		included earlier.
 *	-DDECLARE_ALARM
 *		For systems that don't declare alarm() in their include
 *		files and thus need us to declare it.
 *
 * Usage:
 *	compare [flags] [[user@]machine[!program]:]directory
 *				[[user@]machine[!program]:]directory ...
 *
 * flags (old forms still accepted)
 *	-update (old form: -u)
 *	-follow (old form: -h)
 *	-sgid-dirs (old form: -g)
 *	-mtimes (old form: -t)
 *	-no-owners (old form: -o)
 *	-encode (old form: -x)
 *	-no-sparse
 *	-trace
 *	-prune <name>
 *	-prunewild <pattern>
 *	-prunebase <pattern>
 *	-prunex
 *	-md5
 *	-gzip <gzip-arg>
 *	-rsync
 *	-no-delete
 *	-rsh <program>
 *	-dir <dir>
 *	-mask <mask>
 *	-modemod <and> <or>
 *	-tick <seconds>
 *	-links
 *	-ignore-nonexistent (also -inx) (applies to next directory)
 *	-readonly (also -ro)
 *	-check
 *	-ignore-sockets
 *	-map from to
 *
 *	-R
 *	-accept <port-number>
 *	-connect <address> <port-number>
 *	-force <dir>
 *	-bg
 *	-limit
 *
 * Compare compares directories, possibly on different machines, with
 *  one another.
 *
 * Each (non-option) argument specifies a directory.  If no :s occur in
 *  the argument, or if a slash appears before the first : or !, the
 *  directory is on the local machine.  Otherwise, the portion before
 *  the : specifies a machine name with optional user and program
 *  specifications.  By default, compare uses ssh(1), which must be in
 *  the path, to run compare on the remote machine; see the -rsh option
 *  for a way to change this.  If a username is given with an @ before
 *  the machine name, it will be used with ssh's -l option to specify
 *  the remote username.  If a program name is specified after the
 *  machine name with a !, that will be the name used to run compare on
 *  the remote machine.  The default for the username is whatever ssh
 *  feels like using (normally the local username); the default for the
 *  program name is the argv[0] value for the master run.  If the machine
 *  name begins with a [, then the machine name consists of everything up
 *  to the next ], with the [ ] stripped, regardless of what characters
 *  may be contained between them.  A [ is not special unless it's the
 *  first character of the machine name.  This introduces some
 *  ambiguities and error conditions:
 *	- If the argument contains a colon, and the first character
 *	  after the @ (or the first character, if there's no @) is a [,
 *	  but there's no ], this is an error.
 *	- If likewise, but there's an ], and the next character isn't a
 *	  :, this is an error.
 *	- Neither of the above applies if there's a / before the [.
 *
 * If the machine name is an empty string (ie, the argument begins with
 *  a colon), the argument is taken to be of the form
 *	:keyword:info
 *  to specify an alternative way of contacting the remote.  Currently,
 *  the only defined ways are
 *	:connect:address:port-number:directory
 *  and
 *	:accept:port-number:directory
 *  As with machine names, if "address" begins with a [, it consists of
 *  everything up to the next ], with the [ ] stripped, regardless of what
 *  characters that may include.
 *
 * These cause compare to do a connect or a listen/accept with the given
 *  port number (and address, for "connect") to establish the connection
 *  to the remote compare process.  This is useful when you have shell
 *  access on two machines but can't (or don't want to) allow ssh-style
 *  access between them.  See the -R, -accept, and -connect options for
 *  more.  Note that certain options must normally agree between the
 *  master and the remotes, either given to both or given to neither.
 *  These options are -mtimes, -encode, and -links.  In addition, -gzip
 *  must be given to the remote if it's given to the master, though the
 *  converse is not true (if the master is not given -gzip, it will not
 *  matter whether the remotes are or not).  Other flags (notably -follow,
 *  -no-owners, and -sgid-dirs) may usefully vary between the master and
 *  the remotes; some others (-update, for example) affect only the
 *  master and have no effect when given to a remote.
 *
 * -dir takes its following argument as a directory to compare.  It
 *  exists to allow specifying a directory that begins, or may begin,
 *  with a -.
 *
 * -update means to update: the first-named directory is treated as a
 *  master copy and all others are made identical to it, like a
 *  super-picky rdist (compare compares more things than rdist does).
 *
 * -follow means to follow symlinks instead of checking the links
 *  themselves for matching.
 *
 * -sgid-dirs says to ignore the set-group-ID bits on directories when
 *  comparing modes and to leave them alone when setting modes.  It's
 *  like a variant of "-mask 5777" that applies only to directories.
 *
 * -mtimes says that modification times are important and should be
 *  considered when comparing for differences and preserved when
 *  updating.
 *
 * -no-owners says that the owning UID and GID values for files are
 *  unimportant and should not be set or compared.
 *
 * -encode says that network communication should be passed through a
 *  simple binary-to-printable filter.  (This _should_ never be needed,
 *  but the world is far from ideal, and it does seem to help in some
 *  cases.)
 *
 * -no-sparse says that files should never be created sparse.  Without
 *  this, files compare creates will always be as sparse as possible.
 *
 * -trace says to trace data sent and received between the master
 *  process and the auxiliaries.  (If -encode is in effect, this traces
 *  the encoded form as well.)  Trace output is sent to stderr.
 *
 * -prune <name> says that anything called <name> (which should be a
 *  path relative to the directories being compared) should be ignored
 *  when found: neither it nor anything under it should be compared,
 *  updated, or otherwise touched.  compare behaves as if no such names
 *  existed anywhere, except that if a directory is copied wholesale
 *  because it was nonexistent during an update operation, -prune
 *  entries referring to things inside that directory will not limit
 *  what is copied.
 *
 * -prunewild <pattern> is just like -prune except that the argument is
 *  a globbing pattern rather than a simple name.
 *
 * -prunebase <pattern> is just like -prunewild except that the
 *  argument is matched against just the last component of the name in
 *  question rather than the entire name.
 *
 * -prunex says that that the sense of the tests used for pruning
 *  should be reversed: everything is pruned _except_ the things named
 *  with -prune (and similar) arguments.
 *
 * -md5 says that when plain files contents' need to be compared,
 *  rather than comparing the full contents, simply compute an md5
 *  checksum of each file, and assume that matching checksums means
 *  matching contents.  (This is useful when running over a network
 *  link sufficiently slow that it takes longer to send the file than
 *  it does to compute its checksum, and you're willing to take the
 *  slight risk that different files will checksum the same.)
 *
 * -gzip says that when copying files from one place to another, the
 *  files are to be gzipped at the point of reading and gunzipped at
 *  the point of writing.  (The intention is to reduce bytes sent over
 *  the net; this is intended for use across slow links, where cpu
 *  cycles are cheaper than network bytes.)  The argument is passed to
 *  gzip; it is expected to be something like --best or --fast.
 *
 * -rsync says that when copying files, compare should use an algorithm
 *  akin to rsync(1)'s.
 *
 * -no-delete says that an update operation (see -update) should ignore
 *  files that exist on the remote but not the master.  (Normally, such
 *  files would be deleted.)
 *
 * -rsh <program> says to use the specified <program> instead of ssh
 *  for running remote programs.  This is not a global option; it
 *  affects directory specifications to the right of it (until another
 *  -rsh option), but not those to the left of it.  The <program> is
 *  run as either
 *	<program> machine-name command [args...]
 *  or
 *	<program> machine-name -l username command [args...]
 *  where machine-name, username, and command are as described above,
 *  and args is zero or more arguments to be passed to the remote
 *  executable's command line.
 *
 * -mask specifies what bits to pay attention to when comparing
 *  permissions.  Both objects' mode bits are ANDed with the -mask
 *  argument before comparison.  The argument is an octal number.  (The
 *  mode bits are not modified; if (for example) a file's contents
 *  differ, the mode bits printed will be the actual mode bits, not
 *  masked by the -mask argument.)
 *
 * -modemod specifies that when -update is in effect, all mode bits
 *  read from the master directory tree are to be modified before
 *  comparing, printing, or setting on the other directory trees.
 *  Everything is ANDed with the <and> argument and then ORed with the
 *  <or> argument.  (Only the 07777 bits in each argument are
 *  significant.)  When -update is not in effect, all mode bits read
 *  from any directory tree are modified as described before comparing
 *  or printing.
 *
 * -tick gives an interval in seconds; every this many seconds, the
 *  current path being worked on will be printed.  (Ticks occurring
 *  before all remotes start are ignored.)  A tick can also be forced
 *  by explicitly sending a SIGALRM, either from the command line via
 *  kill(1) or programmatically with kill(2), whether or not -tick was
 *  given.  (The signal must be sent to the parent compare process,
 *  which almost always means the lowest PID.)  This output, like that
 *  generated by SIGINFO, goes to /dev/tty.  If /dev/tty can't be
 *  opened at startup, SIGINFO and SIGALRM output is discarded.
 *
 * -links causes compare to notice files that are linked together.  The
 *  actual link count is ignored, except that compare doesn't bother
 *  doing this for files with link count 1; this is necessary because
 *  it's possible that there are links which are not visible in the
 *  subtree being compared.  Differences in linking patterns are
 *  considered differences, and, with -update, are fixed.  This is
 *  necessary when using -update to update directories with different
 *  link patterns to avoid stuttering.  Consider a master directory
 *  containing foo/file and bar/file, with identical contents but
 *  different permissions, and a replica directory containing foo/file
 *  and bar/file hardlinked together.  An update will chmod the file on
 *  the slave if necessary, to make it match the master's bar/file,
 *  when bar/ is scanned, then chmod it again, to match the master's
 *  foo/file, when foo/ is scanned; there will always be a difference
 *  until the hardlinking on the replica is undone - or a run with
 *  -links is done to fix the linking.  (Note that this is not done for
 *  directories.)
 *
 * -ignore-nonexistent (which can also be spelled -inx) specifies that
 *  the next directory argument is to be treated slightly differently:
 *  if the only reason to print a difference report is that an entry is
 *  nonexistent in one or more so-marked directories, then the report
 *  is not printed.  This option should not be used with -u; the result
 *  is "whatever the implementation gives you", not anything
 *  well-specified.
 *
 * -check is designed to verify that all directories match; if they do,
 *  exit status is 0, if not, 1.  No output is produced for
 *  differences, nor does compare progress past the first difference
 *  discovered.  -check is useful only on the master run; it is not
 *  passed automatically to remotes, and remotes ignore it if it's
 *  given on a run-by-hand remote.
 *
 * -ignore-sockets tells compare to ignore sockets.  When sockets are
 *  found in any of the directories being compared, the effect is
 *  almost as if they didn't exist at all; the only difference is that
 *  when messages are printed, sockets are described as such, rather
 *  than being described as nonexistent.  But, for example, a socket
 *  existing in one directory and the name being nonexistent in another
 *  will not, in itself, cause a mismatch.  But a socket in the master
 *  directory of an update operation will cause a non-socket in a
 *  non-master directory to be deleted.  -ignore-sockets matters only
 *  on the master run.
 *
 * -readonly is used with -R; it specifies that the remote is to reject
 *  any commands which would modify anything.
 *
 * -R says that this compare process is a remote.  It is normally not
 *  needed unless you're using explicit rendezvous points instead of
 *  the usual ssh way of starting the remotes.
 *
 * -accept is useful only with -R.  It specifies that the process is to
 *  listen/accept on the given port number to establish the connection
 *  to the master, instead of assuming the connection is already
 *  present on stdin and stdout.  -R -accept is used when the master
 *  uses a :connect:-style remote specifier.
 *
 * -connect is useful only with -R.  It specifies that the process is
 *  to connect to the given port number at the given address to
 *  establish the connection to the master, instead of assuming the
 *  connection is already present on stdin and stdout.  -R -connect is
 *  used when the master uses a :accept:-style remote specifier.
 *
 * -force is useful only with -R.  It specifies that the remote is to
 *  ignore the directory that is specified at protocol startup, using
 *  the -force argument instead.
 *
 * -bg is useful only with -R.  It specifies that the remote is to
 *  background itself as soon as any initial setup (such as listening
 *  for -accept) is completed.
 *
 * -limit is useful only with -R.  It specifies that the remote is to
 *  refuse to accept a master-specified path that begins with a slash
 *  or attempts to dot-dot its way up the directory tree.
 *
 * -map converts "from" to "to" if it is found when reading the
 *  directory (applies to the next directory only).  Using this with
 *  -update works sanely only when the directory it is applied to is
 *  the master directory.
 *
 * No provision is made for user names containing /s, @s, or :s or
 *  program names containing :s.  Machine names can contain any
 *  character except ], provided they're written []-bracketed.
 *
 * Copyright status: this program is entirely my work, and I explicitly
 *  place it in the public domain.  Anyone may use it in any way for
 *  any purpose (though I would appreciate credit where it is due).
 *  I'd also be interested in hearing about your experiences with it,
 *  especially if you think you've run into a bug.
 *
 *  /~\ The ASCII                           der Mouse
 *  \ / Ribbon Campaign
 *   X  Against HTML               mouse@rodents.montreal.qc.ca
 *  / \ Email!           7D C8 61 52 5D E7 2D 39  4E F1 31 3E E8 B3 27 4B
 */

#include <md5.h>
#include <poll.h>
#include <stdio.h>
#include <errno.h>
#include <stdarg.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <strings.h>
#include <sys/dir.h>
#include <sys/uio.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/file.h>
#include <sys/ioctl.h>
#include <sys/resource.h>

#ifndef NO_NETWORK
#ifdef BROKEN_ARPA_INET_H
#include <netinet/in.h>
#endif
#include <netdb.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#endif

extern const char *__progname;

/* Part of the Linux fixup, below. */
/* Use this only in contexts where the name length is known to be >= n */
#define DLEN_IS(d,n) ((d)->d_namlen == (n))

/* fixup for SunOS versions which have <poll.h> and  poll() but not these */
#ifndef POLLRDNORM
#define POLLRDNORM 0
#endif
#ifndef POLLRDBAND
#define POLLRDBAND 0
#endif
#ifndef POLLPRI
#define POLLPRI 0
#endif
#ifndef INFTIM
#define INFTIM (-1)
#endif
/* fixup for SunOS, which is without lchmod/lchown/lutimes and strtoul */
#if defined(sparc) && defined(sun) /*&& defined(unix)*/
#define lchmod(x,y) ((errno=EINVAL),-1)
#define lchown(x,y,z) ((errno=EINVAL),-1)
#define lutimes(x,y) ((errno=EINVAL),-1)
#define strtoul(s,e,b) ((unsigned long int)strtol(s,e,b))
#endif
/* fixup for Linux, which is without d_namlen or lchmod, and for which
   <time.h> isn't enough for itimerval stuff - it demands <sys/time.h> */
#ifdef __linux__
#undef DLEN_IS
#define DLEN_IS(d,n) ((d)->d_name[(n)] == '\0')
#include <sys/time.h>
static int lchmod(const char *path, int mode)
{
 struct stat stb;
 if ((stat(path,&stb) == 0) && ((stb.st_mode & S_IFMT) == S_IFLNK))
  { errno = EINVAL;
    return(-1);
  }
 return(chmod(path,mode));
}
#endif

#define CURRENT_VERSION 2
#define RSYNC_BLKSIZE 1009
#define RSYNC_POLY 0x82f63b78
#define RSYNC_HTSIZE 65537

#ifdef NO_PROGNAME
const char *__progname;
int main(int, char **);
int main_(int, char **);
int main(int ac, char **av) { __progname = av[0]; return(main_(ac,av)); }
#define main main_
#endif

#if !defined(NO_SOCKADDR_STORAGE) && !defined(HAS_SOCKADDR_STORAGE)
#ifdef NO_GETINFO
#define NO_SOCKADDR_STORAGE
#else
#define HAS_SOCKADDR_STORAGE
#endif
#endif

#ifndef NO_NETWORK
#ifdef NO_GETINFO
struct addrinfo {
  int ai_flags;
  int ai_family;
  int ai_socktype;
  int ai_protocol;
  size_t ai_addrlen;
  char *ai_canonname;
  struct sockaddr *ai_addr;
  struct addrinfo *ai_next;
  } ;
#define EAI_NODATA  10001
#define EAI_SERVICE 10002
#define AI_PASSIVE 0x00000001
static void gai_add_entry(struct addrinfo **rvp, struct in_addr *iap, int pno)
{
 struct addrinfo *ai;
 struct sockaddr_in *sin;
 sin = malloc(sizeof(*sin));
 bzero(sin,sizeof(*sin));
 /* Don't set sin->sin_size; the few systems that need NO_GETINFO
    but have sin_size can deal with a zero sin_size. */
 sin->sin_family = AF_INET;
 sin->sin_addr = *iap;
 sin->sin_port = htons(pno);
 ai = malloc(sizeof(*ai));
 ai->ai_flags = 0;
 ai->ai_family = AF_INET;
 ai->ai_socktype = SOCK_STREAM;
 ai->ai_protocol = IPPROTO_TCP;
 ai->ai_addrlen = sizeof(struct sockaddr_in);
 ai->ai_canonname = 0;
 ai->ai_addr = (void *) sin;
 ai->ai_next = *rvp;
 *rvp = ai;
}
int getaddrinfo(const char *, const char *, const struct addrinfo *, struct addrinfo **);
int getaddrinfo(const char *h, const char *s, const struct addrinfo *hints, struct addrinfo **rvp)
{
 unsigned long int pno;
 char *se;
 struct addrinfo *rv;
 struct in_addr ia;
 rv = 0;
 pno = strtoul(s,&se,0);
 if ((s == se) || *se)
  { struct servent *serv;
    serv = getservbyname(s,"tcp");
    if (serv == 0) return(EAI_SERVICE);
    pno = ntohs(serv->s_port);
  }
 else if (pno > 65535)
  { return(EAI_SERVICE);
  }
 if (h)
  { if (inet_aton(h,&ia))
     { gai_add_entry(&rv,&ia,pno);
     }
    else
     { struct hostent *host;
       int i;
       host = gethostbyname(h);
       if ( !host || !host->h_addr_list ||
	    (host->h_addrtype != AF_INET) ||
	    (host->h_length != sizeof(struct sockaddr_in)) ) return(EAI_NODATA);
#define HAL_SIN(h,i) ((struct sockaddr_in *)(h)->h_addr_list[(i)])
       for (i=0;host->h_addr_list[i];i++)
	{ if (HAL_SIN(host,i)->sin_family == AF_INET) gai_add_entry(&rv,&HAL_SIN(host,i)->sin_addr,pno);
	}
#undef HAL_SIN
     }
  }
 else
  { ia.s_addr = (hints->ai_flags & AI_PASSIVE) ? INADDR_ANY : htonl(0x7f000001);
    gai_add_entry(&rv,&ia,pno);
  }
 *rvp = rv;
 return(rv?0:EAI_NODATA);
}
void freeaddrinfo(struct addrinfo *);
void freeaddrinfo(struct addrinfo *ai)
{
 struct addrinfo *ai2;
 while (ai)
  { free(ai->ai_addr);
    ai2 = ai->ai_next;
    free(ai);
    ai = ai2;
  }
}
const char *gai_strerror(int);
const char *gai_strerror(int ec)
{
 switch (ec)
  { case EAI_NODATA:  return("No address associated with nodename"); break;
    case EAI_SERVICE: return("Servname not known"); break;
  }
 return("Unknown error");
}
#define NI_MAXHOST 32
#define NI_MAXSERV 32
#define NI_NUMERICHOST 1
#define NI_NUMERICSERV 2
int getnameinfo(const struct sockaddr *, socklen_t, char *, size_t, char *, size_t, int);
int getnameinfo(const struct sockaddr *sa, socklen_t len, char *h, size_t hlen, char *s, size_t slen, int flags __attribute__((__unused__)))
{
 if ((len != sizeof(struct sockaddr_in)) || (sa->sa_family != AF_INET))
  { errno = EINVAL;
    return(-1);
  }
 if (h)
  { snprintf(h,hlen,"%s",inet_ntoa(((const struct sockaddr_in *)sa)->sin_addr));
  }
 if (s)
  { snprintf(s,slen,"%u",(unsigned int)ntohs(((const struct sockaddr_in *)sa)->sin_port));
  }
 return(0);
}
#endif
#ifdef NO_INET_ATON
int inet_aton(const char *, struct in_addr *);
int inet_aton(const char *s, struct in_addr *ap)
{
 unsigned long int a;
 a = inet_addr(s);
 if (a == INADDR_NONE) return(0);
 ap->s_addr = a;
 return(1);
}
#endif
#endif

#ifdef NO_SOCKADDR_STORAGE
#define sockaddr_storage sockaddr_in
#endif

#ifdef DECLARE_ALARM
extern void alarm(unsigned int);
#endif

#define NEW(t) ((t *)malloc(sizeof(t)))
#define OLD(x) free((x))

/* code assumes WORKSIZE is a multiple of 512 */
#define WORKSIZE 10240
#define MAXREMOTES 64

#define COPYBLK_DATA 1
#define COPYBLK_HOLE 2

#ifdef NO_FIFO
#undef S_IFIFO
#endif

static int flag_debug;
static int flag_follow;
static int flag_sgiddir;
static int flag_noown;
static int flag_remote;
static int flag_mtimes;
static int flag_update;
static int flag_encode;
#ifndef NO_NETWORK
static int flag_trace;
#endif
static int flag_md5;
static int flag_gzip;
static int flag_rsync;
static int flag_nosparse;
static int flag_nodelete;
static int flag_accept;
static int flag_connect;
static int flag_links;
static int flag_nextinx;
static int flag_readonly;
static int flag_check;
static int flag_bg;
static int flag_limitslave;
static int flag_ignoresockets;

#ifndef NO_NETWORK
static char *conn_port;
static char *conn_addr;
#endif
static int perm_mask;
static int perm_and = ~0;
static int perm_or = 0;
static int tick_seconds;
#ifndef NO_NETWORK
static int trace_ctl = -1;
#endif
static char *force_dir = 0;
static void (*check_exit)(void);

static char *defprogram;

typedef struct mask MASK;
typedef struct entry ENTRY;
typedef struct entry_tconc ENTRY_TCONC;
typedef struct remote REMOTE;
typedef struct rstack RSTACK;
typedef struct prune PRUNE;
typedef struct mvlist MVLIST;
typedef struct mapping MAPPING;
typedef struct linktable LINKTABLE;
typedef struct linkentry LINKENTRY;

#define MASKLONGS ((MAXREMOTES+31)>>5)

struct mask {
  unsigned long int bits[MASKLONGS];
  } ;

#if MASKLONGS == 1
#define MASK_ZERO(m) ((m).bits[0] = 0)
#define MASK_SET(m,bit) ((m).bits[0] |= (1 << (bit)))
#define MASK_CLR(m,bit) ((m).bits[0] &= ~(1 << (bit)))
#define MASK_TST(m,bit) ((m).bits[0] & (1 << (bit)))
#define MASK_ISZERO(m) ((m).bits[0] == 0)
#define MASK_EQ(m1,m2) ((m1).bits[0] == (m2).bits[0])
#define MASK_ONEBIT(m) ((m).bits[0] && (((m).bits[0] & ((m).bits[0]-1)) == 0))
#else
static const MASK zmask;
#define MASK_ZERO(m) ((m) = zmask)
#define MASK_SET(m,bit) ((m).bits[(bit)>>5] |= (1 << ((bit)&31)))
#define MASK_CLR(m,bit) ((m).bits[(bit)>>5] &= ~(1 << ((bit)&31)))
#define MASK_TST(m,bit) ((m).bits[(bit)>>5] & (1 << ((bit)&31)))
#define MASK_ISZERO(m) mask_iszero(m)
#define MASK_EQ(m1,m2) mask_eq(m1,m2)
#define MASK_ONEBIT(m) mask_onebit(m)
#endif

#ifndef NO_NETWORK
typedef struct acinfo ACINFO;
struct acinfo {
  unsigned int type;
#define ACT_ACCEPT  1
#define ACT_CONNECT 2
  int *fdloc;
  union {
    struct {
      int nfds;
      int *fds;
      char **txt;
      } acc;
    struct {
      int fd;
      struct addrinfo *ai;
      char *txt;
      struct addrinfo *ai0;
      const char *astr;
      const char *pstr;
      } conn;
    } u;
  } ;
#endif

struct mapping {
  MAPPING *link;
  char *dir;
  char *from;
  char *to;
  } ;

struct mvlist {
  MVLIST *link;
  MASK m;
  unsigned long long int v;
  } ;

struct prune {
  PRUNE *link;
  unsigned int type;
#define PT_PATH 1
#define PT_WILD 2
#define PT_BASE 3
  char *path;
  } ;

struct entry {
  ENTRY *link;
  char *name;
  int mode;
  int uid;
  int gid;
  long int mtime;
  int nlinks;
  unsigned long long int linkdev;
  unsigned long long int linkino;
  LINKENTRY *hardlink;
  int firstlink;
  unsigned long long int rdev_min;
  unsigned long long int rdev_maj;
  unsigned long long int size;
  char *softlink;
  MASK present;
  } ;

struct entry_tconc {
  ENTRY *list;
  ENTRY **tail;
  } ;

struct remote {
  REMOTE *link;
  char *fullarg;
  char *user;
  char *machine;
  char *program;
  char *directory;
  const char *rsh;
  int rfd;
  int wfd;
  unsigned char errored : 1;
  unsigned char ignnx : 1;
#ifndef NO_NETWORK
  ACINFO *acinfo;
#endif
  FILE *rf;
  FILE *wf;
  int bit;
  ENTRY *dirlist;
  MAPPING *mappings;
  LINKTABLE *links;
  int worklen;
  char work[WORKSIZE];
  } ;

struct linktable {
  LINKENTRY *links;
  } ;

/*
 * The name field has somewhat overloaded semantics.  Normally it is
 *  the path of the first-encountered entity with this <dev,ino>.  But
 *  it can also be nil, which means that this entry has conceptually
 *  been removed from the table, because the entity that corresponded
 *  to the name has been (or should have been) removed.
 */
struct linkentry {
  LINKENTRY *link;
  unsigned long long int dev;
  unsigned long long int ino;
  char *name;
  } ;

struct rstack {
  RSTACK * volatile link;
  ENTRY * volatile list;
  ENTRY * volatile cur;
  } ;

#define EACH_REM(var) var=remotes;var;var=var->link

static const char *currsh = "ssh";
static REMOTE *remotes;
static REMOTE *umaster;
static MASK active;
static const char *cur_dir;
static char *cur_ent;
static int cur_pruned;
static MASK cur_nx;
static int prunex;
static int needhdg;
static PRUNE *prunelist;
static char *gzip_arg;
static RSTACK * volatile rstack;
static volatile int progress;
#define MAX_PROGRESS 5
static REMOTE * volatile waitrem;
static MAPPING *pendmap;

static char slavebase[WORKSIZE];
static char slavework[WORKSIZE];
static char slavetemp[WORKSIZE];
static char slavetemp2[WORKSIZE];

static char zeroblk[WORKSIZE];

static ENTRY *freeentries;

static __inline__ unsigned long int ullmin(unsigned long long int, unsigned long long int) __attribute__((__const__));
static __inline__ unsigned long int ullmin(unsigned long long int a, unsigned long long int b)
{
 return((a<b)?a:b);
}

static void panic(const char *fmt, ...)
{
 va_list ap;

 va_start(ap,fmt);
 fprintf(stderr,"%s: INTERNAL BUG: ",__progname);
 vfprintf(stderr,fmt,ap);
 fprintf(stderr,"\n");
 va_end(ap);
 abort();
}

static ENTRY *newentry(void)
{
 ENTRY *e;

 if (freeentries)
  { e = freeentries;
    freeentries = e->link;
  }
 else
  { e = NEW(ENTRY);
  }
 e->name = 0;
 e->hardlink = 0;
 e->softlink = 0;
 return(e);
}

static void freeentry(ENTRY *e)
{
 if (e->name) free(e->name);
 if (e->softlink) free(e->softlink);
 e->link = freeentries;
 freeentries = e;
}

static ENTRY *copyentry(ENTRY *e)
{
 ENTRY *new;

 new = newentry();
 *new = *e;
 if (e->name) new->name = strdup(e->name);
 if (e->softlink) new->softlink = strdup(e->softlink);
 return(new);
}

#if MASKLONGS > 1
static int mask_iszero(MASK m)
{
 int i;

 for (i=0;i<MASKLONGS;i++) if (m.bits[i]) return(0);
 return(1);
}
#endif

#if MASKLONGS > 1
static int mask_eq(MASK m1, MASK m2)
{
 int i;

 for (i=0;i<MASKLONGS;i++) if (m1.bits[i] != m2.bits[i]) return(0);
 return(1);
}
#endif

#if MASKLONGS > 1
static int mask_onebit(MASK m)
{
 int i;

 for (i=0;i<MASKLONGS;i++) if (m.bits[i]) break;
 if (i >= MASKLONGS) return(0);
 if (m.bits[i] & (m.bits[i]-1)) return(0);
 for (i++;i<MASKLONGS;i++) if (m.bits[i]) return(0);
 return(1);
}
#endif

static MASK mask_or(MASK a, MASK b)
{
#if MASKLONGS == 1
 a.bits[0] |= b.bits[0];
#else
 int i;
 for (i=0;i<MASKLONGS;i++) a.bits[i] |= b.bits[i];
#endif
 return(a);
}

#if MASKLONGS == 1
#define mask_setor(a,b) ((a)->bits[0] |= (b).bits[0])
#else
static void mask_setor(MASK *a, MASK b)
{
 int i;

 for (i=0;i<MASKLONGS;i++) a->bits[i] |= b.bits[i];
}
#endif

static MASK mask_and(MASK a, MASK b)
{
#if MASKLONGS == 1
 a.bits[0] &= b.bits[0];
#else
 int i;
 for (i=0;i<MASKLONGS;i++) a.bits[i] &= b.bits[i];
#endif
 return(a);
}

static MASK mask_andnot(MASK a, MASK b)
{
#if MASKLONGS == 1
 a.bits[0] &= ~b.bits[0];
#else
 int i;
 for (i=0;i<MASKLONGS;i++) a.bits[i] &= ~b.bits[i];
#endif
 return(a);
}

#if MASKLONGS == 1
#define mask_setandnot(a,b) ((a)->bits[0] &= ~(b).bits[0])
#else
static void mask_setandnot(MASK *a, MASK b)
{
 int i;

 for (i=0;i<MASKLONGS;i++) a->bits[i] &= ~b.bits[i];
}
#endif

static void bin2hex(unsigned char *bin, unsigned char *hex, int n)
{
 for (;n>0;n--)
  { *hex++ = 48 + ((*bin)>>4);
    *hex++ = 48 + ((*bin)&15);
    bin ++;
  }
}

static void hex2bin(unsigned char *hex, unsigned char *bin, int n)
{
 for (;n>0;n--)
  { *bin++ = ((hex[0] & 15) << 4) | (hex[1] & 15);
    hex += 2;
  }
}

#ifndef NO_NETWORK
static void trace_print(const char *tag, const char *tag2, void *bvp, int nb)
{
 unsigned char *bp;

 fprintf(stderr,"%s%s[%d] ",tag,tag2,nb);
 for (bp=bvp;nb>0;nb--,bp++)
  { switch (*bp)
     { case '\a': fprintf(stderr,"\\a"); break;
       case '\b': fprintf(stderr,"\\b"); break;
       case '\33':fprintf(stderr,"\\e"); break;
       case '\f': fprintf(stderr,"\\f"); break;
       case '\n': fprintf(stderr,"\\n"); break;
       case '\r': fprintf(stderr,"\\r"); break;
       case '\t': fprintf(stderr,"\\t"); break;
       case '\v': fprintf(stderr,"\\v"); break;
       default:
	  if ((*bp < 32) || ((*bp > 126) && (*bp <= 160)))
	   { if (bp[1] && (bp[1] >= '0') && (bp[1] <= '9'))
	      { fprintf(stderr,"\\%03o",*bp);
	      }
	     else
	      { fprintf(stderr,"\\%o",*bp);
	      }
	   }
	  else
	   { putc(*bp,stderr);
	   }
	  break;
     }
  }
 putc('\n',stderr);
}
#endif

#ifndef NO_NETWORK
static void run_tracer(int ctl, int death)
{
 typedef struct client CLIENT;
 struct client {
   CLIENT *link;
   int fd;
   char *lbuf;
   int lalloc;
   int lfill;
   int fdx;
   } ;
 CLIENT *clients;
 struct pollfd *fdv;
 int fdva;
 int fdvn;
 CLIENT *c;
 CLIENT **cp;

 static int addfd(int fd)
  { struct pollfd *f;
    if (fdvn >= fdva) fdv = realloc(fdv,(fdva=fdvn+1)*sizeof(*fdv));
    f = &fdv[fdvn];
    f->fd = fd;
    f->events = POLLIN | POLLRDNORM | POLLRDBAND | POLLPRI;
    return(fdvn++);
  }

 static void client_line(CLIENT *c, char *addbuf, int addlen)
  { struct iovec iov[3];
    int iox;
    static char nl = '\n';
    iox = 0;
    if (c->lfill > 0)
     { iov[iox].iov_base = c->lbuf;
       iov[iox].iov_len = c->lfill;
       iox ++;
     }
    if (addlen > 0)
     { iov[iox].iov_base = addbuf;
       iov[iox].iov_len = addlen;
       iox ++;
     }
    iov[iox].iov_base = &nl;
    iov[iox].iov_len = 1;
    iox ++;
    writev(2,&iov[0],iox);
    c->lfill = 0;
  }

 static void client_read(CLIENT *c)
  { char rbuf[512];
    int rn;
    char *nl;
    int rx;
    int l;
    rn = read(c->fd,&rbuf[0],sizeof(rbuf));
    if (rn < 0)
     { fprintf(stderr,"%s: tracer client read: %s\n",__progname,strerror(errno));
       exit(1);
     }
    if (rn == 0)
     { if (c->lfill) client_line(c,0,0);
       close(c->fd);
       c->fd = -1;
       return;
     }
    rx = 0;
    while ((nl = memchr(&rbuf[rx],'\n',rn-rx)))
     { client_line(c,&rbuf[rx],nl-&rbuf[rx]);
       c->lfill = 0;
       rx = (nl+1) - &rbuf[0];
     }
    l = rn - rx;
    if (l > 0)
     { if (c->lfill+l > c->lalloc) c->lbuf = realloc(c->lbuf,c->lalloc=c->lfill+l);
       bcopy(&rbuf[rx],c->lbuf+c->lfill,l);
       c->lfill += l;
     }
  }

 static void ctl_read(void)
  { struct msghdr mh;
    struct iovec iov;
#ifdef SCM_RIGHTS
    struct cmsghdr cmh;
    char cbuf[sizeof(struct cmsghdr)+sizeof(int)];
#endif
    int fd;
    char junk;
    CLIENT *c;
    mh.msg_name = 0;
    mh.msg_namelen = 0;
    iov.iov_base = &junk;
    iov.iov_len = 1;
    mh.msg_iov = &iov;
    mh.msg_iovlen = 1;
#ifdef SCM_RIGHTS
    mh.msg_control = &cbuf[0];
    mh.msg_controllen = sizeof(cbuf);
    mh.msg_flags = 0;
#else
    mh.msg_accrights = (void *)&fd;
    mh.msg_accrightslen = sizeof(int);
#endif
    if (recvmsg(ctl,&mh,0) < 0)
     { fprintf(stderr,"%s: can't receive trace fd: %s\n",__progname,strerror(errno));
       exit(1);
     }
#ifdef MSG_CTRUNC
    if (mh.msg_flags & MSG_CTRUNC)
     { fprintf(stderr,"%s: can't receive trace fd (control info truncated)\n",__progname);
       exit(1);
     }
#endif
#ifdef SCM_RIGHTS
    if (mh.msg_controllen < sizeof(cbuf))
     { fprintf(stderr,"%s: can't receive trace fd (control info unexpectedly short)\n",__progname);
       exit(1);
     }
    bcopy(&cbuf[0],&cmh,sizeof(cmh));
    if (cmh.cmsg_len != sizeof(cbuf))
     { fprintf(stderr,"%s: can't receive trace fd (control packet len wrong)\n",__progname);
       exit(1);
     }
    if (cmh.cmsg_level != SOL_SOCKET)
     { fprintf(stderr,"%s: can't receive trace fd (control info level wrong)\n",__progname);
       exit(1);
     }
    if (cmh.cmsg_type != SCM_RIGHTS)
     { fprintf(stderr,"%s: can't receive trace fd (control info type wrong)\n",__progname);
       exit(1);
     }
    bcopy(&cbuf[sizeof(cmh)],&fd,sizeof(int));
#else
    if (mh.msg_accrightslen != sizeof(int))
     { fprintf(stderr,"%s: can't receive trace fd (access rights unexpectedly short)\n",__progname);
       exit(1);
     }
#endif
    c = malloc(sizeof(CLIENT));
    c->fd = fd;
    c->lbuf = 0;
    c->lalloc = 0;
    c->lfill = 0;
    c->link = clients;
    clients = c;
  }

 clients = 0;
 fdv = 0;
 fdva = 0;
 while (1)
  { fdvn = 0;
    addfd(ctl);
    if (death >= 0) addfd(death);
    cp = &clients;
    while ((c = *cp))
     { if (c->fd >= 0)
	{ cp = &c->link;
	  c->fdx = addfd(c->fd);
	}
       else
	{ *cp = c->link;
	  free(c->lbuf);
	  free(c);
	}
     }
    if ((death < 0) && !clients) exit(0);
    if (poll(fdv,fdvn,INFTIM) < 0)
     { if (errno == EINTR) continue;
       fprintf(stderr,"%s; poll: %s\n",__progname,strerror(errno));
       exit(1);
     }
    if (fdv[0].revents & (POLLIN|POLLRDNORM|POLLRDBAND|POLLPRI))
     { ctl_read();
       continue;
     }
    if ((death >= 0) && (fdv[1].revents & (POLLIN|POLLRDNORM|POLLRDBAND|POLLPRI)))
     { close(death);
       death = -1;
       continue;
     }
    for (c=clients;c;c=c->link)
     { if (fdv[c->fdx].revents & (POLLIN|POLLRDNORM|POLLRDBAND|POLLPRI))
	{ client_read(c);
	}
     }
  }
}
#endif

static int devnull(void)
{
 static int fd = -1;

 if (fd < 0) fd = open("/dev/null",O_RDWR,0);
 return(fd);
}

static int mypipe(int *x)
{
 char buf[32];
 int rv;

 rv = pipe(x);
 if (rv < 0) return(-1);
 sprintf(&buf[0],"%d %d",x[0],x[1]);
 write(devnull(),&buf[0],strlen(&buf[0]));
 return(rv);
}
#define pipe(x) mypipe(x)

#ifndef NO_NETWORK
static int mysocketpair(int f, int d, int p, int *x)
{
 char buf[32];
 int rv;

 rv = socketpair(f,d,p,x);
 if (rv < 0) return(-1);
 sprintf(&buf[0],"%d %d",x[0],x[1]);
 write(devnull(),&buf[0],strlen(&buf[0]));
 return(rv);
}
#define socketpair(f,d,p,x) mysocketpair(f,d,p,x)
#endif

#ifndef NO_NETWORK
static void fork_tracer(void)
{
 int s[2];
 int p[2];
 pid_t kid;

 if (socketpair(AF_LOCAL,SOCK_DGRAM,0,&s[0]) < 0)
  { fprintf(stderr,"%s: socketpair AF_LOCAL SOCK_DGRAM: %s\n",__progname,strerror(errno));
    exit(1);
  }
 if (pipe(&p[0]) < 0)
  { fprintf(stderr,"%s: pipe: %s\n",__progname,strerror(errno));
    exit(1);
  }
 kid = fork();
 if (kid < 0)
  { fprintf(stderr,"%s: fork: %s\n",__progname,strerror(errno));
    exit(1);
  }
 if (kid == 0)
  { close(p[1]);
    /* don't close s[0]; let trace filter merger process hold it open
       to prevent ECONNRESET recv errors (we'd rather get death notification
       via the death pipe, here called p). */
    run_tracer(s[1],p[0]);
    exit(0);
  }
 close(p[0]);
 /* drop p[1], let it be closed when we exit - but make sure any future
    children forked to run other programs don't inherit it! */
 fcntl(p[1],F_SETFD,1);
 close(s[1]);
 trace_ctl = s[0];
}
#endif

#ifndef NO_NETWORK
static void send_tracer(int fd)
{
 struct msghdr mh;
 struct iovec iov;
#ifdef SCM_RIGHTS
 struct cmsghdr cmh;
 char cbuf[sizeof(struct cmsghdr)+sizeof(int)];
#endif

 mh.msg_name = 0;
 mh.msg_namelen = 0;
 iov.iov_base = (void *)&iov; /* XXX, cast is for SunOS */
 iov.iov_len = 1;
 mh.msg_iov = &iov;
 mh.msg_iovlen = 1;
#ifdef SCM_RIGHTS
 cmh.cmsg_len = sizeof(cbuf);
 cmh.cmsg_level = SOL_SOCKET;
 cmh.cmsg_type = SCM_RIGHTS;
 bcopy(&cmh,&cbuf[0],sizeof(cmh));
 bcopy(&fd,&cbuf[sizeof(cmh)],sizeof(fd));
 mh.msg_control = &cbuf[0];
 mh.msg_controllen = sizeof(cbuf);
 mh.msg_flags = 0;
#else
 mh.msg_accrights = (void *)&fd;
 mh.msg_accrightslen = sizeof(int);
#endif
 if (sendmsg(trace_ctl,&mh,0) < 0)
  { fprintf(stderr,"%s: can't send trace fd to tracer: %s\n",__progname,strerror(errno));
    exit(1);
  }
 close(fd);
}
#endif

#ifndef NO_NETWORK
static void tracefilter(int rfd, int wfd, const char *tag)
{
 int i;
 int p1[2];
 int p2[2];
 int p3[2];
 unsigned char buf[WORKSIZE];
 int flags;
#define LIVE_R  1
#define LIVE_W  2

 if (trace_ctl < 0) fork_tracer();
 if ((pipe(&p1[0]) < 0) || (pipe(&p2[0]) < 0) || (pipe(&p3[0]) < 0))
  { fprintf(stderr,"%s: pipe: %s\n",__progname,strerror(errno));
    exit(1);
  }
 switch (fork())
  { case -1:
       fprintf(stderr,"%s: can't fork: %s\n",__progname,strerror(errno));
       exit(1);
       break;
    case 0:
       break;
    default:
       dup2(p1[0],rfd);
       dup2(p2[1],wfd);
       close(p1[0]);
       close(p1[1]);
       close(p2[0]);
       close(p2[1]);
       send_tracer(p3[0]);
       close(p3[1]);
       return;
       break;
  }
 if (p3[1] != 2)
  { dup2(p3[1],2);
    close(p3[1]);
  }
 for (i=getdtablesize()-1;i>=0;i--)
  { if ((i != 2) && (i != p1[1]) && (i != p2[0]) && (i != rfd) && (i != wfd))
     { close(i);
     }
  }
 flags = LIVE_R | LIVE_W;
 while (flags & (LIVE_R|LIVE_W))
  { fd_set fds;
    int n;
    FD_ZERO(&fds);
    if (flags & LIVE_W) FD_SET(p2[0],&fds);
    if (flags & LIVE_R) FD_SET(rfd,&fds);
    n = select(FD_SETSIZE,&fds,(fd_set *)0,(fd_set *)0,(struct timeval *)0);
    if (n < 0)
     { if (errno != EINTR)
	{ fprintf(stderr,"%s: select error: %s\n",__progname,strerror(errno));
	  exit(1);
	}
       continue;
     }
    if ((flags & LIVE_W) && FD_ISSET(p2[0],&fds))
     { n = read(p2[0],&buf[0],sizeof(buf));
       if (n < 0)
	{ fprintf(stderr,"%s: read error: %s\n",__progname,strerror(errno));
	  exit(1);
	}
       if (n == 0)
	{ fprintf(stderr,"%s [closed >>]\n",tag);
	  close(p2[0]);
	  close(wfd);
	  flags &= ~LIVE_W;
	}
       else
	{ trace_print(tag,">>",&buf[0],n);
	  write(wfd,&buf[0],n);
	}
     }
    if ((flags & LIVE_R) && FD_ISSET(rfd,&fds))
     { n = read(rfd,&buf[0],sizeof(buf));
       if (n < 0)
	{ fprintf(stderr,"%s: read error: %s\n",__progname,strerror(errno));
	  exit(1);
	}
       if (n == 0)
	{ fprintf(stderr,"%s [closed <<]\n",tag);
	  close(rfd);
	  close(p1[1]);
	  flags &= ~LIVE_R;
	}
       else
	{ trace_print(tag,"<<",&buf[0],n);
	  write(p1[1],&buf[0],n);
	}
     }
  }
 exit(0);
#undef LIVE_R
#undef LIVE_W
}
#endif

static void xfilter(int rfd, int wfd, const char *tag)
{
 int i;
 int p1[2];
 int p2[2];
 unsigned char binbuf[256];
 unsigned char hexbuf[512];
 unsigned char oddsave;
 int flags;
#define SHUT_R  1
#define SHUT_W  2
#define ODDBYTE 4

#ifdef NO_NETWORK
 tag = tag;
#endif
 if ((pipe(&p1[0]) < 0) || (pipe(&p2[0]) < 0))
  { fprintf(stderr,"%s: can't create pipe: %s\n",__progname,strerror(errno));
    exit(1);
  }
 switch (fork())
  { case -1:
       fprintf(stderr,"%s: can't fork: %s\n",__progname,strerror(errno));
       exit(1);
       break;
    case 0:
       break;
    default:
       dup2(p1[0],rfd);
       close(p1[1]);
       close(p2[0]);
       dup2(p2[1],wfd);
       return;
       break;
  }
 for (i=getdtablesize()-1;i>=0;i--)
  { if ((i != 2) && (i != p1[1]) && (i != p2[0]) && (i != rfd) && (i != wfd))
     { close(i);
     }
  }
 flags = 0;
 while (1)
  { fd_set fds;
    int n;
    if ((flags & (SHUT_R|SHUT_W)) == (SHUT_R|SHUT_W)) exit(0);
    FD_ZERO(&fds);
    if (! (flags & SHUT_W)) FD_SET(p2[0],&fds);
    if (! (flags & SHUT_R)) FD_SET(rfd,&fds);
    n = select(FD_SETSIZE,&fds,(fd_set *)0,(fd_set *)0,(struct timeval *)0);
    if (n < 0)
     { if (errno != EINTR)
	{ fprintf(stderr,"%s: select error: %s\n",__progname,strerror(errno));
	  exit(1);
	}
       continue;
     }
    if (FD_ISSET(p2[0],&fds))
     { n = read(p2[0],(char *)&binbuf[0],sizeof(binbuf));
       if (n < 0)
	{ fprintf(stderr,"%s: bin read error: %s\n",__progname,strerror(errno));
	  exit(1);
	}
       if (n == 0)
	{ close(wfd);
	  flags |= SHUT_W;
	}
       else
	{ bin2hex(&binbuf[0],&hexbuf[0],n);
#ifndef NO_NETWORK
	  if (flag_trace) trace_print(tag,"(enc)>>",&hexbuf[0],n*2);
#endif
	  write(wfd,(char *)&hexbuf[0],n*2);
	}
     }
    if (FD_ISSET(rfd,&fds))
     { if (flags & ODDBYTE)
	{ n = read(rfd,(char *)&hexbuf[1],sizeof(hexbuf)-1);
	  if (n < 0)
	   { fprintf(stderr,"%s: hex read error: %s\n",__progname,strerror(errno));
	     exit(1);
	   }
	  if (n == 0)
	   { close(p1[1]);
	     flags |= SHUT_R;
	   }
	  else
	   { n ++;
	     hexbuf[0] = oddsave;
	     flags &= ~ODDBYTE;
	   }
	}
       else
	{ n = read(rfd,(char *)&hexbuf[0],sizeof(hexbuf));
	  if (n < 0)
	   { fprintf(stderr,"%s: hex read error: %s\n",__progname,strerror(errno));
	     exit(1);
	   }
	  if (n == 0)
	   { close(p1[1]);
	     flags |= SHUT_R;
	   }
	}
       if (n)
	{ if (n & 1)
	   { oddsave = hexbuf[--n];
	     flags |= ODDBYTE;
	   }
	  n /= 2;
#ifndef NO_NETWORK
	  if (flag_trace) trace_print(tag,"(enc)<<",&hexbuf[0],n*2);
#endif
	  hex2bin(&hexbuf[0],&binbuf[0],n);
	  write(p1[1],(char *)&binbuf[0],n);
	}
     }
  }
#undef SHUT_R
#undef SHUT_W
#undef ODDBYTE
}

static void newremote(char *arg)
{
 REMOTE *r;
 char *slash;
 char *at;
 char *colon;
 char *bang;
 char *bracket;
 char *ebracket;

 auto void badspec(const char *) __attribute__((__noreturn__));
 static void badspec(const char *why)
  { fprintf(stderr,"%s: %s: %s\n",__progname,arg,why);
    exit(1);
  }

 r = NEW(REMOTE);
 r->rfd = -1;
 r->wfd = -1;
 r->link = remotes;
 remotes = r;
 r->fullarg = arg;
 arg = strdup(arg);
 if (arg[0] == ':')
  { r->machine = (void *) r;
    r->user = arg;
  }
 else do
  { slash = index(arg,'/');
    at = index(arg,'@');
    colon = index(arg,':');
    bang = index(arg,'!');
    bracket = index(arg,'[');
    ebracket = bracket ? index(bracket+1,']') : 0;
    /*
	can have  user  machine             program  directory
	    @     no    yes iff user given  yes      yes
	    !     yes   no                  yes      yes
	    :     no    yes iff in []       no       only after a /
	    /     no    no                  yes      yes
	    [     no    yes (but bad idea)  yes      yes
	    ]     yes   yes iff not in []   yes      yes

	   user @   machine   ! program : directory  (case 1)
	   user @ [ machine ] ! program : directory  (case 2)
		    machine   ! program : directory  (case 3)
		  [ machine ] ! program : directory  (case 4)
	   user @   machine             : directory  (case 5)
	   user @ [ machine ]           : directory  (case 6)
		    machine             : directory  (case 7)
		  [ machine ]           : directory  (case 8)
					  directory  (case 9)
    */
    if ( at && bang && colon &&
	 (at < bang) && (bang < colon) &&
	 (!slash || (bang < slash)) &&
	 (!bracket || (bang < bracket)) )
     { /* case 1 */
       r->user = arg;
       *at = '\0';
       r->machine = at + 1;
       *bang = 0;
       r->program = bang + 1;
       *colon = 0;
       r->directory = colon + 1;
       break;
     }
    if ( at && colon &&
	 (at < colon) &&
	 (!bang || (colon < bang)) &&
	 (!slash || (colon < slash)) &&
	 (!bracket || (colon < bracket)) )
     { /* case 5 */
       r->user = arg;
       *at = '\0';
       r->machine = at + 1;
       *bang = 0;
       r->program = bang + 1;
       *colon = 0;
       r->directory = colon + 1;
       break;
     }
    if (at && bracket && (bracket == at+1) && (!slash || (at < slash)))
     { /* case 2 or 6, or error */
       if (! ebracket) badspec("unclosed [");
       bang = index(ebracket+1,'!');
       colon = index(ebracket+1,':');
       slash = index(ebracket+1,'/');
       if ((bang == ebracket+1) && colon)
	{ /* case 2 */
	  r->user = arg;
	  *at = '\0';
	  r->machine = bracket + 1;
	  *ebracket = '\0';
	  r->program = bang + 1;
	  *colon = '\0';
	  r->directory = colon + 1;
	  break;
	}
       if (colon == ebracket+1)
	{ /* case 6 */
	  r->user = arg;
	  *at = '\0';
	  r->machine = bracket + 1;
	  *ebracket = '\0';
	  r->program = 0;
	  r->directory = colon + 1;
	  break;
	}
       badspec("[ ] machine name not followed by !...: or :");
     }
    if (bracket == arg)
     { /* case 4 or 8, or error */
       if (! ebracket) badspec("unclosed [");
       bang = index(ebracket+1,'!');
       colon = index(ebracket+1,':');
       slash = index(ebracket+1,'/');
       if ((bang == ebracket+1) && colon)
	{ /* case 4 */
	  r->user = 0;
	  r->machine = bracket + 1;
	  *ebracket = '\0';
	  r->program = bang + 1;
	  *colon = '\0';
	  r->directory = colon + 1;
	  break;
	}
       if (colon == ebracket+1)
	{ /* case 8 */
	  r->user = 0;
	  r->machine = bracket + 1;
	  *ebracket = '\0';
	  r->program = 0;
	  r->directory = colon + 1;
	  break;
	}
       badspec("[ ] machine name not followed by !...: or :");
     }
    if (bang && colon && (bang < colon) && (!slash || (bang < slash)))
     { /* case 3 */
       r->user = 0;
       r->machine = arg;
       *bang = '\0';
       r->program = bang + 1;
       *colon = '\0';
       r->directory = colon + 1;
       break;
     }
    if (colon && (!slash || (colon < slash)))
     { /* case 7 */
       r->user = 0;
       r->machine = arg;
       *colon = '\0';
       r->program = 0;
       r->directory = colon + 1;
       break;
     }
    /* case 9 */
    r->user = 0;
    r->machine = 0;
    r->program = 0;
    r->directory = arg;
  } while (0);
 r->rsh = currsh;
 r->ignnx = flag_nextinx;
 r->mappings = pendmap;
 pendmap = 0;
 r->links = 0;
 flag_nextinx = 0;
}

static void reaper(int sig __attribute__((__unused__)))
{
 while (wait3(0,WNOHANG,0) > 0) ;
}

static void sgetnts(void)
{
 int x;
 int c;

 x = 0;
 while (1)
  { c = getchar();
    if (c == EOF)
     { fprintf(stderr,"%s: unexpected EOF (in sgetnts)\n",__progname);
       exit(1);
     }
    slavework[x] = c;
    if (c == '\0') return;
    if (x < WORKSIZE-1) x ++;
  }
}

static void etc_init(ENTRY_TCONC *etc)
{
 etc->tail = &etc->list;
}

static void etc_add(ENTRY_TCONC *etc, ENTRY *e)
{
 *etc->tail = e;
 etc->tail = &e->link;
}

static void etc_end(ENTRY_TCONC *etc)
{
 *etc->tail = 0;
}

static void etc_append(ENTRY_TCONC *etc, ENTRY *e)
{
 if (! e) return;
 *etc->tail = e;
 while (e->link) e = e->link;
 etc->tail = &e->link;
}

static void fork_filter(int ifd, int ofd, const char *argv0, ...)
{
 va_list ap;
 int p[2];
 int na;
 const char **av;
 void (*fn)(void);
 int kid;
 int i;

 if ((ifd < 0) && (ofd < 0)) panic("fork_filter: both fds negative");
 if ((ifd >= 0) && (ofd >= 0)) panic("fork_filter: neither fd negative");
 if (pipe(p) < 0) panic("fork_filter: can't make pipe: %s",strerror(errno));
 if (argv0)
  { va_start(ap,argv0);
    for (na=1;va_arg(ap,const char *);na++) ;
    va_end(ap);
    av = malloc((na+1)*sizeof(const char *));
    av[0] = argv0;
    va_start(ap,argv0);
    for (na=1;(av[na]=va_arg(ap,const char *));na++) ;
    va_end(ap);
  }
 else
  { typedef void (*foo)(void); /* stupid varargs misfeature */
    va_start(ap,argv0);
    fn = va_arg(ap,foo);
    va_end(ap);
  }
 while (wait3(0,WNOHANG,0) > 0) ;
 kid = fork();
 if (kid)
  { if (ifd < 0)
     { close(ofd);
       close(p[0]);
       dup2(p[1],ofd);
       close(p[1]);
     }
    else
     { close(ifd);
       close(p[1]);
       dup2(p[0],ifd);
       close(p[0]);
     }
    return;
  }
 for (i=getdtablesize()-1;i>2;i--)
  { if ((i != ifd) && (i != ofd) && (i != p[0]) && (i != p[1])) close(i);
  }
 if (ifd < 0)
  { if (ofd != 1)
     { dup2(ofd,1);
       close(ofd);
     }
    if (p[0] != 0)
     { dup2(p[0],0);
       close(p[0]);
     }
    close(p[1]);
  }
 else
  { if (ifd != 0)
     { dup2(ifd,0);
       close(ifd);
     }
    if (p[1] != 1)
     { dup2(p[1],1);
       close(p[1]);
     }
    close(p[0]);
  }
 if (argv0)
  { execvp(argv0,(const void *)av);
    perror(argv0);
  }
 else
  { (*fn)();
  }
 exit(1);
}

static void slave_rm(void); /* forward */

/* always sets errno */
static void slave_rm_r(void)
{
 ENTRY_TCONC etc;
 ENTRY *e;
 DIR *dir;
 struct direct *d;
 int err;
 int len;

 err = 0;
 etc_init(&etc);
 dir = opendir(&slavetemp[0]);
 while ((d=readdir(dir)))
  { if ( (d->d_name[0] == '.') &&
	 ( DLEN_IS(d,1) ||
	   ( (d->d_name[1] == '.') &&
	     DLEN_IS(d,2) ) ) ) continue;
    e = NEW(ENTRY);
    e->name = strdup(&d->d_name[0]);
    etc_add(&etc,e);
  }
 closedir(dir);
 etc_end(&etc);
 len = strlen(&slavetemp[0]);
 slavetemp[len++] = '/';
 while (etc.list)
  { e = etc.list;
    etc.list = e->link;
    strcpy(&slavetemp[len],e->name);
    slave_rm();
    if (err == 0) err = errno;
    free(e->name);
    OLD(e);
  }
 slavetemp[len-1] = '\0';
 errno = err;
}

/* unlink(2) on a directory either produces EPERM or orphans the stuff in
   the directory, depending on system; it doesn't necessarily do anything
   sensible and distinctive, like EISDIR, so we have to waste a syscall.... */

/* always sets errno */
static void slave_rm(void)
{
 errno = 0;
 if (rmdir(&slavetemp[0]) == 0) return;
 switch (errno)
  { case ENOTEMPTY:
       slave_rm_r();
       if ((errno == 0) && (rmdir(&slavetemp[0]) == 0)) errno = 0;
       break;
    case ENOTDIR:
       if (unlink(&slavetemp[0]) == 0) errno = 0;
       break;
    default:
       break;
  }
}

static int makenulls(int fd, unsigned long int bytes)
{
 int n;
 int r;
 int w;
 char rbuf[512];

 while (bytes > 0)
  { n = (bytes < 512) ? bytes : 512;
    r = read(fd,&rbuf[0],n);
    if (r < 0) return(-1);
    if (r == 0) return(lseek(fd,bytes,L_INCR));
    if (bcmp(&rbuf[0],&zeroblk[0],r))
     { if (lseek(fd,-r,L_INCR) < 0) return(-1);
       w = write(fd,&zeroblk[0],r);
       if (w < 0) return(-1);
       if (w < r) return(EIO);
     }
    bytes -= r;
  }
 return(0);
}

static void swrite(int skip, const void *buf, int nb)
{
 int w;

 if ((skip > 0) && (makenulls(1,skip*512) < 0))
  { fprintf(stderr,"%s: sparsewrite seek error: %s\n",__progname,strerror(errno));
    exit(1);
  }
 if (nb > 0)
  { w = write(1,buf,nb);
    if (w != nb)
     { if (w < 0)
	{ fprintf(stderr,"%s: sparsewrite write error: %s\n",__progname,strerror(errno));
	  exit(1);
	}
       fprintf(stderr,"%s: sparsewrite write: tried %d, got %d\n",__progname,nb,w);
       exit(1);
     }
  }
}

/* not all ftruncate()s will grow short files, grr! */
static void setsize(int fd, unsigned long long int size)
{
 struct stat stb;

 ftruncate(fd,size);
 fstat(fd,&stb);
 if (stb.st_size < size)
  { lseek(fd,size+(1ULL<<20),L_SET);
    write(fd,"",1);
    ftruncate(fd,size);
  }
}

static void sparsewrite(void)
{
 int inbuf;
 int base;
 int n;
 int skip;
 int w0;
 unsigned long long int size;

 inbuf = 0;
 skip = 0;
 size = 0;
 while (1)
  { if (inbuf == 0) base = 0;
    n = read(0,&slavework[base],WORKSIZE-base);
    if (n < 0)
     { fprintf(stderr,"%s: sparsewrite read error: %s\n",__progname,strerror(errno));
       exit(1);
     }
    if (n == 0)
     { swrite(skip,&slavework[base],inbuf);
       setsize(1,size);
       exit(0);
     }
    size += n;
    inbuf += n;
    w0 = -1;
    while (inbuf >= 512)
     { if (bcmp(&slavework[base],&zeroblk[0],512))
	{ if (w0 < 0) w0 = base;
	}
       else
	{ if (w0 >= 0)
	   { swrite(skip,&slavework[w0],base-w0);
	     skip = 0;
	     w0 = -1;
	   }
	  skip ++;
	}
       inbuf -= 512;
       base += 512;
     }
    if (w0 >= 0)
     { swrite(skip,&slavework[w0],base-w0);
       skip = 0;
     }
  }
}

static void slavepathcheck(void)
{
 char *dp;
 char *wp;

 wp = &slavework[0];
 if ((wp[0] == '.') && (wp[1] == '.') && ((wp[2] == '/') || (wp[2] == '\0')))
  {
badpath:;
    fprintf(stderr,"%s: bad path %s from master\n",__progname,&slavework[0]);
    exit(1);
  }
 wp ++;
 while (1)
  { dp = index(wp,'.');
    if (! dp) break;
    if ( (dp[-1] == '/') &&
	 (dp[1] == '.') &&
	 ((dp[2] == '\0') || (dp[2] == '/')) ) goto badpath;
    wp = dp + 1;
  }
}

static void slave_D(void)
{
 DIR *dir;
 struct direct *d;
 int l;
 int ll;
 struct stat stb;

 sgetnts();
 slavepathcheck();
 sprintf(&slavetemp[0],"%s/%s",&slavebase[0],&slavework[0]);
 dir = opendir(&slavetemp[0]);
 if (dir == 0)
  { putchar('\0');
    return;
  }
 l = strlen(slavetemp);
 slavetemp[l++] = '/';
 while ((d=readdir(dir)))
  { if ( (d->d_ino == 0) ||
	 ( (d->d_name[0] == '.') &&
	   ( (d->d_name[1] == '\0') ||
	     ( (d->d_name[1] == '.') &&
	       (d->d_name[2] == '\0') ) ) ) ) continue;
    strcpy(&slavetemp[l],d->d_name);
    if ((flag_follow?stat(&slavetemp[0],&stb):lstat(&slavetemp[0],&stb)) < 0) continue;
    fputs(d->d_name,stdout);
    putchar('\0');
    printf("%d %d %d",(int)stb.st_mode,(int)stb.st_uid,(int)stb.st_gid);
    if (flag_mtimes) printf(" %ld",(long int)stb.st_mtime);
    if (flag_links) printf(" %d %llu %llu",
	(int)stb.st_nlink,
	(unsigned long long int)stb.st_dev,
	(unsigned long long int)stb.st_ino );
    switch (stb.st_mode & S_IFMT)
     { case S_IFDIR:
	  break;
       case S_IFCHR:
       case S_IFBLK:
	  printf(" %llu %llu",(unsigned long long int)major(stb.st_rdev),(unsigned long long int)minor(stb.st_rdev));
	  break;
       case S_IFREG:
	  printf(" %llu",(unsigned long long int)stb.st_size);
	  break;
       case S_IFLNK:
	  putchar('\0');
	  ll = readlink(&slavetemp[0],&slavework[0],sizeof(slavework));
	  if (ll < 0) ll = 0;
	  fwrite(&slavework[0],1,ll,stdout);
	  break;
       case S_IFSOCK:
	  break;
#ifdef S_IFIFO
       case S_IFIFO:
	  break;
#endif
     }
    putchar('\0');
  }
 closedir(dir);
 putchar('\0');
}

static void slave_I(void)
{
 int fd;
 int cmd;
 int nb;
 int nr;
 unsigned long long int off;
 unsigned long long int curoff;

 sgetnts();
 slavepathcheck();
 sprintf(&slavetemp[0],"%s/%s",&slavebase[0],&slavework[0]);
 fd = open(&slavetemp[0],O_RDONLY,0);
 if (flag_debug) fprintf(stderr,"slave_I entering command loop\n");
 curoff = 0;
 while (1)
  { fflush(stdout);
    cmd = getchar();
    switch (cmd)
     { default:
	  fprintf(stderr,"%s: protocol error, unknown slave I subcommand %d (%c)\n",__progname,cmd,cmd);
	  exit(1);
	  break;
       case EOF:
	  fprintf(stderr,"%s: protocol error, unexpected EOF (slave_I)\n",__progname);
	  exit(1);
	  break;
       case 'c':
	  goto out; /* aka break 2 */
	  break;
       case 'r':
	  sgetnts();
	  sscanf(&slavework[0],"%d%llu",&nb,&off);
	  if (nb > WORKSIZE)
	   { fprintf(stderr,"%s: protocol error, nb too big (%d > %d) in slave R r\n",__progname,nb,WORKSIZE);
	     exit(1);
	   }
	  if (off != curoff) lseek(fd,off,L_SET);
	  curoff = off;
	  nr = read(fd,&slavetemp[0],nb);
	  if (nr <= 0)
	   { printf("%d",COPYBLK_HOLE);
	     putchar('\0');
	   }
	  else
	   { if (nr < nb) bzero(&slavetemp[nr],nb-nr);
	     if (bcmp(&slavetemp[0],&zeroblk[0],nb))
	      { printf("%d",COPYBLK_DATA);
		putchar('\0');
		fwrite(&slavetemp[0],1,nb,stdout);
	      }
	     else
	      { printf("%d",COPYBLK_HOLE);
		putchar('\0');
	      }
	   }
	  curoff += (nr >= 0) ? nr : 0;
	  break;
     }
  }
out:;
 if (fd >= 0) close(fd);
}

static void slave_K(void)
{
 int fd;
 void *md5;
 int nb;
 unsigned long long int from;
 unsigned long long int to;
 int n;
 char cksum[16];

 sgetnts();
 slavepathcheck();
 sprintf(&slavetemp[0],"%s/%s",&slavebase[0],&slavework[0]);
 sgetnts();
 sscanf(&slavework[0],"%llu %llu",&from,&to);
 if (flag_debug) fprintf(stderr,"slave_K %s %llu %llu\n",&slavetemp[0],from,to);
 fd = open(&slavetemp[0],O_RDONLY,0);
 if (fd < 0)
  { printf("O%d",errno);
    putchar('\0');
  }
 else
  { md5 = md5_init();
    lseek(fd,from,SEEK_SET);
    to -= from;
    while (1)
     { n = ullmin(to,WORKSIZE);
       if (n < 1)
	{ md5_result(md5,&cksum[0]);
	  printf("K");
	  fwrite(&cksum[0],1,16,stdout);
	  break;
	}
       nb = n ? read(fd,&slavetemp[0],n) : 0;
       if (nb < 0)
	{ printf("R%d",errno);
	  putchar('\0');
	  break;
	}
       if (nb == 0)
	{ printf("R0");
	  putchar('\0');
	  break;
	}
       md5_process_bytes(md5,&slavetemp[0],nb);
       to -= nb;
     }
    close(fd);
  }
}

static void slave_L(void)
{
 sgetnts();
 slavepathcheck();
 sprintf(&slavetemp[0],"%s/%s",&slavebase[0],&slavework[0]);
 sgetnts();
 sprintf(&slavetemp2[0],"%s/%s",&slavebase[0],&slavework[0]);
 if (flag_debug) fprintf(stderr,"slave_L %s -> %s\n",&slavetemp[0],&slavetemp2[0]);
 if (flag_readonly)
  { printf("%d",EROFS);
    putchar('\0');
    return;
  }
 if (link(&slavetemp2[0],&slavetemp[0]) == 0) errno = 0;
 printf("%d",errno);
 putchar('\0');
}

static void slave_R(void)
{
 int fd;
 int cmd;
 int nb;
 int nr;
 unsigned long long int off;
 unsigned long long int curoff;

 sgetnts();
 slavepathcheck();
 sprintf(&slavetemp[0],"%s/%s",&slavebase[0],&slavework[0]);
 fd = open(&slavetemp[0],O_RDONLY,0);
 if (flag_debug) fprintf(stderr,"slave_R entering command loop\n");
 curoff = 0;
 while (1)
  { fflush(stdout);
    cmd = getchar();
    switch (cmd)
     { default:
	  fprintf(stderr,"%s: protocol error, unknown slave R subcommand %d (%c)\n",__progname,cmd,cmd);
	  exit(1);
	  break;
       case EOF:
	  fprintf(stderr,"%s: protocol error, unexpected EOF (slave_R)\n",__progname);
	  exit(1);
	  break;
       case 'c':
	  if (fd >= 0) close(fd);
	  return;
	  break;
       case 'r':
	  sgetnts();
	  sscanf(&slavework[0],"%d%llu",&nb,&off);
	  if (nb > WORKSIZE)
	   { fprintf(stderr,"%s: protocol error, nb too big (%d > %d) in slave R r\n",__progname,nb,WORKSIZE);
	     exit(1);
	   }
	  if (off != curoff) lseek(fd,curoff,L_SET);
	  curoff = off;
	  nr = read(fd,&slavetemp[0],nb);
	  if (nr <= 0)
	   { curoff = -1;
	     fwrite(&zeroblk[0],1,nb,stdout);
	   }
	  else
	   { curoff += nr;
	     if (nr < nb)
	      { fwrite(&slavetemp[0],1,nr,stdout);
		fwrite(&zeroblk[0],1,nb-nr,stdout);
	      }
	     else
	      { fwrite(&slavetemp[0],1,nb,stdout);
	      }
	   }
	  break;
     }
  }
}

static void slave_d(void)
{
 int mode;
 int uid;
 int gid;
 struct stat stb;

 sgetnts();
 slavepathcheck();
 sprintf(&slavetemp[0],"%s/%s",&slavebase[0],&slavework[0]);
 sgetnts();
 sscanf(&slavework[0],"%d%d%d",&mode,&uid,&gid);
 if (flag_debug) fprintf(stderr,"slave_d %s %o %d %d\n",&slavetemp[0],mode,uid,gid);
 if (flag_readonly)
  { printf("%d",EROFS);
    putchar('\0');
    return;
  }
 if ( (mkdir(&slavetemp[0],mode) == 0) &&
      (stat(&slavetemp[0],&stb) == 0) &&
      ( (((stb.st_mode^mode) & (flag_sgiddir?05777:07777)) == 0) ||
	(chmod(&slavetemp[0],flag_sgiddir?((stb.st_mode&02000)|(mode&05777)):mode) == 0) ) &&
      ( flag_noown ||
	( (chown(&slavetemp[0],-1,gid) == 0) &&
	  (chown(&slavetemp[0],uid,-1) == 0) ) ) ) errno = 0;
 printf("%d",errno);
 putchar('\0');
}

static void slave_f(void)
{
 int mode;
 int uid;
 int gid;

 sgetnts();
 slavepathcheck();
 sprintf(&slavetemp[0],"%s/%s",&slavebase[0],&slavework[0]);
 sgetnts();
 sscanf(&slavework[0],"%d%d%d",&mode,&uid,&gid);
 if (flag_debug) fprintf(stderr,"slave_f %s %o %d %d\n",&slavetemp[0],mode,uid,gid);
 if (flag_readonly)
  { printf("%d",EROFS);
    putchar('\0');
    return;
  }
#ifdef S_IFIFO
 if ( (mkfifo(&slavetemp[0],mode) == 0) &&
      ( flag_noown ||
	( (chown(&slavetemp[0],-1,gid) == 0) &&
	  (chown(&slavetemp[0],uid,-1) == 0) ) ) ) errno = 0;
#else
 errno = ENODEV;
#endif
 printf("%d",errno);
 putchar('\0');
}

static int open_to_update(const char *path, int mode, int *remodp)
{
 int rechmod;
 int fd;

 rechmod = -1;
 mode &= 07777;
 fd = open(path,O_RDWR|O_CREAT,mode);
 if (fd < 0)
  { fd = open(path,O_WRONLY|O_CREAT,mode);
    if (fd < 0)
     { struct stat stb;
       if (errno != EACCES) return(-1);
       if ( (stat(path,&stb) < 0) ||
	    (chmod(path,0600) < 0) )
	{ errno = EACCES;
	  return(-1);
	}
       rechmod = stb.st_mode & 07777;
       fd = open(path,O_RDWR,0);
       if (fd < 0)
	{ int e;
	  e = errno;
	  chmod(path,rechmod);
	  errno = e;
	  return(-1);
	}
     }
  }
 *remodp = rechmod;
 return(fd);
}

static void slave_i(void)
{
 int mode;
 int uid;
 int gid;
 unsigned long long int size;
 unsigned long long int from;
 unsigned long long int to;
 int fd;
 unsigned long long int left;
 int blkleft;
 int blkoff;
 int flg;
 int n;
 int r;
 int tempfill;
 int skip;
 int w0;
 int writearg;
 int writerv;
 int finalerr;
 int rechmod;

 sgetnts();
 slavepathcheck();
 sprintf(&slavetemp[0],"%s/%s",&slavebase[0],&slavework[0]);
 sgetnts();
 sscanf(&slavework[0],"%d%d%d%llu%llu%llu",&mode,&uid,&gid,&size,&from,&to);
 if (flag_debug) fprintf(stderr,"slave_i %s %o %d %d %llu %llu %llu\n",&slavetemp[0],mode&07777,uid,gid,size,from,to);
 if (flag_readonly)
  { printf("%d",EROFS);
    putchar('\0');
    return;
  }
 fd = open_to_update(&slavetemp[0],mode&07777,&rechmod);
 if (fd < 0)
  { printf("%d",errno);
    putchar('\0');
    return;
  }
 if ( ( !flag_noown &&
	( (fchown(fd,-1,gid) < 0) ||
	  (fchown(fd,uid,-1) < 0) ) ) ||
      (lseek(fd,from,SEEK_SET) < 0) )
  { printf("%d",errno);
    putchar('\0');
    if (rechmod >= 0) fchmod(fd,rechmod);
    close(fd);
    return;
  }
 putchar('0');
 putchar('\0');
 fflush(stdout);
 blkleft = 0;
 blkoff = 0;
 tempfill = 0;
 left = to - from;
 skip = 0;
 finalerr = 0;
#define WRITE(buf,nb) do { if ((writearg=(nb)) && (writerv=write(fd,(buf),writearg)) != writearg) goto writeerr; } while (0)
 while ((left > 0) || (blkleft > 0))
  { if (0)
     {
writeerr:;
       finalerr = (writerv < 0) ? errno : EIO;
       if (rechmod >= 0) fchmod(fd,rechmod);
       close(fd);
       fd = -1;
     }
    if (0)
     {
fderr:;
       finalerr = errno;
       if (rechmod >= 0) fchmod(fd,rechmod);
       close(fd);
       fd = -1;
     }
    if (blkleft < 1)
     { sgetnts();
       if (sscanf(&slavework[0],"%d%d",&n,&flg) != 2)
	{ fprintf(stderr,"%s: protocol error (can't read block size and type)\n",__progname);
	  exit(1);
	}
       blkleft += n;
       blkoff = 0;
       left -= n;
     }
    switch (flg)
     { case COPYBLK_DATA:
	  n = WORKSIZE - tempfill;
	  if (blkleft < n) n = blkleft;
	  r = fread(&slavework[tempfill],1,n,stdin);
	  if (r != n)
	   { fprintf(stderr,"%s: protocol error (can't read block data)\n",__progname);
	     exit(1);
	   }
	  blkleft -= n;
	  blkoff += n;
	  if (fd < 0) continue;
	  n += tempfill;
	  r = 0;
	  w0 = -1;
	  while (n-r >= 512)
	   { if (flag_nosparse || bcmp(&slavework[r],&zeroblk[0],512))
	      { if (w0 < 0) w0 = r;
		if (skip > 0)
		 { if (makenulls(fd,skip) < 0) goto fderr;
		   skip = 0;
		 }
	      }
	     else
	      { if (w0 >= 0)
		 { WRITE(&slavework[w0],r-w0);
		   w0 = -1;
		 }
		skip += 512;
	      }
	     r += 512;
	   }
	  if (w0 >= 0) WRITE(&slavework[w0],r-w0);
	  tempfill = n - r;
	  if (tempfill && (r > 0)) bcopy(&slavework[r],&slavework[0],tempfill);
	  break;
       case COPYBLK_HOLE:
	  n = blkleft;
	  blkleft = 0;
	  blkoff += n;
	  if (fd < 0) continue;
	  if (tempfill+n < 512)
	   { bzero(&slavework[tempfill],n);
	     tempfill += n;
	   }
	  else
	   { if (flag_nosparse)
	      { if (tempfill > 0)
		 { bzero(&slavework[tempfill],512-tempfill);
		   WRITE(&slavework[0],512);
		   n -= 512 - tempfill;
		 }
		while (n >= WORKSIZE)
		 { WRITE(&zeroblk[0],WORKSIZE);
		   n -= WORKSIZE;
		 }
		if (n >= 512)
		 { WRITE(&zeroblk[0],n&~511);
		   n &= 511;
		 }
		if (n > 0) bzero(&slavework[0],n);
		tempfill = n;
	      }
	     else
	      { if (tempfill)
		 { if (bcmp(&slavework[0],&zeroblk[0],tempfill))
		    { bzero(&slavework[tempfill],512-tempfill);
		      if (skip > 0)
		       { if (makenulls(fd,skip) < 0) goto fderr;
			 skip = 0;
		       }
		      WRITE(&slavework[0],512);
		      n -= 512 - tempfill;
		    }
		   else
		    { n += tempfill;
		    }
		 }
		skip += n;
		tempfill = skip % 512;
		if (tempfill)
		 { skip -= tempfill;
		   bzero(&slavework[0],tempfill);
		 }
	      }
	   }
	  break;
     }
  }
 if (tempfill > 0)
  { if (skip > 0)
     { if (makenulls(fd,skip) < 0)
	{ finalerr = errno;
	  if (rechmod >= 0) fchmod(fd,rechmod);
	  close(fd);
	  fd = -1;
	}
       skip = 0;
     }
    if (fd >= 0) WRITE(&slavework[0],tempfill);
  }
 if (fd >= 0)
  { setsize(fd,size);
    if (rechmod >= 0) fchmod(fd,rechmod);
    close(fd);
    putchar('0');
  }
 else
  { printf("%d",finalerr);
  }
#undef WRITE
 putchar('\0');
}

static void slave_l(void)
{
 int mode;
 int uid;
 int gid;

 sgetnts();
 slavepathcheck();
 sprintf(&slavetemp[0],"%s/%s",&slavebase[0],&slavework[0]);
 sgetnts();
 sscanf(&slavework[0],"%d%d%d",&mode,&uid,&gid);
 sgetnts();
 if (flag_debug) fprintf(stderr,"slave_l %s -> %s\n",&slavetemp[0],&slavework[0]);
 if (flag_readonly)
  { printf("%d",EROFS);
    putchar('\0');
    return;
  }
 if ( (symlink(&slavework[0],&slavetemp[0]) == 0) &&
      (lchmod(&slavetemp[0],mode) == 0) &&
      ( flag_noown ||
	( (lchown(&slavetemp[0],-1,gid) == 0) &&
	  (lchown(&slavetemp[0],uid,-1) == 0) ) ) ) errno = 0;
 printf("%d",errno);
 putchar('\0');
}

static void slave_m(void)
{
 int mode;

 sgetnts();
 slavepathcheck();
 sprintf(&slavetemp[0],"%s/%s",&slavebase[0],&slavework[0]);
 sgetnts();
 sscanf(&slavework[0],"%d",&mode);
 if (flag_debug) fprintf(stderr,"slave_m %s %o\n",&slavetemp[0],mode&07777);
 if (flag_readonly)
  { printf("%d",EROFS);
    putchar('\0');
    return;
  }
 if (flag_sgiddir)
  { struct stat stb;
    if ((stat(&slavetemp[0],&stb) == 0) && ((stb.st_mode & S_IFMT) == S_IFDIR))
     { mode = (mode & 05777) | (stb.st_mode & 02000);
     }
    if (lchmod(&slavetemp[0],mode) == 0) errno = 0;
  }
 else
  { if (lchmod(&slavetemp[0],mode) == 0) errno = 0;
  }
 printf("%d",errno);
 putchar('\0');
}

static void slave_n(void)
{
 int mode;
 int uid;
 int gid;
 unsigned long long int rdev_maj;
 unsigned long long int rdev_min;

 sgetnts();
 slavepathcheck();
 sprintf(&slavetemp[0],"%s/%s",&slavebase[0],&slavework[0]);
 sgetnts();
 sscanf(&slavework[0],"%d %d %d %llu %llu",&mode,&uid,&gid,&rdev_maj,&rdev_min);
 if (flag_debug) fprintf(stderr,"slave_n %s %o %d %d (%llu,%llu)\n",&slavetemp[0],mode,uid,gid,rdev_maj,rdev_min);
 if (flag_readonly)
  { printf("%d",EROFS);
    putchar('\0');
    return;
  }
 if ( (mknod(&slavetemp[0],mode,makedev(rdev_maj,rdev_min)) == 0) &&
      ( flag_noown ||
	( (chown(&slavetemp[0],-1,gid) == 0) &&
	  (chown(&slavetemp[0],uid,-1) == 0) ) ) ) errno = 0;
 printf("%d",errno);
 putchar('\0');
}

static void slave_o(void)
{
 int uid;
 int gid;

 sgetnts();
 slavepathcheck();
 sprintf(&slavetemp[0],"%s/%s",&slavebase[0],&slavework[0]);
 sgetnts();
 sscanf(&slavework[0],"%d %d",&uid,&gid);
 if (flag_readonly)
  { printf("%d",EROFS);
    putchar('\0');
    return;
  }
 if (flag_debug) fprintf(stderr,"slave_o %s %d %d\n",&slavetemp[0],uid,gid);
 if ( flag_noown ||
      ( (lchown(&slavetemp[0],-1,gid) == 0) &&
	(lchown(&slavetemp[0],uid,-1) == 0) ) ) errno = 0;
 printf("%d",errno);
 putchar('\0');
}

static void slave_r(void)
{
 sgetnts();
 slavepathcheck();
 sprintf(&slavetemp[0],"%s/%s",&slavebase[0],&slavework[0]);
 if (flag_debug) fprintf(stderr,"slave_r %s\n",&slavetemp[0]);
 if (flag_readonly)
  { printf("%d",EROFS);
    putchar('\0');
    return;
  }
 slave_rm();
 printf("%d",errno);
 putchar('\0');
}

static void slave_t(void)
{
 unsigned long long int sz;

 sgetnts();
 slavepathcheck();
 sprintf(&slavetemp[0],"%s/%s",&slavebase[0],&slavework[0]);
 sgetnts();
 sscanf(&slavework[0],"%llu",&sz);
 if (flag_debug) fprintf(stderr,"slave_t %s %llu\n",&slavetemp[0],sz);
 if (flag_readonly)
  { printf("%d",EROFS);
    putchar('\0');
    return;
  }
 /* not all truncate()s will grow short files, grr! */
 if (truncate(&slavetemp[0],sz) >= 0)
  { struct stat stb;
    if (stat(&slavetemp[0],&stb) >= 0)
     { if (stb.st_size < sz)
	{ int fd;
	  fd = open(&slavetemp[0],O_RDWR,0);
	  if (fd >= 0)
	   { lseek(fd,sz+(1ULL<<20),L_SET);
	     write(fd,"",1);
	     ftruncate(fd,sz);
	     close(fd);
	     errno = 0;
	   }
	}
       else if (stb.st_size > sz)
	{ errno = ERANGE;
	}
       else
	{ errno = 0;
	}
     }
  }
 printf("%d",errno);
 putchar('\0');
}

static void slave_u(void)
{
 struct timeval times[2];
 struct stat stb;
 unsigned long int sec;
 unsigned long int usec;

 sgetnts();
 slavepathcheck();
 sprintf(&slavetemp[0],"%s/%s",&slavebase[0],&slavework[0]);
 sgetnts();
 sscanf(&slavework[0],"%lu %lu",&sec,&usec);
 times[1].tv_sec = sec;
 times[1].tv_usec = usec;
 stat(&slavetemp[0],&stb);
 times[0].tv_sec = stb.st_atime;
 times[0].tv_usec = 0;
 if (flag_debug)
  { time_t tmp;
    tmp = stb.st_atime;
    fprintf(stderr,"slave_u %s %.24s\n",&slavetemp[0],ctime(&tmp));
  }
 if (flag_readonly)
  { printf("%d",EROFS);
    putchar('\0');
    return;
  }
 if (lutimes(&slavetemp[0],&times[0]) == 0) errno = 0;
 printf("%d",errno);
 putchar('\0');
}

static int copy_within(int fd, unsigned long long int from, unsigned long long int to, unsigned long long int len)
{
 char dbuf[65536];
 int n;
 int r;
 int w;

 while (len > 0)
  { n = sizeof(dbuf);
    if (len < n) n = len;
    r = pread(fd,&dbuf[0],n,from);
    if (r < 0) return(errno);
    if (r != n) return(EIO);
    w = pwrite(fd,&dbuf[0],n,to);
    if (w != n) return(EIO);
    from += n;
    to += n;
    len -= n;
  }
 return(0);
}

static void ignore_stdin(unsigned long long int len)
{
 for (;len>0;len--) getchar();
}

static int copy_stdin(int fd, unsigned long long int to, int len)
{
 char dbuf[65536];
 int n;
 int r;
 int w;

 while (len > 0)
  { n = sizeof(dbuf);
    if (len < n) n = len;
    r = fread(&dbuf[0],1,n,stdin);
    if (r != n) return(EIO);
    w = pwrite(fd,&dbuf[0],n,to);
    if (w != n) return(EIO);
    to += n;
    len -= n;
  }
 return(0);
}

static void slave_y(void)
{
 typedef struct htent HTENT;
 typedef struct sums SUMS;

 struct sums {
   unsigned int blk;
   unsigned int crc;
   unsigned char md5[16];
   } ;

 struct htent {
   long long int n;
   long long int base;
   } ;

 int i;
 int j;
 int n;
 int fd;
 int rechmod;
 int mode;
 int uid;
 int gid;
 int err;
 unsigned long long int size;
 unsigned long long int blks;
 unsigned long long int at;
 unsigned long long int at0;
 unsigned long long int loc;
 unsigned long long int len;
 struct stat stb;
 static unsigned int crctable1[256] = { [128] = 0 };
 static unsigned int crctable2[256];
 unsigned int crc;
 void *md5;
 unsigned char buf[65536];
 int ifill;
 int iptr;
 unsigned char sumbuf[RSYNC_BLKSIZE];
 unsigned char sumdrop;
 int sumptr;
 int sumfill;
 HTENT ht[RSYNC_HTSIZE];
 HTENT *e;
 SUMS *sumv;
 unsigned char genbuf[8192];
 int genlen;
 unsigned int genblk;
 unsigned int genblk0;
#define L(x) (((x)<<1)+1)
#define R(x) (((x)+1)<<1)
#define U(x) (((x)-1)>>1)

 static void gen_flush(void)
  { fprintf(stderr,"gen flush\n");
    if (genlen > 0)
     { fprintf(stderr,"gen flush: data %d\n",genlen);
       printf("d%d",genlen);
       putchar('\0');
       fwrite(&genbuf[0],1,genlen,stdout);
     }
    else if (genlen < 0)
     { fprintf(stderr,"gen flush: blocks %u..%u\n",genblk0,genblk);
       printf("b%u %d",genblk0,genblk+1-genblk0);
       putchar('\0');
     }
    genlen = 0;
  }

 static void gen_block(unsigned int blk)
  { fprintf(stderr,"gen block %u\n",blk);
    if ((genlen > 0) || ((genlen < 0) && (genblk != blk-1))) gen_flush();
    if (genlen == 0) genblk0 = blk;
    genblk = blk;
  }

 static void gen_byte(unsigned char b)
  { fprintf(stderr,"gen byte %02x\n",b);
    if ((genlen < 0) || (genlen >= sizeof(genbuf))) gen_flush();
    genbuf[genlen++] = b;
  }

 static int sumcmp(const SUMS *a, const SUMS *b)
  { unsigned int ah;
    unsigned int bh;
    ah = a->crc % RSYNC_HTSIZE;
    bh = b->crc % RSYNC_HTSIZE;
    if (ah < bh) return(-1);
    if (ah > bh) return(1);
    if (a->crc < b->crc) return(-1);
    if (a->crc > b->crc) return(1);
    return(memcmp(&a->md5[0],&b->md5[0],16));
  }

 static void sort_sums(void)
  { long long int i;
    SUMS t;
    static void bubble_down(unsigned long long int x, unsigned long long int n)
     { long long int y;
       SUMS t;
       while (1)
	{ y = R(x);
	  if ((y < n) && (sumcmp(&sumv[x],&sumv[y]) < 0))
	   { if (sumcmp(&sumv[x],&sumv[y-1]) < 0)
	      { if (sumcmp(&sumv[y],&sumv[y-1]) < 0) y --;
	      }
	   }
	  else
	   { if ((y-1 < n) && (sumcmp(&sumv[x],&sumv[y-1]) < 0))
	      { y --;
	      }
	     else
	      { return;
	      }
	   }
	  t = sumv[y];
	  sumv[y] = sumv[x];
	  sumv[x] = t;
	  x = y;
	}
     }
    for (i=U(blks-1);i>=0;i--)
     { bubble_down(i,blks);
     }
    for (i=blks-1;i>=0;i--)
     { t = sumv[0];
       sumv[0] = sumv[i];
       sumv[i] = t;
       bubble_down(0,i);
     }
  }

 if (crctable1[128] != RSYNC_POLY)
  { unsigned int p;
    for (i=0;i<256;i++)
     { p = i;
       for (j=8;j>0;j--) p = (p >> 1) ^ ((p & 1) ? RSYNC_POLY : 0);
       crctable1[i] = p;
     }
    for (i=0;i<256;i++)
     { p = i;
       for (j=RSYNC_BLKSIZE;j>0;j--) p = (p >> 8) ^ crctable1[p&0xff];
       crctable2[i] = p;
     }
  }
 i = getchar();
 switch (i)
  { default:
       fprintf(stderr,"%s: protocol error, bad y subcommand %02x\n",__progname,i);
       exit(1);
       break;
    case EOF:
       fprintf(stderr,"%s: unexpected EOF (in slave_y subcommand)\n",__progname);
       exit(1);
       break;
    case 's':
       sgetnts();
       slavepathcheck();
       sprintf(&slavetemp[0],"%s/%s",&slavebase[0],&slavework[0]);
       sgetnts();
       sscanf(&slavework[0],"%d%d%d%llu",&mode,&uid,&gid,&size);
       if (flag_debug) fprintf(stderr,"slave_y s %s %o %d %d %llu\n",&slavetemp[0],mode,uid,gid,size);
       if (flag_readonly)
	{ printf("%d",EROFS);
	  putchar('\0');
	  return;
	}
       fd = open_to_update(&slavetemp[0],mode,&rechmod);
       if (fd < 0)
	{ printf("%d",errno);
	  putchar('\0');
	  return;
	}
       if ( ( !flag_noown &&
	      ( (fchown(fd,-1,gid) < 0) ||
		(fchown(fd,uid,-1) < 0) ) ) ||
	    (lseek(fd,0,SEEK_SET) < 0) )
	{ printf("%d",errno);
	  putchar('\0');
	  if (rechmod >= 0) fchmod(fd,rechmod);
	  close(fd);
	  return;
	}
       putchar('0');
       putchar('\0');
       fstat(fd,&stb);
       printf("%llu",(unsigned long long int)stb.st_size);
       putchar('\0');
       blks = (stb.st_size + RSYNC_BLKSIZE - 1) / RSYNC_BLKSIZE;
       ifill = 0;
       iptr = 0;
       for (at=blks;at>0;at--)
	{ crc = 0;
	  md5 = md5_init();
	  i = (at == 1) ? stb.st_size - ((blks-1)*RSYNC_BLKSIZE) : RSYNC_BLKSIZE;
	  while (i > 0)
	   { if (iptr >= ifill)
	      { ifill = read(fd,&buf[0],sizeof(buf));
		if (ifill < 0)
		 { fprintf(stderr,"%s: read error on %s: %s\n",__progname,&slavetemp[0],strerror(errno));
		   exit(1);
		 }
		if (ifill == 0)
		 { bzero(&buf[0],sizeof(buf));
		   ifill = sizeof(buf);
		 }
		iptr = 0;
	      }
	     n = (ifill-iptr < i) ? ifill-iptr : i;
	     i -= n;
	     md5_process_bytes(md5,&buf[iptr],n);
	     for (;n>0;n--) crc = (crc >> 8) ^ crctable1[(crc&0xff)^buf[iptr++]];
	   }
	  buf[0] = (crc >> 24) & 0xff;
	  buf[1] = (crc >> 16) & 0xff;
	  buf[2] = (crc >>  8) & 0xff;
	  buf[3] =  crc        & 0xff;
	  md5_result(md5,&buf[4]);
	  fwrite(&buf[0],1,20,stdout);
	}
       fflush(stdout);
       err = 0;
       at = 0;
       while (at < size)
	{ i = getchar();
	  switch (i)
	   { default:
		fprintf(stderr,"%s: protocol error, bad y data subcommand %02x\n",__progname,i);
		exit(1);
		break;
	     case EOF:
		fprintf(stderr,"%s: unexpected EOF (in slave_y data subcommand)\n",__progname);
		exit(1);
		break;
	     case 'b':
		sgetnts();
		sscanf(&slavework[0],"%llu%d",&loc,&n);
		loc *= RSYNC_BLKSIZE;
		len = (loc+n == blks) ? stb.st_size-loc : n*RSYNC_BLKSIZE;
		if (! err) err = copy_within(fd,loc,stb.st_size+at,len);
		at += len;
		break;
	     case 'd':
		sgetnts();
		sscanf(&slavework[0],"%llu",&len);
		if (err) ignore_stdin(len); else err = copy_stdin(fd,stb.st_size+at,len);
		at += len;
		break;
	   }
	}
       if (! err) err = copy_within(fd,stb.st_size,0,size);
       if (err) size = stb.st_size;
       setsize(fd,size);
       if (rechmod >= 0) fchmod(fd,rechmod);
       i = close(fd);
       if (! err) err = i;
       printf("%d",err);
       putchar('\0');
       break;
    case 'm':
       sgetnts();
       slavepathcheck();
       sprintf(&slavetemp[0],"%s/%s",&slavebase[0],&slavework[0]);
       fd = open(&slavetemp[0],O_RDONLY,0);
       if (fd < 0)
	{ printf("%d",errno);
	  putchar('\0');
	  return;
	}
       sgetnts();
       if (slavework[0] == 'a')
	{ close(fd);
	  return;
	}
       sscanf(&slavework[0],"%llu",&size);
       if (flag_debug) fprintf(stderr,"slave_y m %s %llu\n",&slavetemp[0],size);
       fstat(fd,&stb);
       blks = (size + RSYNC_BLKSIZE - 1) / RSYNC_BLKSIZE;
       sumv = malloc(blks*sizeof(SUMS));
       for (len=0;len<blks;len++)
	{ unsigned char crcbuf[4];
	  if ( (fread(&crcbuf[0],1,4,stdin) == 4) &&
	       (fread(&sumv[len].md5[0],1,16,stdin) == 16) )
	   { sumv[len].crc = (crcbuf[0] * 0x01000000) | (crcbuf[1] * 0x00010000) | (crcbuf[2] * 0x00000100) | crcbuf[3];
	     sumv[len].blk = len;
	   }
	  else
	   { fprintf(stderr,"%s: unexpected EOF (in slave_y sum vector read)\n",__progname);
	     exit(1);
	   }
	}
       sort_sums();
       for (at=0;at<blks;at++)
	{ fprintf(stderr,"[%lld] hash %5d crc %08x md5 %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\n",
		at,
		sumv[at].crc % RSYNC_HTSIZE,
		sumv[at].crc,
		sumv[at].md5[ 0], sumv[at].md5[ 1], sumv[at].md5[ 2], sumv[at].md5[ 3],
		sumv[at].md5[ 4], sumv[at].md5[ 5], sumv[at].md5[ 6], sumv[at].md5[ 7],
		sumv[at].md5[ 8], sumv[at].md5[ 9], sumv[at].md5[10], sumv[at].md5[11],
		sumv[at].md5[12], sumv[at].md5[13], sumv[at].md5[14], sumv[at].md5[15] );
	}
       for (i=RSYNC_HTSIZE-1;i>=0;i--) ht[i] = (HTENT){.n=0,.base=0};
       at0 = 0;
       for (at=1;at<blks;at++)
	{ if ((sumv[at].crc % RSYNC_HTSIZE) != (sumv[at0].crc % RSYNC_HTSIZE))
	   { ht[sumv[at0].crc%RSYNC_HTSIZE] = (HTENT){.n=at-at0,.base=at0};
	     at0 = at;
	   }
	}
       ht[sumv[at0].crc%RSYNC_HTSIZE] = (HTENT){.n=blks-at0,.base=at0};
       ifill = 0;
       iptr = 0;
       sumfill = 0;
       sumptr = 0;
       crc = 0;
       genlen = 0;
       for (at=0;at<stb.st_size;at++)
	{ if (iptr >= ifill)
	   { ifill = read(fd,&buf[0],sizeof(buf));
	     if (ifill < 0)
	      { fprintf(stderr,"%s: read error on %s: %s\n",__progname,&slavetemp[0],strerror(errno));
		exit(1);
	      }
	     if (ifill == 0)
	      { bzero(&buf[0],sizeof(buf));
		ifill = sizeof(buf);
	      }
	     iptr = 0;
	   }
	  if (sumfill < RSYNC_BLKSIZE)
	   { crc = (crc >> 8) ^ crctable1[(crc&0xff)^buf[iptr]];
	     sumbuf[sumfill++] = buf[iptr];
	     iptr ++;
	     if (sumfill < RSYNC_BLKSIZE) continue;
	   }
	  else
	   { sumdrop = sumbuf[sumptr];
	     crc ^= crctable2[sumdrop];
	     crc = (crc >> 8) ^ crctable1[(crc&0xff)^buf[iptr]];
	     sumbuf[sumptr++] = buf[iptr];
	     iptr ++;
	     if (sumptr >= RSYNC_BLKSIZE) sumptr = 0;
	   }
	  fprintf(stderr,"[%lld] hash %5d crc %08x\n",at,crc%RSYNC_HTSIZE,crc);
	  e = &ht[crc%RSYNC_HTSIZE];
	  if (e->n > 0)
	   { long long int l;
	     long long int m;
	     long long int h;
	     fprintf(stderr,"hash table hit\n");
	     l = e->base - 1;
	     h = e->base + e->n;
	     while (h-l > 1)
	      { m = (h + l) >> 1;
		if (crc <= sumv[m].crc) h = m;
		if (crc >= sumv[m].crc) l = m;
	      }
	     if (h == l)
	      { unsigned char md5buf[16];
		fprintf(stderr,"CRC hit\n");
		md5 = md5_init();
		md5_process_bytes(md5,&sumbuf[sumptr],RSYNC_BLKSIZE-sumptr);
		if (sumptr) md5_process_bytes(md5,&sumbuf[0],sumptr);
		md5_result(md5,&md5buf[0]);
		fprintf(stderr,"MD5 sum %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\n",
			md5buf[ 0], md5buf[ 1], md5buf[ 2], md5buf[ 3],
			md5buf[ 4], md5buf[ 5], md5buf[ 6], md5buf[ 7],
			md5buf[ 8], md5buf[ 9], md5buf[10], md5buf[11],
			md5buf[12], md5buf[13], md5buf[14], md5buf[15] );
		while ((l >= 0) && (sumv[l].crc == crc)) l --;
		while ((h < blks) && (sumv[h].crc == crc)) h ++;
		while (h-l > 1)
		 { int c;
		   m = (h + l) >> 1;
		   c = memcmp(&md5buf[0],&sumv[m].md5[0],16);
		   if (c <= 0) h = m;
		   if (c >= 0) l = m;
		 }
		if (h == l)
		 { fprintf(stderr,"hit for block %u\n",sumv[m].blk);
		   gen_block(sumv[m].blk);
		   sumfill = 0;
		   sumptr = 0;
		   continue;
		 }
	      }
	   }
	  else
	   { fprintf(stderr,"hash table miss\n");
	   }
	  gen_byte(sumdrop);
	}
       if (sumfill > 0)
	{ if (sumfill < RSYNC_BLKSIZE)
	   { for (i=0;i<sumfill;i++) gen_byte(sumbuf[i]);
	   }
	  else
	   { for (i=0;i<RSYNC_BLKSIZE;i++)
	      { gen_byte(sumbuf[sumptr++]);
		if (sumptr >= RSYNC_BLKSIZE) sumptr = 0;
	      }
	   }
	}
       gen_flush();
       break;
  }
#undef L
#undef R
#undef U
}

static void slave_z(void)
{
 if (! flag_gzip)
  { fprintf(stderr,"%s: protocol error, slave_z but no -gzip\n",__progname);
    exit(1);
  }
 switch (getchar())
  { case 'i':
	{ int mode;
	  int uid;
	  int gid;
	  int fd;
	  int n;
	  int r;
	  int left;
	  int err;
	  int rechmod;
	  unsigned long long int from;
	  unsigned long long int to;
	  sgetnts();
	  slavepathcheck();
	  sprintf(&slavetemp[0],"%s/%s",&slavebase[0],&slavework[0]);
	  sgetnts();
	  sscanf(&slavework[0],"%d%d%d%llu%llu",&mode,&uid,&gid,&from,&to);
	  if (flag_debug) fprintf(stderr,"slave_z i %s %o %d %d %llu %llu\n",&slavetemp[0],mode&07777,uid,gid,from,to);
	  if (flag_readonly)
	   { printf("%d",EROFS);
	     putchar('\0');
	     return;
	   }
	  fd = open_to_update(&slavetemp[0],mode,&rechmod);
	  if (fd < 0)
	   { printf("%d",errno);
	     putchar('\0');
	     return;
	   }
	  if ( ( !flag_noown &&
		 ( (fchown(fd,-1,gid) < 0) ||
		   (fchown(fd,uid,-1) < 0) ) ) ||
	       (lseek(fd,from,SEEK_SET) < 0) )
	   { printf("%d",errno);
	     putchar('\0');
	     if (rechmod >= 0) fchmod(fd,rechmod);
	     close(fd);
	     return;
	   }
	  putchar('0');
	  putchar('\0');
	  fflush(stdout);
	  if (! flag_nosparse) fork_filter(-1,fd,0,sparsewrite);
	  fork_filter(-1,fd,"gunzip",(char *)0);
	  err = 0;
	  while (1)
	   { sgetnts();
	     sscanf(&slavework[0],"%d",&left);
	     if (left < 1) break;
	     while (left > 0)
	      { n = (left < WORKSIZE) ? left : WORKSIZE;
		r = fread(&slavework[0],1,n,stdin);
		if (r != n) exit(0); /* what else? */
		left -= r;
		if (err == 0)
		 { n = write(fd,&slavework[0],r);
		   if ((n >= 0) && (n < r))
		    { err = EIO;
		    }
		   else if (n < 0)
		    { err = errno;
		    }
		 }
	      }
	   }
	  if (rechmod >= 0) fchmod(fd,rechmod);
	  close(fd);
	  printf("%d",err);
	  putchar('\0');
	}
       break;
    case 'r':
	{ int fd;
	  int n;
	  sgetnts();
	  slavepathcheck();
	  sprintf(&slavetemp[0],"%s/%s",&slavebase[0],&slavework[0]);
	  if (flag_debug) fprintf(stderr,"slave_z r %s\n",&slavetemp[0]);
	  fd = open(&slavetemp[0],O_RDONLY,0);
	  if (fd < 0)
	   { n = errno;
	     printf("-1");
	     putchar('\0');
	     printf("%d",errno);
	     putchar('\0');
	   }
	  else
	   { putchar('0');
	     putchar('\0');
	     fork_filter(fd,-1,"gzip",gzip_arg,(char *)0);
	     while (1)
	      { n = read(fd,&slavework[0],WORKSIZE);
		if (n < 0)
		 { n = errno;
		   printf("-1");
		   putchar('\0');
		   printf("%d",n);
		   putchar('\0');
		   break;
		 }
		printf("%d",n);
		putchar('\0');
		if (n < 1) break;
		fwrite(&slavework[0],1,n,stdout);
	      }
	     close(fd);
	   }
	}
       break;
  }
}

#if 0

static void slave_S(void)
{
THIS FUNCTION NOT UDPATED FOR 64-BIT FILES
 int fd;
 int cmd;
 int nb;
 int nr;
 long int off;
 long int curoff;

 sgetnts();
 slavepathcheck();
 sprintf(&slavetemp[0],"%s/%s",&slavebase[0],&slavework[0]);
 fd = open(&slavetemp[0],O_RDONLY,0);
 if (flag_debug) fprintf(stderr,"slave_S entering command loop\n");
 curoff = 0;
 while (1)
  { fflush(stdout);
    cmd = getchar();
    switch (cmd)
     { default:
	  fprintf(stderr,"%s: protocol error, unknown slave S subcommand %d (%c)\n",__progname,cmd,cmd);
	  exit(1);
	  break;
       case EOF:
	  fprintf(stderr,"%s: protocol error, unexpected EOF (slave_S)\n",__progname);
	  exit(1);
	  break;
       case 'c':
	  goto out; /* aka break 2 */
	  break;
       case 'r':
	  sgetnts();
	  sscanf(&slavework[0],"%d%ld",&nb,&off);
	  if (nb > WORKSIZE)
	   { fprintf(stderr,"%s: protocol error, nb too big (%d > %d) in slave R r\n",__progname,nb,WORKSIZE);
	     exit(1);
	   }
	  if (off != curoff) lseek(fd,curoff,L_SET);
	  curoff = off;
	  nr = read(fd,&slavetemp[0],nb);
	  if (nr <= 0)
	   { printf("%d",COPYBLK_HOLE);
	     putchar('\0');
	   }
	  else
	   { if (nr < nb) bzero(&slavetemp[nr],nb-nr);
	     if (bcmp(&slavetemp[0],&zeroblk[0],nb))
	      { printf("%d",COPYBLK_DATA);
		putchar('\0');
		fwrite(&slavetemp[0],1,nb,stdout);
	      }
	     else
	      { printf("%d",COPYBLK_HOLE);
		putchar('\0');
	      }
	   }
	  curoff += (nr >= 0) ? nr : 0;
	  break;
     }
  }
out:;
 if (fd >= 0) close(fd);
}

static void slave_c(void)
{
THIS FUNCTION NOT UDPATED FOR 64-BIT FILES
 int mode;
 int uid;
 int gid;
 int size;
 int fd;
 int left;
 int blkleft;
 int blkoff;
 int flg;
 int n;
 int r;
 int tempfill;
 int skip;
 int w0;
 int writearg;
 int writerv;
 int finalerr;

 sgetnts();
 slavepathcheck();
 sprintf(&slavetemp[0],"%s/%s",&slavebase[0],&slavework[0]);
 sgetnts();
 sscanf(&slavework[0],"%d%d%d%d",&mode,&uid,&gid,&size);
 if (flag_debug) fprintf(stderr,"slave_c %s %o %d %d %d\n",&slavetemp[0],mode&07777,uid,gid,size);
 if (flag_readonly)
  { printf("%d",EROFS);
    putchar('\0');
    return;
  }
 fd = open(&slavetemp[0],O_WRONLY|O_CREAT|O_TRUNC,mode&07777);
 if (fd < 0)
  { printf("%d",errno);
    putchar('\0');
    return;
  }
 if (!flag_noown && ((fchown(fd,-1,gid) < 0) || (fchown(fd,uid,-1) < 0)))
  { printf("%d",errno);
    putchar('\0');
    close(fd);
    return;
  }
 putchar('0');
 putchar('\0');
 fflush(stdout);
 blkleft = 0;
 blkoff = 0;
 tempfill = 0;
 left = size;
 skip = 0;
 finalerr = 0;
#define WRITE(buf,nb) do { if ((writearg=(nb)) && (writerv=write(fd,(buf),writearg)) != writearg) goto writeerr; } while (0)
 while ((left > 0) || (blkleft > 0))
  { if (0)
     {
writeerr:;
       finalerr = (writerv < 0) ? errno : EIO;
       close(fd);
       fd = -1;
     }
    if (0)
     {
fderr:;
       finalerr = errno;
       close(fd);
       fd = -1;
     }
    if (blkleft < 1)
     { sgetnts();
       if (sscanf(&slavework[0],"%d%d",&n,&flg) != 2)
	{ fprintf(stderr,"%s: protocol error (can't read block size and type)\n",__progname);
	  exit(1);
	}
       blkleft += n;
       blkoff = 0;
       left -= n;
     }
    switch (flg)
     { case COPYBLK_DATA:
	  n = WORKSIZE - tempfill;
	  if (blkleft < n) n = blkleft;
	  r = fread(&slavework[tempfill],1,n,stdin);
	  if (r != n)
	   { fprintf(stderr,"%s: protocol error (can't read block data)\n",__progname);
	     exit(1);
	   }
	  blkleft -= n;
	  blkoff += n;
	  if (fd < 0) continue;
	  n += tempfill;
	  r = 0;
	  w0 = -1;
	  while (n-r >= 512)
	   { if (flag_nosparse || bcmp(&slavework[r],&zeroblk[0],512))
	      { if (w0 < 0) w0 = r;
		if (skip > 0)
		 { if (lseek(fd,skip,L_INCR) < 0) goto fderr;
		   skip = 0;
		 }
	      }
	     else
	      { if (w0 >= 0)
		 { WRITE(&slavework[w0],r-w0);
		   w0 = -1;
		 }
		skip += 512;
	      }
	     r += 512;
	   }
	  if (w0 >= 0) WRITE(&slavework[w0],r-w0);
	  tempfill = n - r;
	  if (tempfill && (r > 0)) bcopy(&slavework[r],&slavework[0],tempfill);
	  break;
       case COPYBLK_HOLE:
	  n = blkleft;
	  blkleft = 0;
	  blkoff += n;
	  if (fd < 0) continue;
	  if (tempfill+n < 512)
	   { bzero(&slavework[tempfill],n);
	     tempfill += n;
	   }
	  else
	   { if (flag_nosparse)
	      { if (tempfill > 0)
		 { bzero(&slavework[tempfill],512-tempfill);
		   WRITE(&slavework[0],512);
		   n -= 512 - tempfill;
		 }
		while (n >= WORKSIZE)
		 { WRITE(&zeroblk[0],WORKSIZE);
		   n -= WORKSIZE;
		 }
		if (n >= 512)
		 { WRITE(&zeroblk[0],n&~511);
		   n &= 511;
		 }
		if (n > 0) bzero(&slavework[0],n);
		tempfill = n;
	      }
	     else
	      { if (tempfill)
		 { if (bcmp(&slavework[0],&zeroblk[0],tempfill))
		    { bzero(&slavework[tempfill],512-tempfill);
		      if (skip > 0)
		       { if (lseek(fd,skip,L_INCR) < 0) goto fderr;
			 skip = 0;
		       }
		      WRITE(&slavework[0],512);
		      n -= 512 - tempfill;
		    }
		   else
		    { n += tempfill;
		    }
		 }
		skip += n;
		tempfill = skip % 512;
		if (tempfill)
		 { skip -= tempfill;
		   bzero(&slavework[0],tempfill);
		 }
	      }
	   }
	  break;
     }
  }
 if (tempfill > 0)
  { if (skip > 0)
     { if (lseek(fd,skip,L_INCR) < 0)
	{ finalerr = errno;
	  close(fd);
	  fd = -1;
	}
       skip = 0;
     }
    if (fd >= 0) WRITE(&slavework[0],tempfill);
  }
 if (fd >= 0)
  { setsize(fd,size);
    close(fd);
    putchar('0');
  }
 else
  { printf("%d",finalerr);
  }
#undef WRITE
 putchar('\0');
}

#endif

static void slavealarm(int sig __attribute__((__unused__)))
{
 exit(1);
}

static void slave(void)
{
 int cmd;

/*
 if (flag_trace) tracefilter(0,1,"slave");
 if (flag_encode) xfilter(0,1,"slave");
*/
 printf("V%d",CURRENT_VERSION);
 putchar('\0');
 fflush(stdout);
 sgetnts();
 if (! slavework[0]) strcpy(&slavework[0],".");
 if (flag_limitslave)
  { if (slavework[0] == '/')
     { fprintf(stderr,"%s: rooted path %s from master\n",__progname,&slavework[0]);
       exit(1);
     }
    slavepathcheck();
  }
 strcpy(&slavebase[0],force_dir?:&slavework[0]);
 signal(SIGALRM,slavealarm);
 umask(0);
 while (1)
  { fflush(stdout);
    alarm(60*60*12); /* twelve hours of idle time -> die, master probably crashed */
    cmd = getchar();
    switch (cmd)
     { default:
	  fprintf(stderr,"%s: protocol error, unknown slave command %d (%c)\n",__progname,cmd,cmd);
	  exit(1);
	  break;
       case EOF:
	  exit(0);
	  break;
       case 'D': slave_D(); break;
       case 'I': slave_I(); break;
       case 'K': slave_K(); break;
       case 'L': slave_L(); break;
       case 'R': slave_R(); break;
       case 'd': slave_d(); break;
       case 'f': slave_f(); break;
       case 'i': slave_i(); break;
       case 'l': slave_l(); break;
       case 'm': slave_m(); break;
       case 'n': slave_n(); break;
       case 'o': slave_o(); break;
       case 'r': slave_r(); break;
       case 't': slave_t(); break;
       case 'u': slave_u(); break;
       case 'y': slave_y(); break;
       case 'z': slave_z(); break;
/*
       case 'S': slave_S(); break;
       case 'c': slave_c(); break;
*/
     }
  }
}

#ifndef NO_NETWORK
static void set_nbio(int fd, int enb)
{
 ioctl(fd,FIONBIO,&enb);
}
#endif

#ifndef NO_NETWORK
static void ac_preselect(ACINFO *aci, fd_set *r, fd_set *w)
{
 switch (aci->type)
  { default:
       abort();
       break;
    case ACT_ACCEPT:
	{ int i;
	  for (i=aci->u.acc.nfds-1;i>=0;i--) FD_SET(aci->u.acc.fds[i],r);
	}
       break;
    case ACT_CONNECT:
       FD_SET(aci->u.conn.fd,w);
       break;
  }
}
#endif

#ifndef NO_NETWORK
static void set_tcp_nodelay(int fd)
{
 int on;

 on = 1;
 setsockopt(fd,IPPROTO_TCP,TCP_NODELAY,&on,sizeof(on));
}
#endif

#ifndef NO_NETWORK
static int try_next_connect(ACINFO *aci, int verbose)
{
 int fd;
 struct addrinfo *ai;
 char *txt;
 char hn[NI_MAXHOST];
 char pn[NI_MAXSERV];

 while (1)
  { ai = aci->u.conn.ai;
    if (ai) ai = ai->ai_next; else ai = aci->u.conn.ai0;
    aci->u.conn.ai = ai;
    if (! ai)
     { *aci->fdloc = -1;
       return(1);
     }
    fd = socket(ai->ai_family,ai->ai_socktype,ai->ai_protocol);
    if (fd < 0)
     { if (errno != EPROTONOSUPPORT)
	{ fprintf(stderr,"%s: socket (af %d): %s\n",__progname,ai->ai_family,strerror(errno));
	}
       continue;
     }
    if (getnameinfo(ai->ai_addr,ai->ai_addrlen,&hn[0],NI_MAXHOST,&pn[0],NI_MAXSERV,NI_NUMERICHOST|NI_NUMERICSERV))
     { asprintf(&txt,"%s/%s, can't get text string [%s] for af %d",aci->u.conn.astr,aci->u.conn.pstr,strerror(errno),ai->ai_family);
     }
    else
     { asprintf(&txt,"%s/%s",&hn[0],&pn[0]);
     }
    free(aci->u.conn.txt);
    aci->u.conn.txt = txt;
    set_nbio(fd,1);
    if (verbose) fprintf(stderr,"%s: trying %s...\n",__progname,txt);
    if (connect(fd,ai->ai_addr,ai->ai_addrlen) >= 0)
     { set_nbio(fd,0);
       set_tcp_nodelay(fd);
       *aci->fdloc = fd;
       return(1);
     }
    if (errno == EINPROGRESS)
     { aci->u.conn.fd = fd;
       return(0);
     }
    fprintf(stderr,"%s: connect (to %s): %s\n",__progname,txt,strerror(errno));
    close(fd);
    verbose = 1;
  }
}
#endif

#ifndef NO_NETWORK
static int ac_postselect(ACINFO *aci, fd_set *r, fd_set *w)
{
 switch (aci->type)
  { default:
       abort();
       break;
    case ACT_ACCEPT:
	{ int i;
	  for (i=aci->u.acc.nfds-1;i>=0;i--)
	   { if (FD_ISSET(aci->u.acc.fds[i],r))
	      { int s;
		struct sockaddr_storage ss;
		int sssize;
		sssize = sizeof(ss);
		s = accept(aci->u.acc.fds[i],(struct sockaddr *)&ss,&sssize);
		if (s < 0)
		 { fprintf(stderr,"%s: accept (for %s): %s\n",__progname,aci->u.acc.txt[i],strerror(errno));
		   *aci->fdloc = -1;
		   return(1);
		 }
		set_tcp_nodelay(s);
		*aci->fdloc = s;
		return(1);
	      }
	   }
	}
       break;
    case ACT_CONNECT:
       if (FD_ISSET(aci->u.conn.fd,w))
	{ int e;
	  socklen_t len;
	  len = sizeof(e);
	  if (getsockopt(aci->u.conn.fd,SOL_SOCKET,SO_ERROR,&e,&len) < 0)
	   { fprintf(stderr,"%s: getsockopt SO_ERROR (for %s): %s\n",__progname,aci->u.conn.txt,strerror(errno));
	     *aci->fdloc = -1;
	     return(1);
	   }
	  if (e == 0)
	   { set_nbio(aci->u.conn.fd,0);
	     set_tcp_nodelay(aci->u.conn.fd);
	     *aci->fdloc = aci->u.conn.fd;
	     return(1);
	   }
	  fprintf(stderr,"%s: connect (to %s): %s\n",__progname,aci->u.conn.txt,strerror(e));
	  close(aci->u.conn.fd);
	  if (try_next_connect(aci,1)) return(1);
	}
       break;
  }
 return(0);
}
#endif

#ifndef NO_NETWORK
static void ac_free(ACINFO *aci)
{
 switch (aci->type)
  { default:
       abort();
       break;
    case ACT_ACCEPT:
	{ int i;
	  for (i=aci->u.acc.nfds-1;i>=0;i--)
	   { close(aci->u.acc.fds[i]);
	     free(aci->u.acc.txt[i]);
	   }
	  free(aci->u.acc.fds);
	  free(aci->u.acc.txt);
	}
       break;
    case ACT_CONNECT:
       free(aci->u.conn.txt);
       freeaddrinfo(aci->u.conn.ai0);
       break;
  }
 free(aci);
}
#endif

#ifndef NO_NETWORK
static void connect_trailer(REMOTE *r)
{
 if (r->rfd < 0)
  { r->errored = 1;
  }
 else
  { r->wfd = dup(r->rfd);
  }
}
#endif

static void await_netconns(void)
{
#ifndef NO_NETWORK
 REMOTE *r;
 fd_set rfds;
 fd_set wfds;
 int n;

 while (1)
  { FD_ZERO(&rfds);
    FD_ZERO(&wfds);
    n = 0;
    for (EACH_REM(r))
     { if (r->acinfo)
	{ ac_preselect(r->acinfo,&rfds,&wfds);
	  n ++;
	}
     }
    if (n == 0) break;
    if (select(FD_SETSIZE,&rfds,&wfds,0,0) < 0)
     { if (errno == EINTR) continue;
       fprintf(stderr,"%s: select: %s\n",__progname,strerror(errno));
       exit(1);
     }
    for (EACH_REM(r))
     { if (r->acinfo && ac_postselect(r->acinfo,&rfds,&wfds))
	{ ac_free(r->acinfo);
	  r->acinfo = 0;
	  connect_trailer(r);
	}
     }
  }
#endif
}

static void maybe_background(void)
{
 if (flag_bg)
  { int p;
    p = fork();
    if (p < 0)
     { fprintf(stderr,"%s: fork: %s\n",__progname,strerror(errno));
       exit(1);
     }
    if (p != 0) exit(0);
  }
 flag_bg = 0;
}

#ifndef NO_NETWORK
static void aci_setup_accept(ACINFO *aci, const char *portstr, int *fdloc)
{
 struct addrinfo hints;
 struct addrinfo *ai;
 struct addrinfo *ai0;
 int n;
 int *fds;
 char **txt;
 int fd;
 int e;
 char pn[NI_MAXSERV];

 aci->type = ACT_ACCEPT;
 aci->fdloc = fdloc;
 hints.ai_flags = AI_PASSIVE;
 hints.ai_family = PF_UNSPEC;
 hints.ai_socktype = SOCK_STREAM;
 hints.ai_protocol = 0;
 hints.ai_addrlen = 0;
 hints.ai_canonname = 0;
 hints.ai_addr = 0;
 hints.ai_next = 0;
 e = getaddrinfo(0,portstr,&hints,&ai0);
 if (e)
  { fprintf(stderr,"%s: %s: %s\n",__progname,portstr,gai_strerror(e));
    exit(1);
  }
 if (! ai0)
  { fprintf(stderr,"%s: %s: successful lookup but no addresses?\n",__progname,portstr);
    exit(1);
  }
 n = 0;
 for (ai=ai0;ai;ai=ai->ai_next) n ++;
 fds = malloc(n*sizeof(*fds));
 txt = malloc(n*sizeof(*txt));
 n = 0;
 for (ai=ai0;ai;ai=ai->ai_next)
  { fd = socket(ai->ai_family,ai->ai_socktype,ai->ai_protocol);
    if (fd < 0)
     { if (errno != EPROTONOSUPPORT)
	{ fprintf(stderr,"%s: socket (af %d): %s\n",__progname,ai->ai_family,strerror(errno));
	}
       continue;
     }
    if (bind(fd,ai->ai_addr,ai->ai_addrlen) < 0)
     { e = errno;
       if (getnameinfo(ai->ai_addr,ai->ai_addrlen,0,0,&pn[0],NI_MAXSERV,NI_NUMERICSERV))
	{ fprintf(stderr,"%s: %s: af %d bind failed [%s], can't get text string [%s]\n",__progname,portstr,ai->ai_family,strerror(e),strerror(errno));
	}
       else
	{ fprintf(stderr,"%s: %s: af %d bind to %s failed: %s\n",__progname,portstr,ai->ai_family,&pn[0],strerror(e));
	}
       close(fd);
       continue;
     }
    listen(fd,1);
    fds[n] = fd;
    if (getnameinfo(ai->ai_addr,ai->ai_addrlen,0,0,&pn[0],NI_MAXSERV,NI_NUMERICSERV))
     { asprintf(&txt[n],"%s (af %d, can't get text string [%s])",portstr,ai->ai_family,strerror(errno));
     }
    else
     { asprintf(&txt[n],"%s (af %d)",&pn[0],ai->ai_family);
     }
    n ++;
  }
 aci->u.acc.nfds = n;
 aci->u.acc.fds = fds;
 aci->u.acc.txt = txt;
}
#endif

#ifndef NO_NETWORK
static void setup_accept_spec(REMOTE *r, char *s)
{
 char *s0;
 char *portstr;
 char *pathstr;
 char *sp;
 ACINFO *aci;

 s0 = s;
 s = strdup(s);
 portstr = s;
 sp = index(s,':');
 if (sp == 0)
  { fprintf(stderr,"%s: %s: invalid :accept: spec (no third :)\n",__progname,s0);
    exit(1);
  }
 *sp++ = '\0';
 pathstr = sp;
 aci = malloc(sizeof(ACINFO));
 aci_setup_accept(aci,portstr,&r->rfd);
 r->acinfo = aci;
 r->directory = pathstr;
}
#endif

#ifndef NO_NETWORK
static void remote_ac_wait(ACINFO *aci)
{
 fd_set rfds;
 fd_set wfds;

 while (1)
  { FD_ZERO(&rfds);
    FD_ZERO(&wfds);
    ac_preselect(aci,&rfds,&wfds);
    if (select(FD_SETSIZE,&rfds,&wfds,0,0) < 0)
     { if (errno == EINTR) continue;
       fprintf(stderr,"%s: select: %s\n",__progname,strerror(errno));
       exit(1);
     }
    if (ac_postselect(aci,&rfds,&wfds)) return;
  }
}
#endif

#ifndef NO_NETWORK
static int aci_setup_connect(ACINFO *aci, const char *addrstr, const char *portstr, int *fdloc)
{
 int e;
 struct addrinfo hints;
 struct addrinfo *ai0;

 aci->type = ACT_CONNECT;
 aci->fdloc = fdloc;
 hints.ai_flags = 0;
 hints.ai_family = PF_UNSPEC;
 hints.ai_socktype = SOCK_STREAM;
 hints.ai_protocol = 0;
 hints.ai_addrlen = 0;
 hints.ai_canonname = 0;
 hints.ai_addr = 0;
 hints.ai_next = 0;
 e = getaddrinfo(addrstr,portstr,&hints,&ai0);
 if (e)
  { fprintf(stderr,"%s: %s/%s: %s\n",__progname,addrstr,portstr,gai_strerror(e));
    exit(1);
  }
 if (! ai0)
  { fprintf(stderr,"%s: %s/%s: successful lookup but no addresses?\n",__progname,addrstr,portstr);
    exit(1);
  }
 aci->u.conn.fd = -1;
 aci->u.conn.ai = 0;
 aci->u.conn.txt = 0;
 aci->u.conn.ai0 = ai0;
 aci->u.conn.astr = addrstr;
 aci->u.conn.pstr = portstr;
 return(try_next_connect(aci,0));
}
#endif

#ifndef NO_NETWORK
static void setup_connect_spec(REMOTE *r, char *s)
{
 char *s0;
 char *addrstr;
 char *portstr;
 char *pathstr;
 char *sp;
 ACINFO *aci;

 s0 = s;
 s = strdup(s);
 addrstr = s;
 if (*addrstr == '[')
  { addrstr ++;
    sp = index(addrstr,']');
    if (sp == 0)
     { fprintf(stderr,"%s: %s: invalid :connect: spec (unclosed [)\n",__progname,s0);
       exit(1);
     }
    *sp++ = '\0';
    if (*sp != ':') sp = 0;
  }
 else
  { sp = index(s,':');
  }
 if (sp == 0)
  { fprintf(stderr,"%s: %s: invalid :connect: spec (no third :)\n",__progname,s0);
    exit(1);
  }
 *sp++ = '\0';
 portstr = sp;
 sp = index(sp,':');
 if (sp == 0)
  { fprintf(stderr,"%s: %s: invalid :connect: spec (no fourth :)\n",__progname,s0);
    exit(1);
  }
 *sp++ = '\0';
 pathstr = sp;
 aci = malloc(sizeof(ACINFO));
 if (aci_setup_connect(aci,addrstr,portstr,&r->rfd))
  { ac_free(aci);
    r->acinfo = 0;
    connect_trailer(r);
  }
 else
  { r->acinfo = aci;
  }
 r->directory = pathstr;
}
#endif

static void connectremote(REMOTE *r)
{
 int i;
 int p1[2];
 int p2[2];
 const char *newav[64];
 const char **newavp;

#ifndef NO_NETWORK
 r->acinfo = 0;
#endif
 r->errored = 0;
 if (r->machine == (void *)r)
  { char *colon;
    colon = index(r->user+1,':');
    if (! colon)
     { fprintf(stderr,"%s: %s: invalid keyword-style remote spec (no keyword terminator)\n",__progname,r->directory);
       exit(1);
     }
    i = colon - r->user;
#ifndef NO_NETWORK
    if ((i == 7) && !strncmp(r->user,":accept",7))
     { setup_accept_spec(r,colon+1);
     }
    else if ((i == 8) && !strncmp(r->user,":connect",8))
     { setup_connect_spec(r,colon+1);
     }
    else
#endif
     { fprintf(stderr,"%s: %s: invalid keyword-style remote spec (bad keyword %.*s)\n",__progname,r->directory,i-1,r->user+1);
       exit(1);
     }
    return;
  }
 if ((pipe(&p1[0]) < 0) || (pipe(&p2[0]) < 0))
  { fprintf(stderr,"%s: can't create pipe: %s\n",__progname,strerror(errno));
    exit(1);
  }
 switch (fork())
  { case -1:
       fprintf(stderr,"%s: can't fork: %s\n",__progname,strerror(errno));
       exit(1);
       break;
    case 0:
       break;
    default:
       r->wfd = p1[1];
       close(p1[0]);
       r->rfd = p2[0];
       close(p2[1]);
       return;
       break;
  }
 close(p1[1]);
 close(p2[0]);
 if (p1[0] != 0)
  { dup2(p1[0],0);
    close(p1[0]);
  }
 if (p2[1] != 1)
  { dup2(p2[1],1);
    close(p2[1]);
  }
 for (i=getdtablesize()-1;i>2;i--) close(i);
 if (! r->machine)
  { slave();
    exit(0);
  }
 newavp = &newav[0];
 *newavp++ = r->rsh;
 *newavp++ = r->machine;
 if (r->user)
  { *newavp++ = "-l";
    *newavp++ = r->user;
  }
 *newavp++ = r->program ? r->program : defprogram;
 *newavp++ = "-R";
 if (flag_debug) *newavp++ = "-debug";
 if (flag_follow) *newavp++ = "-follow";
 if (flag_sgiddir) *newavp++ = "-sgid-dirs";
 if (flag_noown) *newavp++ = "-no-owners";
 if (flag_mtimes) *newavp++ = "-mtimes";
 if (flag_encode) *newavp++ = "-encode";
#ifndef NO_NETWORK
 if (flag_trace) *newavp++ = "-trace";
#endif
 if (flag_nosparse) *newavp++ = "-no-sparse";
 if (flag_links) *newavp++ = "-links";
 if (flag_gzip)
  { *newavp++ = "-gzip";
    *newavp++ = gzip_arg;
  }
 *newavp++ = 0;
 execvp(r->rsh,(const void *)&newav[0]);
 printf("F%s",strerror(errno));
 putchar('\0');
 exit(0);
}

static void setupremote(REMOTE *r)
{
#ifndef NO_NETWORK
 if (flag_trace) tracefilter(r->rfd,r->wfd,r->fullarg);
#endif
 if (flag_encode) xfilter(r->rfd,r->wfd,r->fullarg);
 r->rf = fdopen(r->rfd,"r");
 r->wf = fdopen(r->wfd,"w");
}

static void runremotes(void)
{
 REMOTE *r;

 signal(SIGCHLD,reaper);
 for (EACH_REM(r)) connectremote(r);
 await_netconns();
 for (EACH_REM(r)) if (r->errored) exit(1);
 for (EACH_REM(r)) setupremote(r);
}

static void setwait(REMOTE *r)
{
 waitrem = r;
}

static void make_progress(void)
{
 int p;

 p = progress;
 if (p < MAX_PROGRESS) progress = p + 1;
 waitrem = 0;
}

static void master_eof(REMOTE *r)
{
 fprintf(stderr,"%s: unexpected eof (or error) reading from %s\n",__progname,r->fullarg);
 exit(1);
}

static void mgetnts(REMOTE *r)
{
 int x;
 int c;

 x = 0;
 while (1)
  { setwait(r);
    c = getc(r->rf);
    make_progress();
    if (c == EOF) master_eof(r);
    r->work[x] = c;
    if (c == 0)
     { r->worklen = x;
       return;
     }
    if (x < WORKSIZE-1) x ++;
  }
}

static void startup_remotes(void)
{
 REMOTE *r;
 int errs;
 int vers;

 errs = 0;
 for (EACH_REM(r))
  { int code;
    setwait(r);
    code = getc(r->rf);
    make_progress();
    if (code == 'V')
     { mgetnts(r);
       if (sscanf(&r->work[0],"%d",&vers) != 1)
	{ fprintf(stderr,"%s: protocol error 1 starting remote for %s\n",__progname,r->fullarg);
	}
       else if (vers != CURRENT_VERSION)
	{ fprintf(stderr,"%s: protocol version mismatch with remote for %s\n",__progname,r->fullarg);
	}
       else
	{ setwait(r);
	  fputs(r->directory,r->wf);
	  putc('\0',r->wf);
	  make_progress();
	  continue;
	}
     }
    errs ++;
    if (code == 'F')
     { mgetnts(r);
       fprintf(stderr,"%s: can't start remote for %s: %s\n",__progname,r->fullarg,&r->work[0]);
     }
    else
     { fprintf(stderr,"%s: protocol error 2 starting remote for %s%s\n",__progname,r->fullarg,r->machine?" (permission problem?)":"");
     }
  }
 if (errs) exit(1);
}

static ENTRY *entsort(ENTRY *l)
{
 ENTRY *a;
 ENTRY *b;
 ENTRY *t;
 ENTRY **lt;

 if (!l || !l->link) return(l);
 a = 0;
 b = 0;
 while (l)
  { t = l;
    l = l->link;
    t->link = a;
    a = b;
    b = t;
  }
 a = entsort(a);
 b = entsort(b);
 lt = &l;
 while (a || b)
  { if (a && (!b || (strcmp(a->name,b->name) < 0)))
     { t = a;
       a = a->link;
     }
    else
     { t = b;
       b = b->link;
     }
    *lt = t;
    lt = &t->link;
  }
 return(l);
}

static int samepath(const char *p1, const char *p2)
{
 if ((p1[0] == '.') && (p1[1] == '/') && p1[2]) p1 += 2;
 if ((p2[0] == '.') && (p2[1] == '/') && p2[2]) p2 += 2;
 return(!strcmp(p1,p2));
}

static LINKENTRY *link_lookup(REMOTE *r, ENTRY *e)
{
 LINKTABLE *lt;
 LINKENTRY *le;

 lt = r->links;
 if (! lt) return(0);
 for (le=lt->links;le;le=le->link)
  { if ((le->dev == e->linkdev) && (le->ino == e->linkino)) return(le);
  }
 return(0);
}

static LINKENTRY *link_add(REMOTE *r, ENTRY *e, const char *dir)
{
 LINKTABLE *lt;
 LINKENTRY *le;

 lt = r->links;
 if (! lt)
  { lt = NEW(LINKTABLE);
    lt->links = 0;
    r->links = lt;
  }
 le = NEW(LINKENTRY);
 le->dev = e->linkdev;
 le->ino = e->linkino;
 asprintf(&le->name,"%s/%s",dir,e->name);
 le->link = lt->links;
 lt->links = le;
 return(le);
}

static void link_dead(LINKENTRY *le)
{
 free(le->name);
 le->name = 0;
}

static void link_resurrect(LINKENTRY *le, ENTRY *e, const char *dir)
{
 asprintf(&le->name,"%s/%s",dir,e->name);
}

static void get_dirlist(REMOTE *r, const char *dir)
{
 ENTRY *e;
 char *wp;
 MAPPING *map;
 MAPPING *mp;
 MAPPING **mpp;

 mpp = &r->mappings;
 map = 0;
 while ((mp = *mpp))
  { if (samepath(dir,mp->dir))
     { *mpp = mp->link;
       mp->link = map;
       map = mp;
     }
    else
     { mpp = &mp->link;
     }
  }
 r->dirlist = 0;
 setwait(r);
 fprintf(r->wf,"D%s",dir);
 putc('\0',r->wf);
 fflush(r->wf);
 make_progress();
 while (1)
  { mgetnts(r);
    if (r->worklen == 0) break;
    e = newentry();
    e->link = r->dirlist;
    r->dirlist = e;
    e->name = 0;
    mpp = &map;
    while ((mp = *mpp))
     { if (!strcmp(mp->from,&r->work[0]))
	{ e->name = strdup(mp->to);
	  *mpp = mp->link;
	  free(mp);
	  break;
	}
       else
	{ mpp = &mp->link;
	}
     }
    if (! e->name) e->name = strdup(&r->work[0]);
    mgetnts(r);
    wp = &r->work[0];
    e->mode = strtol(wp,&wp,0);
    e->uid = strtol(wp,&wp,0);
    e->gid = strtol(wp,&wp,0);
    if (flag_mtimes) e->mtime = strtol(wp,&wp,0);
    if (flag_links)
     { e->nlinks = strtol(wp,&wp,0);
       e->linkdev = strtouq(wp,&wp,0);
       e->linkino = strtouq(wp,&wp,0);
     }
    switch (e->mode & S_IFMT)
     { case S_IFDIR:
	  e->nlinks = 1;
	  break;
       case S_IFSOCK:
	  break;
       case S_IFCHR:
       case S_IFBLK:
	  e->rdev_maj = strtouq(wp,&wp,0);
	  e->rdev_min = strtouq(wp,&wp,0);
	  break;
       case S_IFREG:
	  e->size = strtouq(wp,&wp,0);
	  break;
       case S_IFLNK:
	  mgetnts(r);
	  e->softlink = strdup(&r->work[0]);
	  break;
     }
  }
 r->dirlist = entsort(r->dirlist);
 if (flag_links)
  { for (e=r->dirlist;e;e=e->link)
     { if (e->nlinks > 1)
	{ e->hardlink = link_lookup(r,e);
	  if (e->hardlink)
	   { if (e->hardlink->name)
	      { e->firstlink = 0;
	      }
	     else
	      { link_resurrect(e->hardlink,e,dir);
		e->firstlink = 1;
	      }
	   }
	  else
	   { e->hardlink = link_add(r,e,dir);
	     e->firstlink = 1;
	   }
	}
     }
  }
 while (map)
  { mp = map;
    map = mp->link;
    free(mp);
  }
}

static void installit(MASK, const char *, MASK *); /* forward */

static void rmcpr(MASK m, const char *dir, char *file, MASK *failmask)
{
 MASK *fm;
 REMOTE *r;
 ENTRY *e;
 int err;
 char *addl;

 e = umaster->dirlist;
 if (MASK_TST(m,umaster->bit)) panic("rmcpr mask includes umaster");
 if (strcmp(file,e->name)) panic("rmcpr file name doesn't match entry name");
  { static MASK fail;
    fm = failmask ? failmask : &fail;
  }
 for (EACH_REM(r))
  { if (MASK_TST(m,r->bit))
     { setwait(r);
       fprintf(r->wf,"d%s/%s",dir,file);
       putc('\0',r->wf);
       fprintf(r->wf,"%d %d %d",e->mode,e->uid,e->gid);
       putc('\0',r->wf);
       fflush(r->wf);
       make_progress();
       mgetnts(r);
       sscanf(&r->work[0],"%d",&err);
       if (err)
	{ printf("      mkdir of %s/%s on %s failed: %s\n",dir,file,r->fullarg,strerror(err));
	  MASK_CLR(m,r->bit);
	  MASK_SET(*fm,r->bit);
	}
     }
  }
 if (! MASK_ISZERO(m))
  { addl = malloc(strlen(dir)+1+strlen(file)+1);
    sprintf(addl,"%s/%s",dir,file);
    get_dirlist(umaster,addl);
    if (umaster->dirlist)
     { while (umaster->dirlist)
	{ ENTRY *et;
	  et = umaster->dirlist;
	  et->mode = (et->mode & perm_and) | perm_or;
	  installit(m,addl,fm);
	  umaster->dirlist = et->link;
	  freeentry(et);
	}
     }
    free(addl);
    if (flag_mtimes)
     { for (EACH_REM(r))
	{ if (MASK_TST(m,r->bit))
	   { setwait(r);
	     fprintf(r->wf,"u%s/%s",dir,file);
	     putc('\0',r->wf);
	     fprintf(r->wf,"%ld 0",e->mtime);
	     putc('\0',r->wf);
	     fflush(r->wf);
	     make_progress();
	     mgetnts(r);
	     sscanf(&r->work[0],"%d",&err);
	     if (err)
	      { printf("      utimes of %s/%s on %s failed: %s\n",dir,file,r->fullarg,strerror(err));
		MASK_CLR(m,r->bit);
		MASK_SET(*fm,r->bit);
	      }
	   }
	}
     }
  }
 umaster->dirlist = e;
 if (! failmask)
  { for (EACH_REM(r)) if (MASK_TST(m,r->bit) && !MASK_TST(*fm,r->bit)) printf("      directory copy to %s succeeded\n",r->fullarg);
  }
}

static void rmmknod(MASK m, const char *dir, char *file, MASK *failmask)
{
 REMOTE *r;
 ENTRY *e;

 e = umaster->dirlist;
 if (MASK_TST(m,umaster->bit)) panic("rmmknod mask includes umaster");
 if (strcmp(file,e->name)) panic("rmmknod file name doesn't match entry name");
 for (EACH_REM(r))
  { if (MASK_TST(m,r->bit))
     { int err;
       setwait(r);
       fprintf(r->wf,"n%s/%s",dir,file);
       putc('\0',r->wf);
       fprintf(r->wf,"%d %d %d %llu %llu",e->mode,e->uid,e->gid,(unsigned long long int)e->rdev_maj,(unsigned long long int)e->rdev_min);
       putc('\0',r->wf);
       fflush(r->wf);
       make_progress();
       mgetnts(r);
       sscanf(&r->work[0],"%d",&err);
       if (err)
	{ if (failmask)
	   { MASK_SET(*failmask,r->bit);
	   }
	  else
	   { printf("      mknod on %s failed: %s\n",r->fullarg,strerror(err));
	   }
	}
       else
	{ if (! failmask) printf("      mknod on %s succeeded\n",r->fullarg);
	}
     }
  }
}

static void rmsymlink(MASK m, const char *dir, char *file, MASK *failmask)
{
 REMOTE *r;
 ENTRY *e;

 e = umaster->dirlist;
 if (MASK_TST(m,umaster->bit)) panic("rmsymlink mask includes umaster");
 if (strcmp(file,e->name)) panic("rmsymlink file name doesn't match entry name");
 for (EACH_REM(r))
  { if (MASK_TST(m,r->bit))
     { int err;
       setwait(r);
       fprintf(r->wf,"l%s/%s",dir,file);
       putc('\0',r->wf);
       fprintf(r->wf,"%d %d %d",umaster->dirlist->mode,umaster->dirlist->uid,umaster->dirlist->gid);
       putc('\0',r->wf);
       fprintf(r->wf,"%s",e->softlink);
       putc('\0',r->wf);
       fflush(r->wf);
       make_progress();
       mgetnts(r);
       sscanf(&r->work[0],"%d",&err);
       if (err)
	{ if (failmask)
	   { MASK_SET(*failmask,r->bit);
	   }
	  else
	   { printf("      symlink on %s failed: %s\n",r->fullarg,strerror(err));
	   }
	}
       else
	{ if (! failmask) printf("      symlink on %s succeeded\n",r->fullarg);
	}
     }
  }
}

#ifdef S_IFIFO
static void rmmkfifo(MASK m, const char *dir, char *file, MASK *failmask)
{
 REMOTE *r;
 ENTRY *e;

 e = umaster->dirlist;
 if (MASK_TST(m,umaster->bit)) panic("rmmkfifo mask includes umaster");
 if (strcmp(file,e->name)) panic("rmmkfifo file name doesn't match entry name");
 for (EACH_REM(r))
  { if (MASK_TST(m,r->bit))
     { int err;
       setwait(r);
       fprintf(r->wf,"f%s/%s",dir,file);
       putc('\0',r->wf);
       fprintf(r->wf,"%d %d %d",e->mode,e->uid,e->gid);
       putc('\0',r->wf);
       fflush(r->wf);
       make_progress();
       mgetnts(r);
       sscanf(&r->work[0],"%d",&err);
       if (err)
	{ if (failmask)
	   { MASK_SET(*failmask,r->bit);
	   }
	  else
	   { printf("      mkfifo on %s failed: %s\n",r->fullarg,strerror(err));
	   }
	}
       else
	{ if (! failmask) printf("      mkfifo on %s succeeded\n",r->fullarg);
	}
     }
  }
}
#endif

static void rmutimes(MASK m, const char *dir, char *file, MASK *failmask)
{
 REMOTE *r;
 ENTRY *e;

 e = umaster->dirlist;
 if (MASK_TST(m,umaster->bit)) panic("rmutimes mask includes umaster");
 if (strcmp(file,e->name)) panic("rmutimes file name doesn't match entry name");
 for (EACH_REM(r))
  { if (MASK_TST(m,r->bit))
     { int err;
       setwait(r);
       fprintf(r->wf,"u%s/%s",dir,file);
       putc('\0',r->wf);
       fprintf(r->wf,"%ld 0",e->mtime);
       putc('\0',r->wf);
       fflush(r->wf);
       make_progress();
       mgetnts(r);
       sscanf(&r->work[0],"%d",&err);
       if (err)
	{ if (failmask)
	   { MASK_SET(*failmask,r->bit);
	   }
	  else
	   { printf("      utimes on %s failed: %s\n",r->fullarg,strerror(err));
	   }
	}
       else
	{ if (! failmask) printf("      utimes on %s succeeded\n",r->fullarg);
	}
     }
  }
}

static void copy_n(unsigned long long int n, REMOTE *from, REMOTE *to)
{
 int i;
 int j;
 char dbuf[8192];

 while (n > 0)
  { make_progress();
    i = (n < sizeof(dbuf)) ? n : sizeof(dbuf);
    j = fread(&dbuf[0],1,i,from->rf);
    if (i != j) master_eof(from);
    fwrite(&dbuf[0],1,i,to->wf);
    n -= i;
  }
}

static void rmcp(MASK m, const char *dir, const char *file, MASK *failmask, unsigned long long int fromoff, unsigned long long int tooff)
{
 REMOTE *r;
 ENTRY *e;
 unsigned long long int left;
 int n;
 unsigned long long int off;
 int err;

 static void error(void)
  { if (failmask)
     { MASK_SET(*failmask,r->bit);
     }
    else
     { printf("      copy to %s failed: %s\n",r->fullarg,strerror(err));
     }
  }

 e = umaster->dirlist;
 if (MASK_TST(m,umaster->bit)) panic("rmcp mask includes umaster");
 if (MASK_ISZERO(m)) return;
 if (strcmp(file,e->name)) panic("rmcp file name doesn't match entry name");
 if (fromoff > e->size) panic("rmcp begins after eof");
 if (tooff > e->size) tooff = e->size;
 if (flag_rsync)
  { for (EACH_REM(r))
     { if (MASK_TST(m,r->bit))
	{ unsigned long long int dsize;
	  unsigned long long int nbytes;
	  unsigned long long int at;
	  unsigned long long int loc;
	  int n;
	  int i;
	  make_progress();
	  setwait(umaster);
	  fprintf(umaster->wf,"ym%s/%s",dir,file);
	  putc('\0',umaster->wf);
	  fflush(umaster->wf);
	  mgetnts(umaster);
	  sscanf(&umaster->work[0],"%d",&err);
	  if (err)
	   { error();
	     continue;
	   }
	  make_progress();
	  setwait(r);
	  fprintf(r->wf,"ys%s/%s",dir,file);
	  putc('\0',r->wf);
	  fprintf(r->wf,"%d %d %d %llu",e->mode,e->uid,e->gid,e->size);
	  putc('\0',r->wf);
	  fflush(r->wf);
	  mgetnts(r);
	  sscanf(&r->work[0],"%d",&err);
	  if (err)
	   { error();
	     putc('a',umaster->wf);
	     putc('\0',umaster->wf);
	     continue;
	   }
	  mgetnts(r);
	  sscanf(&r->work[0],"%llu",&dsize);
	  fprintf(umaster->wf,"%llu",dsize);
	  putc('\0',umaster->wf);
	  nbytes = 20 * ((dsize + RSYNC_BLKSIZE - 1) / RSYNC_BLKSIZE);
	  copy_n(nbytes,r,umaster);
	  fflush(umaster->wf);
	  at = 0;
	  while (at < dsize)
	   { i = getc(umaster->rf);
	     switch (i)
	      { default:
		   fprintf(stderr,"%s: bad rsync command %02x from %s\n",__progname,i,umaster->fullarg);
		   exit(1);
		   break;
		case EOF:
		   master_eof(umaster);
		   break;
		case 'b':
		   mgetnts(umaster);
		   sscanf(&umaster->work[0],"%llu %d",&loc,&n);
		   fprintf(r->wf,"b%llu %d",loc,n);
		   putc('\0',r->wf);
		   at += n * RSYNC_BLKSIZE;
		   break;
		case 'd':
		   mgetnts(umaster);
		   sscanf(&umaster->work[0],"%llu",&nbytes);
		   fprintf(r->wf,"d%llu",nbytes);
		   putc('\0',r->wf);
		   copy_n(nbytes,umaster,r);
		   at += nbytes;
		   break;
	      }
	   }
	  fflush(r->wf);
	  mgetnts(r);
	  sscanf(&r->work[0],"%d",&err);
	  if (err)
	   { error();
	     continue;
	   }
	}
     }
  }
 else
  { for (EACH_REM(r))
     { if (MASK_TST(m,r->bit))
	{ setwait(r);
	  if (flag_gzip)
	   { fprintf(r->wf,"zi%s/%s",dir,file);
	     putc('\0',r->wf);
	     fprintf(r->wf,"%d %d %d %llu %llu",e->mode,e->uid,e->gid,fromoff,tooff);
	     putc('\0',r->wf);
	   }
	  else
	   { fprintf(r->wf,"i%s/%s",dir,file);
	     putc('\0',r->wf);
	     fprintf(r->wf,"%d %d %d %llu %llu %llu",e->mode,e->uid,e->gid,e->size,fromoff,tooff);
	     putc('\0',r->wf);
	   }
	  fflush(r->wf);
	  make_progress();
	  mgetnts(r);
	  sscanf(&r->work[0],"%d",&err);
	  if (err != 0)
	   { if (failmask)
	      { MASK_SET(*failmask,r->bit);
	      }
	     else
	      { printf("      copy to %s failed: %s\n",r->fullarg,strerror(err));
	      }
	     MASK_CLR(m,r->bit);
	   }
	}
     }
    if (! MASK_ISZERO(m))
     { if (flag_gzip)
	{ setwait(umaster);
	  fprintf(umaster->wf,"zI%s/%s",dir,file);
	  putc('\0',umaster->wf);
	  fprintf(umaster->wf,"%llu %llu",fromoff,tooff);
	  putc('\0',umaster->wf);
	  fflush(umaster->wf);
	  make_progress();
	  mgetnts(umaster);
	  sscanf(&umaster->work[0],"%d",&n);
	  if (n < 0)
	   { mgetnts(umaster);
	     sscanf(&umaster->work[0],"%d",&err);
	     if (failmask)
	      { mask_setor(failmask,m);
	      }
	     else
	      { printf("      open %s failed: %s\n",umaster->fullarg,strerror(err));
	      }
	   }
	  else
	   { while (1)
	      { mgetnts(umaster);
		sscanf(&umaster->work[0],"%d",&n);
		for (EACH_REM(r)) if (MASK_TST(m,r->bit))
		 { setwait(r);
		   fprintf(r->wf,"%d",n);
		   putc('\0',r->wf);
		   make_progress();
		 }
		if (n < 1) break;
		left = n;
		while (left > 0)
		 { n = (left < WORKSIZE) ? left : WORKSIZE;
		   setwait(umaster);
		   fread(&umaster->work[0],1,n,umaster->rf);
		   make_progress();
		   for (EACH_REM(r)) if (MASK_TST(m,r->bit))
		    { setwait(r);
		      fwrite(&umaster->work[0],1,n,r->wf);
		      make_progress();
		    }
		   left -= n;
		 }
	      }
	     if (n < 0)
	      { mgetnts(umaster);
		sscanf(&umaster->work[0],"%d",&err);
		if (failmask)
		 { mask_setor(failmask,m);
		 }
		else
		 { printf("      reading from %s failed: %s\n",umaster->fullarg,strerror(err));
		 }
	      }
	   }
	}
       else
	{ setwait(umaster);
	  fprintf(umaster->wf,"I%s/%s",dir,file);
	  putc('\0',umaster->wf);
	  fflush(umaster->wf);
	  make_progress();
	  left = tooff - fromoff;
	  off = fromoff;
	  while (left > 0)
	   { int flg;
	     n = (left < WORKSIZE) ? left : WORKSIZE;
	     setwait(umaster);
	     fprintf(umaster->wf,"r%d %llu",n,off);
	     putc('\0',umaster->wf);
	     fflush(umaster->wf);
	     make_progress();
	     mgetnts(umaster);
	     if (sscanf(&umaster->work[0],"%d",&flg) != 1)
	      { fprintf(stderr,"%s: protocol error: update master returned no flag\n",__progname);
	      }
	     switch (flg)
	      { case COPYBLK_DATA:
		   setwait(umaster);
		   fread(&umaster->work[0],1,n,umaster->rf);
		   make_progress();
		   for (EACH_REM(r))
		    { if (MASK_TST(m,r->bit))
		       { setwait(r);
			 fprintf(r->wf,"%d %d",n,COPYBLK_DATA);
			 putc('\0',r->wf);
			 fwrite(&umaster->work[0],1,n,r->wf);
			 make_progress();
		       }
		    }
		   break;
		case COPYBLK_HOLE:
		   for (EACH_REM(r))
		    { if (MASK_TST(m,r->bit))
		       { setwait(r);
			 fprintf(r->wf,"%d %d",n,COPYBLK_HOLE);
			 putc('\0',r->wf);
			 make_progress();
		       }
		    }
		   break;
		default:
		   fprintf(stderr,"%s: update master returned unknown block code %d\n",__progname,flg);
		   exit(1);
		   break;
	      }
	     off += n;
	     left -= n;
	   }
	  setwait(umaster);
	  putc('c',umaster->wf);
	  make_progress();
	}
       for (EACH_REM(r))
	{ if (MASK_TST(m,r->bit))
	   { setwait(r);
	     fflush(r->wf);
	     make_progress();
	     mgetnts(r);
	     sscanf(&r->work[0],"%d",&err);
	     if (err)
	      { if (failmask)
		 { MASK_SET(*failmask,r->bit);
		 }
		else
		 { printf("      copy to %s failed: %s\n",r->fullarg,strerror(err));
		 }
	      }
	     else
	      { if (! failmask) printf("      copy to %s succeeded\n",r->fullarg);
	      }
	   }
	}
     }
  }
}

static void installreg(MASK mask, const char *addl, const char *name, MASK *failmask, unsigned long long int fromoff, unsigned long long int tooff)
{
 rmcp(mask,addl,name,failmask,fromoff,tooff);
}

static void installit(MASK mask, const char *addl, MASK *failmask)
{
 ENTRY *e;

 e = umaster->dirlist;
 switch (e->mode & S_IFMT)
  { default:
       if (failmask)
	{ *failmask = mask;
	}
       else
	{ printf("      install failed: unknown mode %o for %s in %s:%s\n",e->mode,e->name,umaster->fullarg,addl);
	}
       break;
    case S_IFDIR:
       rmcpr(mask,addl,e->name,failmask);
       break;
    case S_IFCHR:
    case S_IFBLK:
       rmmknod(mask,addl,e->name,failmask);
       if (flag_mtimes) rmutimes(mask,addl,e->name,failmask);
       break;
    case S_IFREG:
       installreg(mask,addl,e->name,failmask,0,~0ULL);
       if (flag_mtimes) rmutimes(mask,addl,e->name,failmask);
       break;
    case S_IFLNK:
       rmsymlink(mask,addl,e->name,failmask);
       break;
    case S_IFSOCK:
       if (failmask)
	{ *failmask = mask;
	}
       else
	{ printf("      install %s/%s failed: can't copy sockets\n",addl,e->name);
	}
       break;
#ifdef S_IFIFO
    case S_IFIFO:
       rmmkfifo(mask,addl,e->name,failmask);
       if (flag_mtimes) rmutimes(mask,addl,e->name,failmask);
       break;
#endif
  }
}

static int rmln(REMOTE *r, const char *dir, const char *name, const char *to)
{
 int err;

 setwait(r);
 fprintf(r->wf,"L%s/%s",dir,name);
 putc('\0',r->wf);
 fprintf(r->wf,"%s",to);
 putc('\0',r->wf);
 fflush(r->wf);
 make_progress();
 mgetnts(r);
 sscanf(&r->work[0],"%d",&err);
 return(err);
}

static void install_hardlink(MASK mask, const char *addl, const char *to)
{
 REMOTE *r;

 for (EACH_REM(r))
  { if (MASK_TST(mask,r->bit))
     { printf("      hardlink from %s %s\n",to,rmln(r,addl,umaster->dirlist->name,to)?"failed":"succeeded");
     }
  }
}

static int rmrm(REMOTE *r, const char *dir, const char *name)
{
 int err;

 setwait(r);
 fprintf(r->wf,"r%s/%s",dir,name);
 putc('\0',r->wf);
 fflush(r->wf);
 make_progress();
 mgetnts(r);
 sscanf(&r->work[0],"%d",&err);
 return(err);
}

static void rmtrunc(MASK m, const char *dir, char *file, MASK *failmask)
{
 REMOTE *r;
 ENTRY *e;

 e = umaster->dirlist;
 if (MASK_TST(m,umaster->bit)) panic("rmtrunc mask includes umaster");
 if (strcmp(file,e->name)) panic("rmtrunc file name doesn't match entry name");
 for (EACH_REM(r))
  { if (MASK_TST(m,r->bit))
     { int err;
       setwait(r);
       fprintf(r->wf,"t%s/%s",dir,file);
       putc('\0',r->wf);
       fprintf(r->wf,"%llu",e->size);
       putc('\0',r->wf);
       fflush(r->wf);
       make_progress();
       mgetnts(r);
       sscanf(&r->work[0],"%d",&err);
       if (err)
	{ if (failmask)
	   { MASK_SET(*failmask,r->bit);
	   }
	  else
	   { printf("      truncate on %s failed: %s\n",r->fullarg,strerror(err));
	   }
	}
       else
	{ if (! failmask) printf("      truncate on %s succeeded\n",r->fullarg);
	}
     }
  }
}

static void rmchmod(MASK m, const char *dir, char *file, MASK *failmask)
{
 REMOTE *r;
 ENTRY *e;

 e = umaster->dirlist;
 if (MASK_TST(m,umaster->bit)) panic("rmchmod mask includes umaster");
 if (strcmp(file,e->name)) panic("rmchmod file name doesn't match entry name");
 for (EACH_REM(r))
  { if (MASK_TST(m,r->bit))
     { int err;
       setwait(r);
       fprintf(r->wf,"m%s/%s",dir,file);
       putc('\0',r->wf);
       fprintf(r->wf,"%d",e->mode&07777);
       putc('\0',r->wf);
       fflush(r->wf);
       make_progress();
       mgetnts(r);
       sscanf(&r->work[0],"%d",&err);
       if (err)
	{ if (failmask)
	   { MASK_SET(*failmask,r->bit);
	   }
	  else
	   { printf("      chmod on %s failed: %s\n",r->fullarg,strerror(err));
	   }
	}
       else
	{ if (! failmask) printf("      chmod on %s succeeded\n",r->fullarg);
	}
     }
  }
}

static void rmchown(MASK m, const char *dir, char *file, MASK *failmask)
{
 REMOTE *r;
 ENTRY *e;

 e = umaster->dirlist;
 if (MASK_TST(m,umaster->bit)) panic("rmchown mask includes umaster");
 if (strcmp(file,e->name)) panic("rmchown file name doesn't match entry name");
 for (EACH_REM(r))
  { if (MASK_TST(m,r->bit))
     { int err;
       setwait(r);
       fprintf(r->wf,"o%s/%s",dir,file);
       putc('\0',r->wf);
       fprintf(r->wf,"%d %d",e->uid,e->gid);
       putc('\0',r->wf);
       fflush(r->wf);
       make_progress();
       mgetnts(r);
       sscanf(&r->work[0],"%d",&err);
       if (err)
	{ if (failmask)
	   { MASK_SET(*failmask,r->bit);
	   }
	  else
	   { printf("      chown on %s failed: %s\n",r->fullarg,strerror(err));
	   }
	}
       else
	{ if (! failmask) printf("      chown on %s succeeded\n",r->fullarg);
	}
     }
  }
}

static char *modestring(int mode)
{
 static char modebuf[64];

 switch (mode & S_IFMT)
  { case S_IFDIR:  modebuf[0] = 'd'; break;
    case S_IFCHR:  modebuf[0] = 'c'; break;
    case S_IFBLK:  modebuf[0] = 'b'; break;
    case S_IFREG:  modebuf[0] = '-'; break;
    case S_IFLNK:  modebuf[0] = 'l'; break;
    case S_IFSOCK: modebuf[0] = 's'; break;
#ifdef S_IFIFO
    case S_IFIFO:  modebuf[0] = 'p'; break;
#endif
    default:
       sprintf(&modebuf[0],"?%07o",mode&07777777);
       return(&modebuf[0]);
       break;
  }
 modebuf[1] = (mode & 0400) ? 'r' : '-';
 modebuf[2] = (mode & 0200) ? 'w' : '-';
 switch (mode & (S_ISUID|0100))
  { case 0           : modebuf[3] = '-'; break;
    case S_ISUID     : modebuf[3] = 'S'; break;
    case         0100: modebuf[3] = 'x'; break;
    case S_ISUID|0100: modebuf[3] = 's'; break;
  }
 modebuf[4] = (mode & 040) ? 'r' : '-';
 modebuf[5] = (mode & 020) ? 'w' : '-';
 switch (mode & (S_ISGID|010))
  { case 0          : modebuf[6] = '-'; break;
    case S_ISGID    : modebuf[6] = 'S'; break;
    case         010: modebuf[6] = 'x'; break;
    case S_ISGID|010: modebuf[6] = 's'; break;
  }
 modebuf[7] = (mode & 04) ? 'r' : '-';
 modebuf[8] = (mode & 02) ? 'w' : '-';
 switch (mode & (S_ISVTX|01))
  { case 0         : modebuf[9] = '-'; break;
    case S_ISVTX   : modebuf[9] = 'T'; break;
    case         01: modebuf[9] = 'x'; break;
    case S_ISVTX|01: modebuf[9] = 't'; break;
  }
 modebuf[10] = '\0';
 return(&modebuf[0]);
}

static void get_md5_sums(MASK m, const char *dir, const char *name, unsigned long long int from, unsigned long long int to)
{
 REMOTE *r;

 for (EACH_REM(r))
  { if (MASK_TST(m,r->bit))
     { setwait(r);
       fprintf(r->wf,"K%s/%s",dir,name);
       putc('\0',r->wf);
       fprintf(r->wf,"%llu %llu",from,to);
       putc('\0',r->wf);
       fflush(r->wf);
       make_progress();
     }
  }
 for (EACH_REM(r))
  { if (MASK_TST(m,r->bit))
     { setwait(r);
       fread(&r->work[0],1,1,r->rf);
       make_progress();
       switch (r->work[0])
	{ case 'O': case 'R':
	     mgetnts(r);
	     r->work[0] = 0;
	     break;
	  case 'K':
	     setwait(r);
	     fread(&r->work[1],1,16,r->rf);
	     make_progress();
	     r->work[0] = 1;
	     break;
	}
     }
  }
}

static MASK compare_reg_topmask;
static int compare_reg_firstset;
static unsigned long long int compare_reg_size;
static int compare_reg_psize;

static void showhdg(void)
{
 REMOTE *r;

 if (flag_check)
  { if (check_exit) (*check_exit)();
    exit(1);
  }
 if (needhdg)
  { printf("%s/%s:\n",cur_dir,cur_ent);
    for (EACH_REM(r)) if (MASK_TST(cur_nx,r->bit)) printf("    nonexistent in %s\n",r->fullarg);
    needhdg = 0;
  }
}

static void compare_reg_set(MASK mask)
{
 REMOTE *r;

 if (flag_debug) fprintf(stderr,"compare_reg_set mask=0x%lx\n",mask.bits[0]);
 if (MASK_EQ(mask,compare_reg_topmask)) return;
 showhdg();
 if (compare_reg_firstset)
  { printf("    plain file %s vary",flag_md5?"checksums":"contents");
    if (compare_reg_psize) printf(" for size %llu",compare_reg_size);
    printf(":\n");
  }
 else
  { printf("    --------\n");
  }
 compare_reg_firstset = 0;
 for (EACH_REM(r)) if (MASK_TST(mask,r->bit))
  { printf("\t%s",r->fullarg);
    if (flag_md5 && !r->work[0]) printf(" (open/read failure)");
    printf("\n");
  }
}

static void compare_reg_aux(MASK mask, unsigned long long int off0)
{
 MASK todo;
 MASK these;
 REMOTE *r;
 REMOTE *cmpr;
 unsigned long long int off;
 int n;

 if (MASK_ISZERO(mask)) panic("compare_reg_aux zero mask");
 if (MASK_ONEBIT(mask))
  { compare_reg_set(mask);
    return;
  }
 if (flag_debug)
  { fprintf(stderr,"compare_reg_aux starting off0=%llu mask=0x%lx\n",off0,mask.bits[0]);
    for (EACH_REM(r)) if (MASK_TST(mask,r->bit)) fprintf(stderr,"\t%s\n",r->fullarg);
  }
 off = off0;
 while (1)
  { if (flag_debug) fprintf(stderr,"compare_reg_aux top of loop, off=%llu\n",off);
    n = compare_reg_size - off;
    if (n > WORKSIZE) n = WORKSIZE;
    if (n < 1)
     { compare_reg_set(mask);
       return;
     }
    for (EACH_REM(r))
     { if (MASK_TST(mask,r->bit))
	{ setwait(r);
	  fprintf(r->wf,"r%d %llu",n,off);
	  putc('\0',r->wf);
	  fflush(r->wf);
	  fread(&r->work[0],1,n,r->rf);
	  make_progress();
	}
     }
    off += n;
    if (flag_debug) fprintf(stderr,"compare_reg_aux off advanced to %llu\n",off);
    todo = mask;
    while (1)
     { for (EACH_REM(r)) if (MASK_TST(todo,r->bit)) break;
       cmpr = r;
       if (cmpr == 0)
	{ panic("compare_reg_aux can't find a remote to do");
	}
       MASK_ZERO(these);
       for (EACH_REM(r))
	{ if (MASK_TST(todo,r->bit))
	   { if ((r == cmpr) || !bcmp(&r->work[0],&cmpr->work[0],n))
	      { MASK_SET(these,r->bit);
		MASK_CLR(todo,r->bit);
	      }
	   }
	}
       if (MASK_ISZERO(todo)) break;
       compare_reg_aux(these,off);
     }
    mask = these;
  }
}

static void compare_reg(MASK mask, int size, int printsize)
{
 REMOTE *r;

 compare_reg_topmask = mask;
 compare_reg_size = size;
 compare_reg_psize = printsize;
 compare_reg_firstset = 1;
 if (MASK_ONEBIT(mask)) return;
 if (flag_md5)
  { MASK m;
    MASK left;
    REMOTE *r2;
    get_md5_sums(mask,cur_dir,cur_ent,0,size);
    left = mask;
    while (! MASK_ISZERO(left))
     { for (EACH_REM(r)) if (MASK_TST(left,r->bit)) break;
       MASK_ZERO(m);
       if (r->work[0])
	{ for (EACH_REM(r2)) if (MASK_TST(left,r2->bit) && r2->work[0] && !bcmp(&r->work[1],&r2->work[1],16)) MASK_SET(m,r2->bit);
	}
       else
	{ MASK_SET(m,r->bit);
	}
       compare_reg_set(m);
       mask_setandnot(&left,m);
     }
  }
 else
  { static void closedown(void)
     { REMOTE *r;
       for (EACH_REM(r)) if (MASK_TST(mask,r->bit))
	{ setwait(r);
	  putc('c',r->wf);
	  make_progress();
	}
     }
    for (EACH_REM(r))
     { if (MASK_TST(mask,r->bit))
	{ setwait(r);
	  fprintf(r->wf,"R%s/%s",cur_dir,cur_ent);
	  putc('\0',r->wf);
	  fflush(r->wf);
	  make_progress();
	}
     }
    check_exit = &closedown;
    compare_reg_aux(mask,0);
    closedown();
    check_exit = 0;
  }
}

static void check_reg_diff(REMOTE *mr, const char *dir, MASK *m, unsigned long long int fromsize, unsigned long long int tosize)
{
 int left;
 int n;
 unsigned long long int off;
 REMOTE *r;
 MASK w;
 MASK all;

 if (MASK_TST(*m,mr->bit)) panic("check_reg_diff master in mask");
 if (fromsize >= tosize)
  { if ((fromsize == 0) && (tosize == 0))
     { MASK_ZERO(*m);
       return;
     }
    panic("check_reg_diff fromsize >= tosize");
  }
 if (tosize > mr->dirlist->size) panic("check_reg_diff tosize > master size");
 if (MASK_ISZERO(*m)) return;
 w = *m;
 if (flag_debug) fprintf(stderr,"check_reg_diff master is %s\n",mr->fullarg);
 MASK_ZERO(*m);
 for (EACH_REM(r))
  { if ( MASK_TST(w,r->bit) &&
	 (ullmin(r->dirlist->size,tosize) != ullmin(mr->dirlist->size,tosize)) )
     { if (flag_debug) fprintf(stderr,"check_reg_diff sees %s differs in size\n",r->fullarg);
       MASK_CLR(w,r->bit);
       MASK_SET(*m,r->bit);
     }
  }
 if (flag_md5)
  { MASK_SET(w,mr->bit);
    get_md5_sums(w,dir,mr->dirlist->name,fromsize,tosize);
    MASK_CLR(w,mr->bit);
    for (EACH_REM(r)) if (MASK_TST(w,r->bit) && (!mr->work[0] || !r->work[0] || bcmp(&mr->work[1],&r->work[1],16)))
     { if (flag_debug) fprintf(stderr,"check_reg_diff sees %s differs in checksum\n",r->fullarg);
       MASK_SET(*m,r->bit);
     }
  }
 else
  { static void closedown(void)
     { REMOTE *r;
       for (EACH_REM(r)) if ((r == mr) || MASK_TST(all,r->bit))
	{ setwait(r);
	  putc('c',r->wf);
	  make_progress();
	}
     }
    all = w;
    for (EACH_REM(r))
     { if ((r == mr) || MASK_TST(all,r->bit))
	{ setwait(r);
	  fprintf(r->wf,"R%s/%s",dir,mr->dirlist->name);
	  putc('\0',r->wf);
	  fflush(r->wf);
	  make_progress();
	}
     }
    check_exit = &closedown;
    left = tosize - fromsize;
    off = fromsize;
    while (1)
     { if (flag_debug) fprintf(stderr,"check_reg_diff top of loop, off=%llu\n",off);
       n = (left < WORKSIZE) ? left : WORKSIZE;
       if (n < 1)
	{ closedown();
	  check_exit = 0;
	  return;
	}
       make_progress();
       for (EACH_REM(r))
	{ if ((r == mr) || MASK_TST(w,r->bit))
	   { setwait(r);
	     fprintf(r->wf,"r%d %llu",n,off);
	     putc('\0',r->wf);
	     fflush(r->wf);
	     fread(&r->work[0],1,n,r->rf);
	     make_progress();
	   }
	}
       off += n;
       left -= n;
       if (flag_debug) fprintf(stderr,"check_reg_diff off advanced to %llu\n",off);
       for (EACH_REM(r))
	{ if (MASK_TST(w,r->bit))
	   { if (bcmp(&mr->work[0],&r->work[0],n))
	      { if (flag_debug) fprintf(stderr,"check_reg_diff finds %s differs\n",r->fullarg);
		MASK_CLR(w,r->bit);
		MASK_SET(*m,r->bit);
	      }
	   }
	}
     }
  }
}

static int wildmatch(const char *pat, const char *s, const char *s1, const char *s2)
{
 while (1)
  { if (!*s && s1)
     { s = s1;
       s1 = s2;
       s2 = 0;
     }
    switch (*pat)
     { case '?':
	  if (! *s) return(0);
	  break;
       case '[':
	   { int found;
	     int neg;
	     int first;
	     const char *pp;
	     found = 0;
	     neg = 0;
	     first = 1;
	     pp = pat + 1;
	     if (*pp == '^')
	      { neg = 1;
		pp ++;
	      }
	     while (first || (*pp != ']'))
	      { if ( (pp[1] == '-') &&
		     pp[2] && (pp[2] != ']') &&
		     (pp[0] <= pp[2]) )
		 { if ((pp[0] <= *s) && (*s <= pp[2])) found = 1;
		   pp += 3;
		 }
		else
		 { if (pp[0] == *s) found = 1;
		   pp ++;
		 }
		first = 0;
	      }
	     if (! *pp)
	      { fprintf(stderr,"%s: unclosed [ in pattern\n",__progname);
		exit(1);
	      }
	     if (neg?found:!found) return(0);
	     pat = pp;
	   }
	  break;
       case '*':
	  if (wildmatch(pat+1,s,s1,s2)) return(1);
	  if (! *s) return(0);
	  s ++;
	  continue;
	  break;
       case '\0':
	  if (*s) return(0);
	  return(1);
	  break;
       default:
	  if (*pat != *s) return(0);
	  break;
     }
    pat ++;
    s ++;
  }
}

static int prunematch(PRUNE *p, const char *dir, const char *ent)
{
 switch (p->type)
  { case PT_PATH:
       if (dir)
	{ int dl;
	  dl = strlen(dir);
	  if ( !bcmp(dir,p->path,dl) &&
	       (p->path[dl] == '/') &&
	       !strcmp(p->path+dl+1,ent) ) return(1);
	}
       else
	{ if (!strcmp(p->path,ent)) return(1);
	}
       break;
    case PT_WILD:
       if (wildmatch(p->path,dir?dir:ent,dir?"/":0,dir?ent:0)) return(1);
       break;
    case PT_BASE:
       if (wildmatch(p->path,ent,0,0)) return(1);
       break;
  }
 return(0);
}

static void newfile_init(const char *dir, char *ent)
{
 PRUNE *p;
 PRUNE **pp;

 cur_dir = dir;
 cur_ent = ent;
 cur_pruned = 0;
 if ((dir[0] == '.') && !dir[1])
  { pp = &prunelist;
    while ((p = *pp))
     { if (prunematch(p,0,ent)) break;
       pp = &p->link;
     }
  }
 else
  { if ((dir[0] == '.') && (dir[1] == '/')) dir += 2;
    pp = &prunelist;
    while ((p = *pp))
     { if (prunematch(p,dir,ent)) break;
       pp = &p->link;
     }
  }
 if (p)
  { cur_pruned = 1;
    switch (p->type)
     { case PT_PATH:
	  *pp = p->link;
	  OLD(p);
	  break;
       case PT_WILD:
	  break;
     }
  }
 needhdg = 1;
 if (prunex) cur_pruned = ! cur_pruned;
}

static void endfile_flush(void)
{
 if (! needhdg) fflush(stdout);
}

#define ENTDIFF_TYPE 0x00000001
#define ENTDIFF_MODE 0x00000002
#define ENTDIFF_UID  0x00000004
#define ENTDIFF_GID  0x00000008
#define ENTDIFF_SIZE 0x00000010
#define ENTDIFF_RDEV 0x00000020
#define ENTDIFF_LINK 0x00000040
#define ENTDIFF_TIME 0x00000080
#define ENTDIFF_HLNK 0x00000100
static int entdiff(ENTRY *e1, ENTRY *e2)
{
 int how;

 if ((e1->mode & S_IFMT) != (e2->mode & S_IFMT)) return(ENTDIFF_TYPE);
 how = 0;
 if ((e1->mode ^ e2->mode) & perm_mask) how |= ENTDIFF_MODE;
 if (! flag_noown)
  { if (e1->uid != e2->uid) how |= ENTDIFF_UID;
    if (e1->gid != e2->gid) how |= ENTDIFF_GID;
  }
 if (flag_mtimes && (e1->mtime != e2->mtime)) how |= ENTDIFF_TIME;
 if (flag_links)
  { if ( (e1->hardlink && e1->hardlink->name && !e1->firstlink) ?
	   ( !(e2->hardlink && e2->hardlink->name && !e2->firstlink) ||
	     strcmp(e1->hardlink->name,e2->hardlink->name) ) :
	   (e2->hardlink && e2->hardlink->name && !e2->firstlink) )
     { how |= ENTDIFF_HLNK;
     }
  }
 switch (e1->mode & S_IFMT)
  { default:
       return(0);
       break;
    case S_IFDIR:
       if ( flag_sgiddir &&
	    (how & ENTDIFF_MODE) &&
	    ((e1->mode&05777&perm_mask) == (e2->mode&05777&perm_mask)) )
	{ how &= ~ENTDIFF_MODE;
	}
       break;
    case S_IFCHR:
    case S_IFBLK:
       if ( (e1->rdev_min != e2->rdev_min) ||
	    (e1->rdev_maj != e2->rdev_maj) ) how |= ENTDIFF_RDEV;
       break;
    case S_IFREG:
       if (e1->size != e2->size) how |= ENTDIFF_SIZE;
       break;
    case S_IFLNK:
       if (strcmp(e1->softlink,e2->softlink)) how |= ENTDIFF_LINK;
       how &= ~ENTDIFF_TIME;
       break;
    case S_IFSOCK:
       break;
#ifdef S_IFIFO
    case S_IFIFO:
       break;
#endif
  }
 return(how);
}

static void printent(REMOTE *r, const char *pref)
{
 ENTRY *e;
 time_t ttmp;

 e = r->dirlist;
 switch (e->mode & S_IFMT)
  { default:
       printf("%sunknown mode %o",pref,e->mode);
       break;
    case S_IFDIR:
       printf("%s%s",pref,modestring(e->mode));
       if (! flag_noown) printf(" (owner %d/%d)",e->uid,e->gid);
       break;
    case S_IFCHR:
    case S_IFBLK:
       printf("%s%s (",pref,modestring(e->mode));
       if (! flag_noown) printf("owner %d/%d, ",e->uid,e->gid);
       printf("device (%llu,%llu))",e->rdev_maj,e->rdev_min);
       break;
    case S_IFREG:
       printf("%s%s (",pref,modestring(e->mode));
       if (! flag_noown) printf("owner %d/%d, ",e->uid,e->gid);
       printf("size %llu)",e->size);
       break;
    case S_IFLNK:
       printf("%s%s (",pref,modestring(e->mode));
       if (! flag_noown) printf("owner %d/%d, ",e->uid,e->gid);
       printf("to %s)",e->softlink);
       break;
    case S_IFSOCK:
       printf("%s%s",pref,modestring(e->mode));
       if (! flag_noown) printf(" (owner %d/%d)",e->uid,e->gid);
       break;
#ifdef S_IFIFO
    case S_IFIFO:
       printf("%s%s",pref,modestring(e->mode));
       if (! flag_noown) printf(" (owner %d/%d)",e->uid,e->gid);
       break;
#endif
  }
 if (flag_mtimes)
  { ttmp = e->mtime;
    printf(" mtime %.24s",ctime(&ttmp));
  }
 if (flag_links && e->hardlink)
  { printf(" [hardlink");
    if (! e->firstlink) printf(" to %s",e->hardlink->name);
    printf("]");
  }
 printf(" in %s\n",r->fullarg);
}

static void union_entlist(ENTRY **listp, ENTRY *with)
{
 ENTRY *list;
 ENTRY_TCONC new;

 etc_init(&new);
 list = *listp;
 while (list && with)
  { int c;
    c = strcmp(list->name,with->name);
    if (c < 0)
     { etc_add(&new,list);
       list = list->link;
     }
    else if (c > 0)
     { etc_add(&new,copyentry(with));
       with = with->link;
     }
    else
     { etc_add(&new,list);
       list = list->link;
       with = with->link;
     }
  }
 while (with)
  { etc_add(&new,copyentry(with));
    with = with->link;
  }
 etc_append(&new,list);
 etc_end(&new);
 *listp = new.list;
}

static MVLIST *sort_mvlist(MVLIST *list, int (*fn)(MVLIST *, MVLIST *))
{
 MVLIST *l1;
 MVLIST *l2;
 MVLIST **lpp1;
 MVLIST **lpp2;
 MVLIST *l;

 if (!list || !list->link) return(list);
 lpp1 = &l1;
 lpp2 = &l2;
 while (list)
  { l = list;
    list = l->link;
    *lpp1 = l;
    lpp1 = lpp2;
    lpp2 = &l->link;
  }
 *lpp1 = 0;
 *lpp2 = 0;
 lpp1 = &list;
 while (l1 || l2)
  { if (!l2 || (l1 && (*fn)(l1,l2) < 0))
     { l = l1;
       l1 = l->link;
     }
    else
     { l = l2;
       l2 = l->link;
     }
    *lpp1 = l;
    lpp1 = &l->link;
  }
 *lpp1 = 0;
 return(list);
}

static int mvlist_sort_v_up(MVLIST *a, MVLIST *b)
{
 if (a->v < b->v) return(-1);
 if (a->v > b->v) return(1);
 return(0);
}

static void master_dir(const char *addl)
{
 REMOTE *r;
 MASK saveact;
 const char *last;
 REMOTE *nextr;
 MASK next;
 MASK dirs;
 ENTRY *e;
 ENTRY_TCONC recurse;
 int maxlen;
 int needprint;
 RSTACK rs;

 if (MASK_ISZERO(active) || MASK_ONEBIT(active)) return;
 saveact = active;
 for (EACH_REM(r))
  { if (MASK_TST(active,r->bit))
     { get_dirlist(r,addl);
     }
  }
 e = 0;
 for (EACH_REM(r)) union_entlist(&e,r->dirlist);
 rs.list = e;
 rs.cur = rs.list;
 rs.link = rstack;
 rstack = &rs;
 last = "";
 maxlen = 0;
 etc_init(&recurse);
 if (flag_update)
  { MASK mustrm;
    MASK mustinstall;
    MASK musttrunc;
    MASK mustchmod;
    MASK mustchown;
    MASK mustprint;
    MASK mustutimes;
    MASK ssmaller;
    MASK sequal;
    MASK slarger;
    MASK cdiffs;
    MASK sockets;
    MVLIST *instlist;
    static void add_instlist(MASK m, unsigned long int s)
     { MVLIST *mvl;
       if (! MASK_ISZERO(m))
	{ mvl = NEW(MVLIST);
	  mvl->m = m;
	  mvl->v = s & ~511UL;
	  mvl->link = instlist;
	  instlist = mvl;
	}
     }
    if (! MASK_TST(active,umaster->bit)) panic("-u master not active in master_dir");
    while (1)
     { nextr = 0;
       make_progress();
       for (EACH_REM(r)) if (MASK_TST(active,r->bit) && r->dirlist && ((nextr == 0) || (strcmp(r->dirlist->name,nextr->dirlist->name) < 0))) nextr = r;
       if (nextr == 0) break;
       while (rs.cur && (strcmp(rs.cur->name,nextr->dirlist->name) < 0)) rs.cur = rs.cur->link;
       MASK_ZERO(next);
       for (EACH_REM(r)) if (MASK_TST(active,r->bit) && r->dirlist && !strcmp(r->dirlist->name,nextr->dirlist->name)) MASK_SET(next,r->bit);
       newfile_init(addl,nextr->dirlist->name);
       MASK_ZERO(sockets);
       if (flag_ignoresockets) for (EACH_REM(r)) if (MASK_TST(next,r->bit) && ((r->dirlist->mode & S_IFMT) == S_IFSOCK)) MASK_SET(sockets,r->bit);
       if (! cur_pruned)
	{ MASK_ZERO(mustprint);
	  if (MASK_TST(next,umaster->bit))
	   { MASK_ZERO(mustrm);
	     MASK_ZERO(mustchmod);
	     MASK_ZERO(mustchown);
	     MASK_ZERO(mustutimes);
	     MASK_ZERO(ssmaller);
	     MASK_ZERO(sequal);
	     MASK_ZERO(slarger);
	     instlist = 0;
	     umaster->dirlist->mode = (umaster->dirlist->mode & perm_and) | perm_or;
	     for (EACH_REM(r))
	      { if (r == umaster) continue;
		if (MASK_TST(next,r->bit))
		 { int how;
		   how = entdiff(r->dirlist,umaster->dirlist);
		   if (how & ENTDIFF_TYPE)
		    { MASK_SET(mustrm,r->bit);
		    }
		   else
		    { if (how & ENTDIFF_MODE)
		       { MASK_SET(mustchmod,r->bit);
		       }
		      if (how & (ENTDIFF_UID|ENTDIFF_GID))
		       { MASK_SET(mustchown,r->bit);
		       }
		      if (how & (ENTDIFF_RDEV|ENTDIFF_LINK))
		       { MASK_SET(mustrm,r->bit);
		       }
		      if (how & ENTDIFF_SIZE)
		       { if (umaster->dirlist->size > r->dirlist->size) MASK_SET(ssmaller,r->bit); else if (umaster->dirlist->size < r->dirlist->size) MASK_SET(slarger,r->bit);
		       }
		      else
		       { MASK_SET(sequal,r->bit);
		       }
		      if (how & ENTDIFF_TIME)
		       { MASK_SET(mustutimes,r->bit);
		       }
		      if (how & ENTDIFF_HLNK)
		       { MASK_SET(mustrm,r->bit);
		       }
		    }
		 }
	      }
	     switch (umaster->dirlist->mode & S_IFMT)
	      { case S_IFREG:
		    { MVLIST *mvl;
		      unsigned long long int howfar;
		      MASK tocheck;
		      unsigned long long int s;
		      MASK cmp;
		      MASK diffs;
		      MASK eqsize;
		      MASK_ZERO(cdiffs);
		      mustinstall = mask_or(mustrm,mask_andnot(active,next));
		      tocheck = ssmaller;
		      howfar = 0;
		      while (! MASK_ISZERO(tocheck))
		       { s = ~0ULL;
			 for (EACH_REM(r)) if (MASK_TST(tocheck,r->bit) && (r->dirlist->size < s)) s = r->dirlist->size;
			 MASK_ZERO(cmp);
			 MASK_ZERO(eqsize);
			 for (EACH_REM(r)) if (MASK_TST(tocheck,r->bit))
			  { if (r->dirlist->size >= s) MASK_SET(cmp,r->bit);
			    if (r->dirlist->size == s) MASK_SET(eqsize,r->bit);
			  }
			 if (s == howfar)
			  { if (s) panic("master_dir without advancement");
			    MASK_ZERO(diffs);
			  }
			 else
			  { diffs = cmp;
			    check_reg_diff(umaster,addl,&diffs,howfar,s);
			  }
			 add_instlist(diffs,howfar);
			 add_instlist(mask_andnot(eqsize,diffs),s);
			 for (EACH_REM(r)) if (MASK_TST(tocheck,r->bit) && (r->dirlist->size >= s)) MASK_SET(cmp,r->bit);
			 howfar = s;
			 mask_setandnot(&tocheck,cmp);
		       }
		      diffs = mask_or(slarger,sequal);
		      check_reg_diff(umaster,addl,&diffs,0,umaster->dirlist->size);
		      add_instlist(mask_or(diffs,mustinstall),0);
		      cdiffs = mask_and(diffs,sequal);
		      musttrunc = mask_andnot(slarger,diffs);
		      mask_setor(&mustprint,musttrunc);
		      instlist = sort_mvlist(instlist,&mvlist_sort_v_up);
		      mvl = instlist;
		      while (mvl)
		       { if (mvl->link && (mvl->link->v == mvl->v))
			  { MVLIST *t;
			    t = mvl->link;
			    mask_setor(&mvl->m,t->m);
			    mvl->link = t->link;
			    free(t);
			  }
			 else
			  { mask_setor(&mustinstall,mvl->m);
			    mvl = mvl->link;
			  }
		       }
		      if (flag_debug)
		       { fprintf(stderr,"instlist:\n");
			 for (mvl=instlist;mvl;mvl=mvl->link) fprintf(stderr,"\tmask %08lx v %llu\n",mvl->m.bits[0],mvl->v);
		       }
		    }
		   break;
		case S_IFSOCK:
		   if (flag_ignoresockets)
		    { if (flag_nodelete) MASK_ZERO(mustrm);
		      else               mustrm = mask_andnot(next,sockets);
		      MASK_ZERO(mustinstall);
		      MASK_ZERO(mustchmod);
		      MASK_ZERO(mustchown);
		      MASK_ZERO(mustutimes);
		      MASK_ZERO(musttrunc);
		      mask_setandnot(&mustrm,sockets);
		      break;
		    }
		   /* fall through */
		default:
		   mustinstall = mask_or(mask_andnot(active,next),mustrm);
		   mask_setor(&mustutimes,mustinstall);
		   MASK_ZERO(musttrunc);
		   break;
	      }
	   }
	  else
	   { if (flag_nodelete) MASK_ZERO(mustrm);
	     else               mustrm = mask_andnot(next,sockets);
	     MASK_ZERO(mustinstall);
	     MASK_ZERO(mustchmod);
	     MASK_ZERO(mustchown);
	     MASK_ZERO(mustutimes);
	     MASK_ZERO(musttrunc);
	   }
	  mask_setandnot(&mustchmod,mustrm);
	  mask_setandnot(&mustchown,mustrm);
	  mask_setor(&mustprint,mask_or( mask_or(mustrm,mustinstall),
					 mask_or(mustchmod,mustchown) ));
	  if (flag_mtimes) mask_setor(&mustprint,mustutimes);
	  if (!MASK_ISZERO(mustprint))
	   { showhdg();
	     if (MASK_TST(next,umaster->bit))
	      { printent(umaster,"  * ");
	      }
	     else
	      { printf("  * nonexistent in %s\n",umaster->fullarg);
	      }
	     for (EACH_REM(r))
	      { if (r == umaster) continue;
		if (MASK_TST(mustprint,r->bit))
		 { if (MASK_TST(next,r->bit))
		    { printent(r,"    ");
		    }
		   else
		    { printf("    nonexistent in %s\n",r->fullarg);
		    }
		 }
	      }
	     if (MASK_TST(next,umaster->bit) && ((umaster->dirlist->mode & S_IFMT) == S_IFREG) && !MASK_ISZERO(cdiffs))
	      { printf("    plain file %s differing from master copy:\n",flag_md5?"checksums":"contents");
		for (EACH_REM(r)) if (MASK_TST(cdiffs,r->bit)) printf("\t%s\n",r->fullarg);
	      }
	   }
	  MASK_ZERO(dirs);
	  fflush(stdout);
	  if (MASK_TST(next,umaster->bit))
	   { ENTRY *e;
	     e = umaster->dirlist;
	     for (EACH_REM(r))
	      { if (MASK_TST(mustrm,r->bit))
		 { if (r->dirlist->hardlink && r->dirlist->firstlink)
		    { link_dead(r->dirlist->hardlink);
		    }
		   printf("      removal from %s %s\n",r->fullarg,rmrm(r,addl,e->name)?"failed":"succeeded");
		 }
	      }
	     if (! MASK_ISZERO(mustinstall))
	      { if (umaster->dirlist->hardlink && !umaster->dirlist->firstlink)
		 { install_hardlink(mustinstall,addl,umaster->dirlist->hardlink->name);
		 }
		else if ((umaster->dirlist->mode & S_IFMT) == S_IFREG)
		 { MVLIST *mvl;
		   MASK m;
		   MASK_ZERO(m);
		   for (mvl=instlist;mvl;mvl=mvl->link)
		    { unsigned long long int nextsize;
		      nextsize = mvl->link ? mvl->link->v : e->size;
		      mask_setor(&m,mvl->m);
		      if (flag_mtimes) mask_setor(&mustutimes,mvl->m);
		      installreg(m,addl,e->name,0,mvl->v,nextsize);
		    }
		 }
		else
		 { installit(mustinstall,addl,0);
		   if (flag_mtimes) mask_setor(&mustutimes,mustinstall);
		 }
	      }
	     if (! MASK_ISZERO(mustchown)) rmchown(mustchown,addl,e->name,0);
	     if (! MASK_ISZERO(musttrunc)) rmtrunc(musttrunc,addl,e->name,0);
	     if (! MASK_ISZERO(mustchmod)) rmchmod(mustchmod,addl,e->name,0);
	     if (flag_mtimes && !MASK_ISZERO(mustutimes)) rmutimes(mustutimes,addl,e->name,0);
	     if ((umaster->dirlist->mode & S_IFMT) == S_IFDIR) dirs = next;
	   }
	  else
	   { for (EACH_REM(r)) if (MASK_TST(mustrm,r->bit)) printf("      removal from %s %s\n",r->fullarg,rmrm(r,addl,nextr->dirlist->name)?"failed":"succeeded");
	   }
	  endfile_flush();
	  if (! MASK_ISZERO(dirs))
	   { int l;
	     for (EACH_REM(r)) if (MASK_TST(dirs,r->bit)) break;
	     e = r->dirlist;
	     e->present = dirs;
	     etc_add(&recurse,e);
	     l = strlen(e->name);
	     if (l > maxlen) maxlen = l;
	     r->dirlist = r->dirlist->link;
	     MASK_CLR(next,r->bit);
	   }
	}
       for (EACH_REM(r))
	{ if (MASK_TST(next,r->bit))
	   { e = r->dirlist;
	     r->dirlist = e->link;
	     freeentry(e);
	   }
	}
     }
  }
 else
  { MASK todo;
    MASK these;
    MASK ignnx;
    MASK sockets;
    MASK effnext;
    while (1)
     { nextr = 0;
       for (EACH_REM(r)) if (MASK_TST(active,r->bit) && r->dirlist && ((nextr == 0) || (strcmp(r->dirlist->name,nextr->dirlist->name) < 0))) nextr = r;
       if (nextr == 0) break;
       while (rs.cur && (strcmp(rs.cur->name,nextr->dirlist->name) < 0)) rs.cur = rs.cur->link;
       MASK_ZERO(next);
       MASK_ZERO(ignnx);
       MASK_ZERO(sockets);
       for (EACH_REM(r))
	{ if (MASK_TST(active,r->bit))
	   { if (r->dirlist && !strcmp(r->dirlist->name,nextr->dirlist->name))
	      { MASK_SET(next,r->bit);
		r->dirlist->mode = (r->dirlist->mode & perm_and) | perm_or;
	      }
	     if (r->ignnx) MASK_SET(ignnx,r->bit);
	   }
	}
       if (flag_ignoresockets) for (EACH_REM(r)) if (MASK_TST(next,r->bit) && ((r->dirlist->mode & S_IFMT) == S_IFSOCK)) MASK_SET(sockets,r->bit);
       newfile_init(addl,nextr->dirlist->name);
       effnext = mask_andnot(next,sockets);
       if (!cur_pruned && !MASK_ISZERO(effnext))
	{ MASK_ZERO(dirs);
	  if (! MASK_TST(effnext,nextr->bit))
	   { for (EACH_REM(r))
	      { if (MASK_TST(effnext,r->bit))
		 { nextr = r;
		   break;
		 }
	      }
	     if (! MASK_TST(effnext,nextr->bit)) panic("can't find non-socket representative");
	   }
	  for (EACH_REM(r)) if (MASK_TST(effnext,r->bit) && ((r->dirlist->mode & S_IFMT) == S_IFDIR)) MASK_SET(dirs,r->bit);
	  for (EACH_REM(r)) if (MASK_TST(effnext,r->bit) && entdiff(r->dirlist,nextr->dirlist)) break;
	  cur_nx = mask_andnot(active,next);
	  if (r || !MASK_ISZERO(mask_andnot(cur_nx,ignnx)))
	   { showhdg();
	     todo = next;
	     while (! MASK_ISZERO(todo))
	      { for (EACH_REM(r)) if (MASK_TST(todo,r->bit)) break;
		e = r->dirlist;
		MASK_ZERO(these);
		for (EACH_REM(r))
		 { if (MASK_TST(todo,r->bit) && (entdiff(e,r->dirlist) == 0))
		    { printent(r,"    ");
		      MASK_SET(these,r->bit);
		    }
		 }
		mask_setandnot(&todo,these);
	      }
	     todo = next;
	     for (EACH_REM(r)) if (MASK_TST(todo,r->bit) && ((r->dirlist->mode & S_IFMT) != S_IFREG)) MASK_CLR(todo,r->bit);
	     needprint = 0;
	     while (! MASK_ISZERO(todo))
	      { REMOTE *r2;
		for (EACH_REM(r2)) if (MASK_TST(todo,r2->bit)) break;
		MASK_ZERO(these);
		for (EACH_REM(r)) if (MASK_TST(todo,r->bit) && (r->dirlist->size == r2->dirlist->size)) MASK_SET(these,r->bit);
		if (! MASK_EQ(these,todo)) needprint = 1;
		compare_reg(these,r2->dirlist->size,needprint);
		mask_setandnot(&todo,these);
	      }
	   }
	  else
	   { e = nextr->dirlist;
	     if ((e->mode & S_IFMT) == S_IFREG)
	      { compare_reg(next,e->size,0);
	      }
	   }
	  endfile_flush();
	  if (! MASK_ISZERO(dirs))
	   { int l;
	     for (EACH_REM(r)) if (MASK_TST(dirs,r->bit)) break;
	     e = r->dirlist;
	     e->present = dirs;
	     etc_add(&recurse,e);
	     l = strlen(e->name);
	     if (l > maxlen) maxlen = l;
	     r->dirlist = r->dirlist->link;
	     MASK_CLR(next,r->bit);
	   }
	}
       for (EACH_REM(r))
	{ if (MASK_TST(next,r->bit))
	   { e = r->dirlist;
	     r->dirlist = e->link;
	     freeentry(e);
	   }
	}
     }
  }
 e = rs.list;
 rs.list = 0;
 rs.cur = 0;
 while (e)
  { ENTRY *e2;
    e2 = e->link;
    freeentry(e);
    e = e2;
  }
 etc_end(&recurse);
 if (recurse.list)
  { int oldlen;
    char *newname;
    oldlen = strlen(addl);
    newname = malloc(oldlen+1+maxlen+1);
    bcopy(addl,newname,oldlen);
    newname[oldlen] = '/';
    rs.list = recurse.list;
    for (e=recurse.list;e;e=e->link)
     { strcpy(newname+oldlen+1,e->name);
       active = e->present;
       rs.cur = e;
       master_dir(newname);
     }
    rs.list = 0;
    while (recurse.list)
     { e = recurse.list->link;
       freeentry(recurse.list);
       recurse.list = e;
     }
    free(newname);
  }
 rstack = rs.link;
 active = saveact;
}

static FILE *siginfo_outf;

#define f siginfo_outf
static void print_rstack(RSTACK *rs, int verbose)
{
 ENTRY *e;
 int n;

 if (! rs) return;
 print_rstack(rs->link,verbose);
 if (! rs->list) return;
 if (! verbose)
  { fprintf(f,"%s",rs->cur->name);
    if ((rs->cur->mode & S_IFMT) == S_IFDIR) fprintf(f,"/");
    return;
  }
 for (n=10,e=rs->list;e&&(n>0);n--,e=e->link) ;
 if (e)
  { if (! rs->cur)
     { while (e->link) e = e->link;
       fprintf(f,"%s ... %s",rs->list->name,e->name);
     }
    else
     { e = rs->cur;
       if (e == rs->list)
	{ fprintf(f,"[ %s ]",e->name);
	}
       else if (e == rs->list->link)
	{ fprintf(f,"%s [ %s ]",rs->list->name,e->name);
	}
       else if (e == rs->list->link->link)
	{ fprintf(f,"%s %s [ %s ]",rs->list->name,rs->list->link->name,e->name);
	}
       else if (e == rs->list->link->link->link)
	{ fprintf(f,"%s %s %s [ %s ]",rs->list->name,rs->list->link->name,rs->list->link->link->name,e->name);
	}
       else if (e == rs->list->link->link->link->link)
	{ fprintf(f,"%s %s %s %s [ %s ]",rs->list->name,rs->list->link->name,rs->list->link->link->name,rs->list->link->link->link->name,e->name);
	}
       else
	{ ENTRY *e1;
	  ENTRY *e2;
	  ENTRY *e3;
	  fprintf(f,"%s ... ",rs->list->name);
	  e1 = 0;
	  e2 = 0;
	  e3 = 0;
	  for (e=rs->list;e&&(e!=rs->cur);e=e->link)
	   { e1 = e2;
	     e2 = e3;
	     e3 = e;
	   }
	  if (e)
	   { fprintf(f,"%s %s %s [ %s ]",e1->name,e2->name,e3->name,e->name);
	   }
	  else
	   { fprintf(f,"%s [?can't find current entry]",e3->name);
	   }
	}
       if (e)
	{ n = 0;
	  for (e=e->link;e;n++,e=e->link)
	   { if (!e->link || (n < 3)) fprintf(f," %s",e->name);
	     if ((n == 2) && e->link && e->link->link) fprintf(f," ...");
	   }
	}
     }
  }
 else
  { for (e=rs->list;e;e=e->link)
     { if (e == rs->cur)
	{ fprintf(f,"[ %s ]%s",e->name,e->link?" ":"");
	}
       else
	{ fprintf(f,"%s%s",e->name,e->link?" ":"");
	}
     }
  }
 fprintf(f,"\n");
}
#undef f

#ifdef SIGINFO

static void dumpstate(void)
{
 REMOTE *r;

 r = waitrem;
 if (r)
  { fprintf(siginfo_outf,"Blocking on %s\n",r->fullarg);
  }
 else
  { fprintf(siginfo_outf,"Not blocking\n");
  }
}

static void siginfo(int sig __attribute__((__unused__)))
{
 int p;

 print_rstack(rstack,1);
 fprintf(siginfo_outf,"\n");
 p = progress;
 if (p > 0) progress = p-1; else dumpstate();
 fflush(siginfo_outf);
}

#endif

static void sigalrm(int sig __attribute__((__unused__)))
{
 print_rstack(rstack,0);
 fprintf(siginfo_outf,"\n");
 fflush(siginfo_outf);
}

static void runmaster(void)
{
 REMOTE *r;
 int bit;

 bit = 0;
 MASK_ZERO(active);
 for (EACH_REM(r))
  { r->bit = bit;
    MASK_SET(active,bit);
    bit ++;
  }
 if (bit > MAXREMOTES)
  { fprintf(stderr,"%s: too many directories specified\n",__progname);
    exit(1);
  }
 if (flag_update) for (EACH_REM(r)) umaster = r;
 rstack = 0;
 siginfo_outf = fopen("/dev/tty","w");
 signal(SIGALRM,siginfo_outf?sigalrm:SIG_IGN);
 if (siginfo_outf && (tick_seconds > 0))
  { struct itimerval itv;
    itv.it_interval.tv_sec = tick_seconds;
    itv.it_interval.tv_usec = 0;
    itv.it_value = itv.it_interval;
    setitimer(ITIMER_REAL,&itv,0);
  }
 progress = MAX_PROGRESS;
#ifdef SIGINFO
 if (siginfo_outf) signal(SIGINFO,siginfo);
#endif
 master_dir(".");
 for (EACH_REM(r))
  { setwait(r);
    fflush(r->wf);
    make_progress();
  }
}

static void master(void)
{
 if (remotes == 0)
  { fprintf(stderr,"%s: no directories specified\n",__progname);
    exit(1);
  }
 if (remotes->link == 0)
  { fprintf(stderr,"%s: only one directory specified\n",__progname);
    exit(1);
  }
 runremotes();
 startup_remotes();
 runmaster();
}

static void add_prune(char *path, int type)
{
 PRUNE *p;

 if ((path[0] == '.') && (path[1] == '/'))
  { path += 2;
    while (path[0] == '/') path ++;
  }
 p = NEW(PRUNE);
 p->type = type;
 p->path = path;
 p->link = prunelist;
 prunelist = p;
}

#ifndef NO_NETWORK
static void setup_accept(char *s)
{
 conn_port = s;
 conn_addr = 0;
 flag_accept = 1;
}
#endif

static void add_mapping(char *dir, char *from, char *to)
{
 MAPPING *m;

 m = malloc(sizeof(MAPPING));
 m->dir = dir;
 m->from = from;
 m->to = to;
 m->link = pendmap;
 pendmap = m;
}

int main(int, char **);
int main(int ac, char **av)
{
 remotes = 0;
 defprogram = av[0];
 perm_mask = 07777;
 tick_seconds = 0;
 pendmap = 0;
 signal(SIGALRM,SIG_IGN);
#define SKIP(n) do{ac-=(n);av+=(n);}while(0)
 while (ac > 1)
  { SKIP(1);
    if (!strcmp(av[0],"-debug") || !strcmp(av[0],"-D"))
     { flag_debug = 1;
       continue;
     }
    if (!strcmp(av[0],"-follow") || !strcmp(av[0],"-h"))
     { flag_follow = 1;
       continue;
     }
    if (!strcmp(av[0],"-sgid-dirs") || !strcmp(av[0],"-g"))
     { flag_sgiddir = 1;
       continue;
     }
    if (!strcmp(av[0],"-no-owners") || !strcmp(av[0],"-o"))
     { flag_noown = 1;
       continue;
     }
    if (!strcmp(av[0],"-R"))
     { flag_remote = 1;
       continue;
     }
    if (!strcmp(av[0],"-mtimes") || !strcmp(av[0],"-t"))
     { flag_mtimes = 1;
       continue;
     }
    if (!strcmp(av[0],"-update") || !strcmp(av[0],"-u"))
     { flag_update = 1;
       continue;
     }
    if (!strcmp(av[0],"-encode") || !strcmp(av[0],"-x"))
     { flag_encode = 1;
       continue;
     }
#ifndef NO_NETWORK
    if (!strcmp(av[0],"-trace") || !strcmp(av[0],"-X"))
     { flag_trace = 1;
       continue;
     }
#endif
    if (!strcmp(av[0],"-readonly") || !strcmp(av[0],"-ro"))
     { flag_readonly = 1;
       continue;
     }
    if (!strcmp(av[0],"-check"))
     { flag_check = 1;
       continue;
     }
    if (!strcmp(av[0],"-ignore-sockets"))
     { flag_ignoresockets = 1;
       continue;
     }
    if (!strcmp(av[0],"-no-sparse"))
     { flag_nosparse = 1;
       continue;
     }
    if (!strcmp(av[0],"-links"))
     { flag_links = 1;
       continue;
     }
    if (!strcmp(av[0],"-ignore-nonexistent") || !strcmp(av[0],"-inx"))
     { flag_nextinx = 1;
       continue;
     }
    if (!strcmp(av[0],"-md5"))
     { flag_md5 = 1;
       continue;
     }
    if (!strcmp(av[0],"-no-delete"))
     { flag_nodelete = 1;
       continue;
     }
    if (!strcmp(av[0],"-rsync"))
     { flag_rsync = 1;
       continue;
     }
    if (!strcmp(av[0],"-gzip") && av[1])
     { gzip_arg = av[1];
       flag_gzip = 1;
       SKIP(1);
       continue;
     }
    if (!strcmp(av[0],"-prune") && av[1])
     { add_prune(av[1],PT_PATH);
       SKIP(1);
       continue;
     }
    if (!strcmp(av[0],"-prunewild") && av[1])
     { add_prune(av[1],PT_WILD);
       SKIP(1);
       continue;
     }
    if (!strcmp(av[0],"-prunebase") && av[1])
     { add_prune(av[1],PT_BASE);
       SKIP(1);
       continue;
     }
#ifndef NO_NETWORK
    if (!strcmp(av[0],"-accept") && av[1])
     { setup_accept(av[1]);
       SKIP(1);
       continue;
     }
    if (!strcmp(av[0],"-connect") && av[1] && av[2])
     { conn_addr = av[1];
       conn_port = av[2];
       flag_connect = 1;
       SKIP(2);
       continue;
     }
#endif
    if (!strcmp(av[0],"-prunex"))
     { prunex = 1;
       continue;
     }
    if (!strcmp(av[0],"-bg"))
     { flag_bg = 1;
       continue;
     }
    if (!strcmp(av[0],"-limit"))
     { flag_limitslave = 1;
       continue;
     }
#ifndef NO_NETWORK
    if (!strcmp(av[0],"-rsh") && av[1])
     { currsh = av[1];
       SKIP(1);
       continue;
     }
#endif
    if (!strcmp(av[0],"-dir") && av[1])
     { newremote(av[1]);
       SKIP(1);
       continue;
     }
    if (!strcmp(av[0],"-tick") && av[1])
     { tick_seconds = atoi(av[1]);
       SKIP(1);
       continue;
     }
    if (!strcmp(av[0],"-mask") && av[1])
     { perm_mask = strtol(av[1],0,8) & 07777;
       SKIP(1);
       continue;
     }
    if (!strcmp(av[0],"-modemod") && av[1] && av[2])
     { perm_and = (strtol(av[1],0,8) & 07777) | (~0U << 12);
       perm_or = strtol(av[2],0,8) & 07777;
       SKIP(2);
       continue;
     }
    if (!strcmp(av[0],"-map") && av[1] && av[2] && av[3])
     { add_mapping(av[1],av[2],av[3]);
       SKIP(3);
       continue;
     }
    if (!strcmp(av[0],"-force") && av[1])
     { force_dir = av[1];
       SKIP(1);
       continue;
     }
    if (av[0][0] == '-')
     { fprintf(stderr,"%s: unrecognized flag %s\n",__progname,av[0]);
       exit(1);
     }
    newremote(av[0]);
  }
#undef SKIP
 bzero(&zeroblk[0],WORKSIZE);
 check_exit = 0;
 if (flag_remote)
  {
#ifndef NO_NETWORK
    int s;
    ACINFO *aci;
#endif
    if (remotes)
     { fprintf(stderr,"%s: no directories may be given when using -R\n",__progname);
       exit(1);
     }
#ifndef NO_NETWORK
    if (flag_accept)
     { aci = malloc(sizeof(ACINFO));
       aci_setup_accept(aci,conn_port,&s);
     }
    else if (flag_connect)
     { aci = malloc(sizeof(ACINFO));
       aci_setup_connect(aci,conn_addr,conn_port,&s);
     }
    else
     { aci = 0;
     }
#endif
    maybe_background();
#ifndef NO_NETWORK
    if (aci)
     { remote_ac_wait(aci);
       if (s < 0) exit(1);
       ac_free(aci);
       if (s != 0)
	{ dup2(s,0);
	  close(s);
	}
       dup2(0,1);
     }
#endif
    slave();
  }
 else
  { if (flag_readonly)
     { fprintf(stderr,"%s: can't use -readonly except with -R\n",__progname);
       exit(1);
     }
    if (flag_connect)
     { fprintf(stderr,"%s: can't use -connect except with -R\n",__progname);
       exit(1);
     }
    if (flag_accept)
     { fprintf(stderr,"%s: can't use -accept except with -R\n",__progname);
       exit(1);
     }
    if (flag_bg)
     { fprintf(stderr,"%s: can't use -bg except with -R\n",__progname);
       exit(1);
     }
    master();
  }
 exit(0);
}
