386BSD 0.0 development
authorWilliam F. Jolitz <wjolitz@soda.berkeley.edu>
Thu, 18 Apr 1991 23:31:56 +0000 (15:31 -0800)
committerWilliam F. Jolitz <wjolitz@soda.berkeley.edu>
Thu, 18 Apr 1991 23:31:56 +0000 (15:31 -0800)
Work on file usr/src/usr.bin/rdist/expand.c
Work on file usr/src/usr.bin/rdist/defs.h
Work on file usr/src/usr.bin/rdist/main.c
Work on file usr/src/usr.bin/rdist/pathnames.h
Work on file usr/src/usr.bin/rdist/lookup.c
Work on file usr/src/usr.bin/rdist/server.c

Co-Authored-By: Lynne Greer Jolitz <ljolitz@cardio.ucsf.edu>
Synthesized-from: 386BSD-0.0/src

usr/src/usr.bin/rdist/defs.h [new file with mode: 0644]
usr/src/usr.bin/rdist/expand.c [new file with mode: 0644]
usr/src/usr.bin/rdist/lookup.c [new file with mode: 0644]
usr/src/usr.bin/rdist/main.c [new file with mode: 0644]
usr/src/usr.bin/rdist/pathnames.h [new file with mode: 0644]
usr/src/usr.bin/rdist/server.c [new file with mode: 0644]

diff --git a/usr/src/usr.bin/rdist/defs.h b/usr/src/usr.bin/rdist/defs.h
new file mode 100644 (file)
index 0000000..d39da0f
--- /dev/null
@@ -0,0 +1,158 @@
+/*
+ * Copyright (c) 1983 Regents of the University of California.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *     This product includes software developed by the University of
+ *     California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *     @(#)defs.h      5.9 (Berkeley) 8/27/90
+ */
+
+#include <sys/param.h>
+#include <sys/dir.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/file.h>
+#include <netinet/in.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <errno.h>
+#include <pwd.h>
+#include <grp.h>
+#include "pathnames.h"
+
+/*
+ * The version number should be changed whenever the protocol changes.
+ */
+#define VERSION         3
+
+       /* defines for yacc */
+#define EQUAL  1
+#define LP     2
+#define RP     3
+#define SM     4
+#define ARROW  5
+#define COLON  6
+#define DCOLON 7
+#define NAME   8
+#define STRING 9
+#define INSTALL        10
+#define NOTIFY 11
+#define EXCEPT 12
+#define PATTERN        13
+#define SPECIAL        14
+#define OPTION 15
+
+       /* lexical definitions */
+#define        QUOTE   0200            /* used internally for quoted characters */
+#define        TRIM    0177            /* Mask to strip quote bit */
+
+       /* table sizes */
+#define HASHSIZE       1021
+#define INMAX  3500
+
+       /* option flags */
+#define VERIFY 0x1
+#define WHOLE  0x2
+#define YOUNGER        0x4
+#define COMPARE        0x8
+#define REMOVE 0x10
+#define FOLLOW 0x20
+#define IGNLNKS        0x40
+
+       /* expand type definitions */
+#define E_VARS 0x1
+#define E_SHELL        0x2
+#define E_TILDE        0x4
+#define E_ALL  0x7
+
+       /* actions for lookup() */
+#define LOOKUP 0
+#define INSERT 1
+#define REPLACE        2
+
+#define ISDIR(m) (((m) & S_IFMT) == S_IFDIR)
+
+#define ALLOC(x) (struct x *) malloc(sizeof(struct x))
+
+struct namelist {      /* for making lists of strings */
+       char    *n_name;
+       struct  namelist *n_next;
+};
+
+struct subcmd {
+       short   sc_type;        /* type - INSTALL,NOTIFY,EXCEPT,SPECIAL */
+       short   sc_options;
+       char    *sc_name;
+       struct  namelist *sc_args;
+       struct  subcmd *sc_next;
+};
+
+struct cmd {
+       int     c_type;         /* type - ARROW,DCOLON */
+       char    *c_name;        /* hostname or time stamp file name */
+       char    *c_label;       /* label for partial update */
+       struct  namelist *c_files;
+       struct  subcmd *c_cmds;
+       struct  cmd *c_next;
+};
+
+struct linkbuf {
+       ino_t   inum;
+       dev_t   devnum;
+       int     count;
+       char    pathname[BUFSIZ];
+       char    target[BUFSIZ];
+       struct  linkbuf *nextp;
+};
+
+extern int debug;              /* debugging flag */
+extern int nflag;              /* NOP flag, don't execute commands */
+extern int qflag;              /* Quiet. don't print messages */
+extern int options;            /* global options */
+
+extern int nerrs;              /* number of errors seen */
+extern int rem;                        /* remote file descriptor */
+extern int iamremote;          /* acting as remote server */
+extern char tempfile[];                /* file name for logging changes */
+extern struct linkbuf *ihead;  /* list of files with more than one link */
+extern struct passwd *pw;      /* pointer to static area used by getpwent */
+extern struct group *gr;       /* pointer to static area used by getgrent */
+extern char host[];            /* host name of master copy */
+extern char buf[];             /* general purpose buffer */
+extern int errno;              /* system error number */
+
+char *makestr();
+struct namelist *makenl();
+struct subcmd *makesubcmd();
+struct namelist *lookup();
+struct namelist *expand();
+char *exptilde();
+char *malloc();
+char *rindex();
+char *index();
diff --git a/usr/src/usr.bin/rdist/expand.c b/usr/src/usr.bin/rdist/expand.c
new file mode 100644 (file)
index 0000000..d3c0e9e
--- /dev/null
@@ -0,0 +1,649 @@
+/*
+ * Copyright (c) 1983 Regents of the University of California.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *     This product includes software developed by the University of
+ *     California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef lint
+static char sccsid[] = "@(#)expand.c   5.6 (Berkeley) 6/1/90";
+#endif /* not lint */
+
+#include "defs.h"
+
+#define        GAVSIZ  NCARGS / 6
+#define LC '{'
+#define RC '}'
+
+static char    shchars[] = "${[*?";
+
+int    which;          /* bit mask of types to expand */
+int    eargc;          /* expanded arg count */
+char   **eargv;        /* expanded arg vectors */
+char   *path;
+char   *pathp;
+char   *lastpathp;
+char   *tilde;         /* "~user" if not expanding tilde, else "" */
+char   *tpathp;
+int    nleft;
+
+int    expany;         /* any expansions done? */
+char   *entp;
+char   **sortbase;
+
+char   *index();
+int    argcmp();
+
+#define sort() qsort((char *)sortbase, &eargv[eargc] - sortbase, \
+                     sizeof(*sortbase), argcmp), sortbase = &eargv[eargc]
+
+/*
+ * Take a list of names and expand any macros, etc.
+ * wh = E_VARS if expanding variables.
+ * wh = E_SHELL if expanding shell characters.
+ * wh = E_TILDE if expanding `~'.
+ * or any of these or'ed together.
+ *
+ * Major portions of this were snarfed from csh/sh.glob.c.
+ */
+struct namelist *
+expand(list, wh)
+       struct namelist *list;
+       int wh;
+{
+       register struct namelist *nl, *prev;
+       register int n;
+       char pathbuf[BUFSIZ];
+       char *argvbuf[GAVSIZ];
+
+       if (debug) {
+               printf("expand(%x, %d)\nlist = ", list, wh);
+               prnames(list);
+       }
+
+       if (wh == 0) {
+               register char *cp;
+
+               for (nl = list; nl != NULL; nl = nl->n_next)
+                       for (cp = nl->n_name; *cp; cp++)
+                               *cp = *cp & TRIM;
+               return(list);
+       }
+
+       which = wh;
+       path = tpathp = pathp = pathbuf;
+       *pathp = '\0';
+       lastpathp = &path[sizeof pathbuf - 2];
+       tilde = "";
+       eargc = 0;
+       eargv = sortbase = argvbuf;
+       *eargv = 0;
+       nleft = NCARGS - 4;
+       /*
+        * Walk the name list and expand names into eargv[];
+        */
+       for (nl = list; nl != NULL; nl = nl->n_next)
+               expstr(nl->n_name);
+       /*
+        * Take expanded list of names from eargv[] and build a new list.
+        */
+       list = prev = NULL;
+       for (n = 0; n < eargc; n++) {
+               nl = makenl(NULL);
+               nl->n_name = eargv[n];
+               if (prev == NULL)
+                       list = prev = nl;
+               else {
+                       prev->n_next = nl;
+                       prev = nl;
+               }
+       }
+       if (debug) {
+               printf("expanded list = ");
+               prnames(list);
+       }
+       return(list);
+}
+
+expstr(s)
+       char *s;
+{
+       register char *cp, *cp1;
+       register struct namelist *tp;
+       char *tail;
+       char buf[BUFSIZ];
+       int savec, oeargc;
+       extern char homedir[];
+
+       if (s == NULL || *s == '\0')
+               return;
+
+       if ((which & E_VARS) && (cp = index(s, '$')) != NULL) {
+               *cp++ = '\0';
+               if (*cp == '\0') {
+                       yyerror("no variable name after '$'");
+                       return;
+               }
+               if (*cp == LC) {
+                       cp++;
+                       if ((tail = index(cp, RC)) == NULL) {
+                               yyerror("unmatched '{'");
+                               return;
+                       }
+                       *tail++ = savec = '\0';
+                       if (*cp == '\0') {
+                               yyerror("no variable name after '$'");
+                               return;
+                       }
+               } else {
+                       tail = cp + 1;
+                       savec = *tail;
+                       *tail = '\0';
+               }
+               tp = lookup(cp, NULL, 0);
+               if (savec != '\0')
+                       *tail = savec;
+               if (tp != NULL) {
+                       for (; tp != NULL; tp = tp->n_next) {
+                               sprintf(buf, "%s%s%s", s, tp->n_name, tail);
+                               expstr(buf);
+                       }
+                       return;
+               }
+               sprintf(buf, "%s%s", s, tail);
+               expstr(buf);
+               return;
+       }
+       if ((which & ~E_VARS) == 0 || !strcmp(s, "{") || !strcmp(s, "{}")) {
+               Cat(s, "");
+               sort();
+               return;
+       }
+       if (*s == '~') {
+               cp = ++s;
+               if (*cp == '\0' || *cp == '/') {
+                       tilde = "~";
+                       cp1 = homedir;
+               } else {
+                       tilde = cp1 = buf;
+                       *cp1++ = '~';
+                       do
+                               *cp1++ = *cp++;
+                       while (*cp && *cp != '/');
+                       *cp1 = '\0';
+                       if (pw == NULL || strcmp(pw->pw_name, buf+1) != 0) {
+                               if ((pw = getpwnam(buf+1)) == NULL) {
+                                       strcat(buf, ": unknown user name");
+                                       yyerror(buf+1);
+                                       return;
+                               }
+                       }
+                       cp1 = pw->pw_dir;
+                       s = cp;
+               }
+               for (cp = path; *cp++ = *cp1++; )
+                       ;
+               tpathp = pathp = cp - 1;
+       } else {
+               tpathp = pathp = path;
+               tilde = "";
+       }
+       *pathp = '\0';
+       if (!(which & E_SHELL)) {
+               if (which & E_TILDE)
+                       Cat(path, s);
+               else
+                       Cat(tilde, s);
+               sort();
+               return;
+       }
+       oeargc = eargc;
+       expany = 0;
+       expsh(s);
+       if (eargc == oeargc)
+               Cat(s, "");             /* "nonomatch" is set */
+       sort();
+}
+
+static
+argcmp(a1, a2)
+       char **a1, **a2;
+{
+
+       return (strcmp(*a1, *a2));
+}
+
+/*
+ * If there are any Shell meta characters in the name,
+ * expand into a list, after searching directory
+ */
+expsh(s)
+       char *s;
+{
+       register char *cp;
+       register char *spathp, *oldcp;
+       struct stat stb;
+
+       spathp = pathp;
+       cp = s;
+       while (!any(*cp, shchars)) {
+               if (*cp == '\0') {
+                       if (!expany || stat(path, &stb) >= 0) {
+                               if (which & E_TILDE)
+                                       Cat(path, "");
+                               else
+                                       Cat(tilde, tpathp);
+                       }
+                       goto endit;
+               }
+               addpath(*cp++);
+       }
+       oldcp = cp;
+       while (cp > s && *cp != '/')
+               cp--, pathp--;
+       if (*cp == '/')
+               cp++, pathp++;
+       *pathp = '\0';
+       if (*oldcp == '{') {
+               execbrc(cp, NULL);
+               return;
+       }
+       matchdir(cp);
+endit:
+       pathp = spathp;
+       *pathp = '\0';
+}
+
+matchdir(pattern)
+       char *pattern;
+{
+       struct stat stb;
+       register struct direct *dp;
+       DIR *dirp;
+
+       dirp = opendir(path);
+       if (dirp == NULL) {
+               if (expany)
+                       return;
+               goto patherr2;
+       }
+       if (fstat(dirp->dd_fd, &stb) < 0)
+               goto patherr1;
+       if (!ISDIR(stb.st_mode)) {
+               errno = ENOTDIR;
+               goto patherr1;
+       }
+       while ((dp = readdir(dirp)) != NULL)
+               if (match(dp->d_name, pattern)) {
+                       if (which & E_TILDE)
+                               Cat(path, dp->d_name);
+                       else {
+                               strcpy(pathp, dp->d_name);
+                               Cat(tilde, tpathp);
+                               *pathp = '\0';
+                       }
+               }
+       closedir(dirp);
+       return;
+
+patherr1:
+       closedir(dirp);
+patherr2:
+       strcat(path, ": ");
+       strcat(path, strerror(errno));
+       yyerror(path);
+}
+
+execbrc(p, s)
+       char *p, *s;
+{
+       char restbuf[BUFSIZ + 2];
+       register char *pe, *pm, *pl;
+       int brclev = 0;
+       char *lm, savec, *spathp;
+
+       for (lm = restbuf; *p != '{'; *lm++ = *p++)
+               continue;
+       for (pe = ++p; *pe; pe++)
+               switch (*pe) {
+
+               case '{':
+                       brclev++;
+                       continue;
+
+               case '}':
+                       if (brclev == 0)
+                               goto pend;
+                       brclev--;
+                       continue;
+
+               case '[':
+                       for (pe++; *pe && *pe != ']'; pe++)
+                               continue;
+                       if (!*pe)
+                               yyerror("Missing ']'");
+                       continue;
+               }
+pend:
+       if (brclev || !*pe) {
+               yyerror("Missing '}'");
+               return (0);
+       }
+       for (pl = pm = p; pm <= pe; pm++)
+               switch (*pm & (QUOTE|TRIM)) {
+
+               case '{':
+                       brclev++;
+                       continue;
+
+               case '}':
+                       if (brclev) {
+                               brclev--;
+                               continue;
+                       }
+                       goto doit;
+
+               case ',':
+                       if (brclev)
+                               continue;
+doit:
+                       savec = *pm;
+                       *pm = 0;
+                       strcpy(lm, pl);
+                       strcat(restbuf, pe + 1);
+                       *pm = savec;
+                       if (s == 0) {
+                               spathp = pathp;
+                               expsh(restbuf);
+                               pathp = spathp;
+                               *pathp = 0;
+                       } else if (amatch(s, restbuf))
+                               return (1);
+                       sort();
+                       pl = pm + 1;
+                       continue;
+
+               case '[':
+                       for (pm++; *pm && *pm != ']'; pm++)
+                               continue;
+                       if (!*pm)
+                               yyerror("Missing ']'");
+                       continue;
+               }
+       return (0);
+}
+
+match(s, p)
+       char *s, *p;
+{
+       register int c;
+       register char *sentp;
+       char sexpany = expany;
+
+       if (*s == '.' && *p != '.')
+               return (0);
+       sentp = entp;
+       entp = s;
+       c = amatch(s, p);
+       entp = sentp;
+       expany = sexpany;
+       return (c);
+}
+
+amatch(s, p)
+       register char *s, *p;
+{
+       register int scc;
+       int ok, lc;
+       char *spathp;
+       struct stat stb;
+       int c, cc;
+
+       expany = 1;
+       for (;;) {
+               scc = *s++ & TRIM;
+               switch (c = *p++) {
+
+               case '{':
+                       return (execbrc(p - 1, s - 1));
+
+               case '[':
+                       ok = 0;
+                       lc = 077777;
+                       while (cc = *p++) {
+                               if (cc == ']') {
+                                       if (ok)
+                                               break;
+                                       return (0);
+                               }
+                               if (cc == '-') {
+                                       if (lc <= scc && scc <= *p++)
+                                               ok++;
+                               } else
+                                       if (scc == (lc = cc))
+                                               ok++;
+                       }
+                       if (cc == 0) {
+                               yyerror("Missing ']'");
+                               return (0);
+                       }
+                       continue;
+
+               case '*':
+                       if (!*p)
+                               return (1);
+                       if (*p == '/') {
+                               p++;
+                               goto slash;
+                       }
+                       for (s--; *s; s++)
+                               if (amatch(s, p))
+                                       return (1);
+                       return (0);
+
+               case '\0':
+                       return (scc == '\0');
+
+               default:
+                       if ((c & TRIM) != scc)
+                               return (0);
+                       continue;
+
+               case '?':
+                       if (scc == '\0')
+                               return (0);
+                       continue;
+
+               case '/':
+                       if (scc)
+                               return (0);
+slash:
+                       s = entp;
+                       spathp = pathp;
+                       while (*s)
+                               addpath(*s++);
+                       addpath('/');
+                       if (stat(path, &stb) == 0 && ISDIR(stb.st_mode))
+                               if (*p == '\0') {
+                                       if (which & E_TILDE)
+                                               Cat(path, "");
+                                       else
+                                               Cat(tilde, tpathp);
+                               } else
+                                       expsh(p);
+                       pathp = spathp;
+                       *pathp = '\0';
+                       return (0);
+               }
+       }
+}
+
+smatch(s, p)
+       register char *s, *p;
+{
+       register int scc;
+       int ok, lc;
+       int c, cc;
+
+       for (;;) {
+               scc = *s++ & TRIM;
+               switch (c = *p++) {
+
+               case '[':
+                       ok = 0;
+                       lc = 077777;
+                       while (cc = *p++) {
+                               if (cc == ']') {
+                                       if (ok)
+                                               break;
+                                       return (0);
+                               }
+                               if (cc == '-') {
+                                       if (lc <= scc && scc <= *p++)
+                                               ok++;
+                               } else
+                                       if (scc == (lc = cc))
+                                               ok++;
+                       }
+                       if (cc == 0) {
+                               yyerror("Missing ']'");
+                               return (0);
+                       }
+                       continue;
+
+               case '*':
+                       if (!*p)
+                               return (1);
+                       for (s--; *s; s++)
+                               if (smatch(s, p))
+                                       return (1);
+                       return (0);
+
+               case '\0':
+                       return (scc == '\0');
+
+               default:
+                       if ((c & TRIM) != scc)
+                               return (0);
+                       continue;
+
+               case '?':
+                       if (scc == 0)
+                               return (0);
+                       continue;
+
+               }
+       }
+}
+
+Cat(s1, s2)
+       register char *s1, *s2;
+{
+       int len = strlen(s1) + strlen(s2) + 1;
+       register char *s;
+
+       nleft -= len;
+       if (nleft <= 0 || ++eargc >= GAVSIZ)
+               yyerror("Arguments too long");
+       eargv[eargc] = 0;
+       eargv[eargc - 1] = s = malloc(len);
+       if (s == NULL)
+               fatal("ran out of memory\n");
+       while (*s++ = *s1++ & TRIM)
+               ;
+       s--;
+       while (*s++ = *s2++ & TRIM)
+               ;
+}
+
+addpath(c)
+       char c;
+{
+
+       if (pathp >= lastpathp)
+               yyerror("Pathname too long");
+       else {
+               *pathp++ = c & TRIM;
+               *pathp = '\0';
+       }
+}
+
+/*
+ * Expand file names beginning with `~' into the
+ * user's home directory path name. Return a pointer in buf to the
+ * part corresponding to `file'.
+ */
+char *
+exptilde(buf, file)
+       char buf[];
+       register char *file;
+{
+       register char *s1, *s2, *s3;
+       extern char homedir[];
+
+       if (*file != '~') {
+               strcpy(buf, file);
+               return(buf);
+       }
+       if (*++file == '\0') {
+               s2 = homedir;
+               s3 = NULL;
+       } else if (*file == '/') {
+               s2 = homedir;
+               s3 = file;
+       } else {
+               s3 = file;
+               while (*s3 && *s3 != '/')
+                       s3++;
+               if (*s3 == '/')
+                       *s3 = '\0';
+               else
+                       s3 = NULL;
+               if (pw == NULL || strcmp(pw->pw_name, file) != 0) {
+                       if ((pw = getpwnam(file)) == NULL) {
+                               error("%s: unknown user name\n", file);
+                               if (s3 != NULL)
+                                       *s3 = '/';
+                               return(NULL);
+                       }
+               }
+               if (s3 != NULL)
+                       *s3 = '/';
+               s2 = pw->pw_dir;
+       }
+       for (s1 = buf; *s1++ = *s2++; )
+               ;
+       s2 = --s1;
+       if (s3 != NULL) {
+               s2++;
+               while (*s1++ = *s3++)
+                       ;
+       }
+       return(s2);
+}
diff --git a/usr/src/usr.bin/rdist/lookup.c b/usr/src/usr.bin/rdist/lookup.c
new file mode 100644 (file)
index 0000000..6a48a54
--- /dev/null
@@ -0,0 +1,165 @@
+/*
+ * Copyright (c) 1983 Regents of the University of California.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *     This product includes software developed by the University of
+ *     California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef lint
+static char sccsid[] = "@(#)lookup.c   5.5 (Berkeley) 6/1/90";
+#endif /* not lint */
+
+#include "defs.h"
+
+       /* symbol types */
+#define VAR    1
+#define CONST  2
+
+struct syment {
+       int     s_type;
+       char    *s_name;
+       struct  namelist *s_value;
+       struct  syment *s_next;
+};
+
+static struct syment *hashtab[HASHSIZE];
+
+/*
+ * Define a variable from a command line argument.
+ */
+define(name)
+       char *name;
+{
+       register char *cp, *s;
+       register struct namelist *nl;
+       struct namelist *value;
+
+       if (debug)
+               printf("define(%s)\n", name);
+
+       cp = index(name, '=');
+       if (cp == NULL)
+               value = NULL;
+       else if (cp[1] == '\0') {
+               *cp = '\0';
+               value = NULL;
+       } else if (cp[1] != '(') {
+               *cp++ = '\0';
+               value = makenl(cp);
+       } else {
+               nl = NULL;
+               *cp++ = '\0';
+               do
+                       cp++;
+               while (*cp == ' ' || *cp == '\t');
+               for (s = cp; ; s++) {
+                       switch (*s) {
+                       case ')':
+                               *s = '\0';
+                       case '\0':
+                               break;
+                       case ' ':
+                       case '\t':
+                               *s++ = '\0';
+                               while (*s == ' ' || *s == '\t')
+                                       s++;
+                               if (*s == ')')
+                                       *s = '\0';
+                               break;
+                       default:
+                               continue;
+                       }
+                       if (nl == NULL)
+                               value = nl = makenl(cp);
+                       else {
+                               nl->n_next = makenl(cp);
+                               nl = nl->n_next;
+                       }
+                       if (*s == '\0')
+                               break;
+                       cp = s;
+               }
+       }
+       (void) lookup(name, REPLACE, value);
+}
+
+/*
+ * Lookup name in the table and return a pointer to it.
+ * LOOKUP - just do lookup, return NULL if not found.
+ * INSERT - insert name with value, error if already defined.
+ * REPLACE - insert or replace name with value.
+ */
+
+struct namelist *
+lookup(name, action, value)
+       char *name;
+       int action;
+       struct namelist *value;
+{
+       register unsigned n;
+       register char *cp;
+       register struct syment *s;
+       char buf[256];
+
+       if (debug)
+               printf("lookup(%s, %d, %x)\n", name, action, value);
+
+       n = 0;
+       for (cp = name; *cp; )
+               n += *cp++;
+       n %= HASHSIZE;
+
+       for (s = hashtab[n]; s != NULL; s = s->s_next) {
+               if (strcmp(name, s->s_name))
+                       continue;
+               if (action != LOOKUP) {
+                       if (action != INSERT || s->s_type != CONST) {
+                               (void)sprintf(buf, "%s redefined", name);
+                               yyerror(buf);
+                       }
+               }
+               return(s->s_value);
+       }
+
+       if (action == LOOKUP) {
+               (void)sprintf(buf, "%s undefined", name);
+               yyerror(buf);
+               return(NULL);
+       }
+
+       s = ALLOC(syment);
+       if (s == NULL)
+               fatal("ran out of memory\n");
+       s->s_next = hashtab[n];
+       hashtab[n] = s;
+       s->s_type = action == INSERT ? VAR : CONST;
+       s->s_name = name;
+       s->s_value = value;
+       return(value);
+}
diff --git a/usr/src/usr.bin/rdist/main.c b/usr/src/usr.bin/rdist/main.c
new file mode 100644 (file)
index 0000000..0422e17
--- /dev/null
@@ -0,0 +1,303 @@
+/*
+ * Copyright (c) 1983 Regents of the University of California.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *     This product includes software developed by the University of
+ *     California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef lint
+char copyright[] =
+"@(#) Copyright (c) 1983 Regents of the University of California.\n\
+ All rights reserved.\n";
+#endif /* not lint */
+
+#ifndef lint
+static char sccsid[] = "@(#)main.c     5.6 (Berkeley) 8/27/90";
+#endif /* not lint */
+
+#include "defs.h"
+
+#define NHOSTS 100
+
+/*
+ * Remote distribution program.
+ */
+
+char   *distfile = NULL;
+#define _RDIST_TMP     "/rdistXXXXXX"
+char   tempfile[sizeof _PATH_TMP + sizeof _RDIST_TMP + 1];
+char   *tempname;
+
+int    debug;          /* debugging flag */
+int    nflag;          /* NOP flag, just print commands without executing */
+int    qflag;          /* Quiet. Don't print messages */
+int    options;        /* global options */
+int    iamremote;      /* act as remote server for transfering files */
+
+FILE   *fin = NULL;    /* input file pointer */
+int    rem = -1;       /* file descriptor to remote source/sink process */
+char   host[32];       /* host name */
+int    nerrs;          /* number of errors while sending/receiving */
+char   user[10];       /* user's name */
+char   homedir[128];   /* user's home directory */
+int    userid;         /* user's user ID */
+int    groupid;        /* user's group ID */
+
+struct passwd *pw;     /* pointer to static area used by getpwent */
+struct group *gr;      /* pointer to static area used by getgrent */
+
+main(argc, argv)
+       int argc;
+       char *argv[];
+{
+       register char *arg;
+       int cmdargs = 0;
+       char *dhosts[NHOSTS], **hp = dhosts;
+
+       pw = getpwuid(userid = getuid());
+       if (pw == NULL) {
+               fprintf(stderr, "%s: Who are you?\n", argv[0]);
+               exit(1);
+       }
+       strcpy(user, pw->pw_name);
+       strcpy(homedir, pw->pw_dir);
+       groupid = pw->pw_gid;
+       gethostname(host, sizeof(host));
+       strcpy(tempfile, _PATH_TMP);
+       strcat(tempfile, _RDIST_TMP);
+       if ((tempname = rindex(tempfile, '/')) != 0)
+               tempname++;
+       else
+               tempname = tempfile;
+
+       while (--argc > 0) {
+               if ((arg = *++argv)[0] != '-')
+                       break;
+               if (!strcmp(arg, "-Server"))
+                       iamremote++;
+               else while (*++arg)
+                       switch (*arg) {
+                       case 'f':
+                               if (--argc <= 0)
+                                       usage();
+                               distfile = *++argv;
+                               if (distfile[0] == '-' && distfile[1] == '\0')
+                                       fin = stdin;
+                               break;
+
+                       case 'm':
+                               if (--argc <= 0)
+                                       usage();
+                               if (hp >= &dhosts[NHOSTS-2]) {
+                                       fprintf(stderr, "rdist: too many destination hosts\n");
+                                       exit(1);
+                               }
+                               *hp++ = *++argv;
+                               break;
+
+                       case 'd':
+                               if (--argc <= 0)
+                                       usage();
+                               define(*++argv);
+                               break;
+
+                       case 'D':
+                               debug++;
+                               break;
+
+                       case 'c':
+                               cmdargs++;
+                               break;
+
+                       case 'n':
+                               if (options & VERIFY) {
+                                       printf("rdist: -n overrides -v\n");
+                                       options &= ~VERIFY;
+                               }
+                               nflag++;
+                               break;
+
+                       case 'q':
+                               qflag++;
+                               break;
+
+                       case 'b':
+                               options |= COMPARE;
+                               break;
+
+                       case 'R':
+                               options |= REMOVE;
+                               break;
+
+                       case 'v':
+                               if (nflag) {
+                                       printf("rdist: -n overrides -v\n");
+                                       break;
+                               }
+                               options |= VERIFY;
+                               break;
+
+                       case 'w':
+                               options |= WHOLE;
+                               break;
+
+                       case 'y':
+                               options |= YOUNGER;
+                               break;
+
+                       case 'h':
+                               options |= FOLLOW;
+                               break;
+
+                       case 'i':
+                               options |= IGNLNKS;
+                               break;
+
+                       default:
+                               usage();
+                       }
+       }
+       *hp = NULL;
+
+       setreuid(0, userid);
+       mktemp(tempfile);
+
+       if (iamremote) {
+               server();
+               exit(nerrs != 0);
+       }
+
+       if (cmdargs)
+               docmdargs(argc, argv);
+       else {
+               if (fin == NULL) {
+                       if(distfile == NULL) {
+                               if((fin = fopen("distfile","r")) == NULL)
+                                       fin = fopen("Distfile", "r");
+                       } else
+                               fin = fopen(distfile, "r");
+                       if(fin == NULL) {
+                               perror(distfile ? distfile : "distfile");
+                               exit(1);
+                       }
+               }
+               yyparse();
+               if (nerrs == 0)
+                       docmds(dhosts, argc, argv);
+       }
+
+       exit(nerrs != 0);
+}
+
+usage()
+{
+       printf("Usage: rdist [-nqbhirvwyD] [-f distfile] [-d var=value] [-m host] [file ...]\n");
+       printf("or: rdist [-nqbhirvwyD] -c source [...] machine[:dest]\n");
+       exit(1);
+}
+
+/*
+ * rcp like interface for distributing files.
+ */
+docmdargs(nargs, args)
+       int nargs;
+       char *args[];
+{
+       register struct namelist *nl, *prev;
+       register char *cp;
+       struct namelist *files, *hosts;
+       struct subcmd *cmds;
+       char *dest;
+       static struct namelist tnl = { NULL, NULL };
+       int i;
+
+       if (nargs < 2)
+               usage();
+
+       prev = NULL;
+       for (i = 0; i < nargs - 1; i++) {
+               nl = makenl(args[i]);
+               if (prev == NULL)
+                       files = prev = nl;
+               else {
+                       prev->n_next = nl;
+                       prev = nl;
+               }
+       }
+
+       cp = args[i];
+       if ((dest = index(cp, ':')) != NULL)
+               *dest++ = '\0';
+       tnl.n_name = cp;
+       hosts = expand(&tnl, E_ALL);
+       if (nerrs)
+               exit(1);
+
+       if (dest == NULL || *dest == '\0')
+               cmds = NULL;
+       else {
+               cmds = makesubcmd(INSTALL);
+               cmds->sc_options = options;
+               cmds->sc_name = dest;
+       }
+
+       if (debug) {
+               printf("docmdargs()\nfiles = ");
+               prnames(files);
+               printf("hosts = ");
+               prnames(hosts);
+       }
+       insert(NULL, files, hosts, cmds);
+       docmds(NULL, 0, NULL);
+}
+
+/*
+ * Print a list of NAME blocks (mostly for debugging).
+ */
+prnames(nl)
+       register struct namelist *nl;
+{
+       printf("( ");
+       while (nl != NULL) {
+               printf("%s ", nl->n_name);
+               nl = nl->n_next;
+       }
+       printf(")\n");
+}
+
+/*VARARGS*/
+warn(fmt, a1, a2,a3)
+       char *fmt;
+{
+       extern int yylineno;
+
+       fprintf(stderr, "rdist: line %d: Warning: ", yylineno);
+       fprintf(stderr, fmt, a1, a2, a3);
+       fputc('\n', stderr);
+}
diff --git a/usr/src/usr.bin/rdist/pathnames.h b/usr/src/usr.bin/rdist/pathnames.h
new file mode 100644 (file)
index 0000000..7f28fc6
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 1989 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *     This product includes software developed by the University of
+ *     California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *     @(#)pathnames.h 5.4 (Berkeley) 8/27/90
+ */
+
+#include <paths.h>
+
+#define        _PATH_RDIST     "rdist"
diff --git a/usr/src/usr.bin/rdist/server.c b/usr/src/usr.bin/rdist/server.c
new file mode 100644 (file)
index 0000000..d129ab9
--- /dev/null
@@ -0,0 +1,1514 @@
+/*
+ * Copyright (c) 1983 Regents of the University of California.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *     This product includes software developed by the University of
+ *     California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef lint
+static char sccsid[] = "@(#)server.c   5.15 (Berkeley) 3/1/91";
+#endif /* not lint */
+
+#include "defs.h"
+
+#define        ack()   (void) write(rem, "\0\n", 2)
+#define        err()   (void) write(rem, "\1\n", 2)
+
+struct linkbuf *ihead;         /* list of files with more than one link */
+char   buf[BUFSIZ];            /* general purpose buffer */
+char   target[BUFSIZ];         /* target/source directory name */
+char   *tp;                    /* pointer to end of target name */
+char   *Tdest;                 /* pointer to last T dest*/
+int    catname;                /* cat name to target name */
+char   *stp[32];               /* stack of saved tp's for directories */
+int    oumask;                 /* old umask for creating files */
+
+extern FILE *lfp;              /* log file for mailing changes */
+
+void   cleanup();
+struct linkbuf *savelink();
+
+/*
+ * Server routine to read requests and process them.
+ * Commands are:
+ *     Tname   - Transmit file if out of date
+ *     Vname   - Verify if file out of date or not
+ *     Qname   - Query if file exists. Return mtime & size if it does.
+ */
+server()
+{
+       char cmdbuf[BUFSIZ];
+       register char *cp;
+
+       signal(SIGHUP, cleanup);
+       signal(SIGINT, cleanup);
+       signal(SIGQUIT, cleanup);
+       signal(SIGTERM, cleanup);
+       signal(SIGPIPE, cleanup);
+
+       rem = 0;
+       oumask = umask(0);
+       (void) sprintf(buf, "V%d\n", VERSION);
+       (void) write(rem, buf, strlen(buf));
+
+       for (;;) {
+               cp = cmdbuf;
+               if (read(rem, cp, 1) <= 0)
+                       return;
+               if (*cp++ == '\n') {
+                       error("server: expected control record\n");
+                       continue;
+               }
+               do {
+                       if (read(rem, cp, 1) != 1)
+                               cleanup();
+               } while (*cp++ != '\n' && cp < &cmdbuf[BUFSIZ]);
+               *--cp = '\0';
+               cp = cmdbuf;
+               switch (*cp++) {
+               case 'T':  /* init target file/directory name */
+                       catname = 1;    /* target should be directory */
+                       goto dotarget;
+
+               case 't':  /* init target file/directory name */
+                       catname = 0;
+               dotarget:
+                       if (exptilde(target, cp) == NULL)
+                               continue;
+                       tp = target;
+                       while (*tp)
+                               tp++;
+                       ack();
+                       continue;
+
+               case 'R':  /* Transfer a regular file. */
+                       recvf(cp, S_IFREG);
+                       continue;
+
+               case 'D':  /* Transfer a directory. */
+                       recvf(cp, S_IFDIR);
+                       continue;
+
+               case 'K':  /* Transfer symbolic link. */
+                       recvf(cp, S_IFLNK);
+                       continue;
+
+               case 'k':  /* Transfer hard link. */
+                       hardlink(cp);
+                       continue;
+
+               case 'E':  /* End. (of directory) */
+                       *tp = '\0';
+                       if (catname <= 0) {
+                               error("server: too many 'E's\n");
+                               continue;
+                       }
+                       tp = stp[--catname];
+                       *tp = '\0';
+                       ack();
+                       continue;
+
+               case 'C':  /* Clean. Cleanup a directory */
+                       clean(cp);
+                       continue;
+
+               case 'Q':  /* Query. Does the file/directory exist? */
+                       query(cp);
+                       continue;
+
+               case 'S':  /* Special. Execute commands */
+                       dospecial(cp);
+                       continue;
+
+#ifdef notdef
+               /*
+                * These entries are reserved but not currently used.
+                * The intent is to allow remote hosts to have master copies.
+                * Currently, only the host rdist runs on can have masters.
+                */
+               case 'X':  /* start a new list of files to exclude */
+                       except = bp = NULL;
+               case 'x':  /* add name to list of files to exclude */
+                       if (*cp == '\0') {
+                               ack();
+                               continue;
+                       }
+                       if (*cp == '~') {
+                               if (exptilde(buf, cp) == NULL)
+                                       continue;
+                               cp = buf;
+                       }
+                       if (bp == NULL)
+                               except = bp = expand(makeblock(NAME, cp), E_VARS);
+                       else
+                               bp->b_next = expand(makeblock(NAME, cp), E_VARS);
+                       while (bp->b_next != NULL)
+                               bp = bp->b_next;
+                       ack();
+                       continue;
+
+               case 'I':  /* Install. Transfer file if out of date. */
+                       opts = 0;
+                       while (*cp >= '0' && *cp <= '7')
+                               opts = (opts << 3) | (*cp++ - '0');
+                       if (*cp++ != ' ') {
+                               error("server: options not delimited\n");
+                               return;
+                       }
+                       install(cp, opts);
+                       continue;
+
+               case 'L':  /* Log. save message in log file */
+                       log(lfp, cp);
+                       continue;
+#endif
+
+               case '\1':
+                       nerrs++;
+                       continue;
+
+               case '\2':
+                       return;
+
+               default:
+                       error("server: unknown command '%s'\n", cp);
+               case '\0':
+                       continue;
+               }
+       }
+}
+
+/*
+ * Update the file(s) if they are different.
+ * destdir = 1 if destination should be a directory
+ * (i.e., more than one source is being copied to the same destination).
+ */
+install(src, dest, destdir, opts)
+       char *src, *dest;
+       int destdir, opts;
+{
+       char *rname;
+       char destcopy[BUFSIZ];
+
+       if (dest == NULL) {
+               opts &= ~WHOLE; /* WHOLE mode only useful if renaming */
+               dest = src;
+       }
+
+       if (nflag || debug) {
+               printf("%s%s%s%s%s %s %s\n", opts & VERIFY ? "verify":"install",
+                       opts & WHOLE ? " -w" : "",
+                       opts & YOUNGER ? " -y" : "",
+                       opts & COMPARE ? " -b" : "",
+                       opts & REMOVE ? " -R" : "", src, dest);
+               if (nflag)
+                       return;
+       }
+
+       rname = exptilde(target, src);
+       if (rname == NULL)
+               return;
+       tp = target;
+       while (*tp)
+               tp++;
+       /*
+        * If we are renaming a directory and we want to preserve
+        * the directory heirarchy (-w), we must strip off the leading
+        * directory name and preserve the rest.
+        */
+       if (opts & WHOLE) {
+               while (*rname == '/')
+                       rname++;
+               destdir = 1;
+       } else {
+               rname = rindex(target, '/');
+               if (rname == NULL)
+                       rname = target;
+               else
+                       rname++;
+       }
+       if (debug)
+               printf("target = %s, rname = %s\n", target, rname);
+       /*
+        * Pass the destination file/directory name to remote.
+        */
+       (void) sprintf(buf, "%c%s\n", destdir ? 'T' : 't', dest);
+       if (debug)
+               printf("buf = %s", buf);
+       (void) write(rem, buf, strlen(buf));
+       if (response() < 0)
+               return;
+
+       if (destdir) {
+               strcpy(destcopy, dest);
+               Tdest = destcopy;
+       }
+       sendf(rname, opts);
+       Tdest = 0;
+}
+
+#define protoname() (pw ? pw->pw_name : user)
+#define protogroup() (gr ? gr->gr_name : group)
+/*
+ * Transfer the file or directory in target[].
+ * rname is the name of the file on the remote host.
+ */
+sendf(rname, opts)
+       char *rname;
+       int opts;
+{
+       register struct subcmd *sc;
+       struct stat stb;
+       int sizerr, f, u, len;
+       off_t i;
+       DIR *d;
+       struct direct *dp;
+       char *otp, *cp;
+       extern struct subcmd *subcmds;
+       static char user[15], group[15];
+
+       if (debug)
+               printf("sendf(%s, %x)\n", rname, opts);
+
+       if (except(target))
+               return;
+       if ((opts & FOLLOW ? stat(target, &stb) : lstat(target, &stb)) < 0) {
+               error("%s: %s\n", target, strerror(errno));
+               return;
+       }
+       if ((u = update(rname, opts, &stb)) == 0) {
+               if ((stb.st_mode & S_IFMT) == S_IFREG && stb.st_nlink > 1)
+                       (void) savelink(&stb);
+               return;
+       }
+
+       if (pw == NULL || pw->pw_uid != stb.st_uid)
+               if ((pw = getpwuid(stb.st_uid)) == NULL) {
+                       log(lfp, "%s: no password entry for uid %d \n",
+                               target, stb.st_uid);
+                       pw = NULL;
+                       sprintf(user, ":%d", stb.st_uid);
+               }
+       if (gr == NULL || gr->gr_gid != stb.st_gid)
+               if ((gr = getgrgid(stb.st_gid)) == NULL) {
+                       log(lfp, "%s: no name for group %d\n",
+                               target, stb.st_gid);
+                       gr = NULL;
+                       sprintf(group, ":%d", stb.st_gid);
+               }
+       if (u == 1) {
+               if (opts & VERIFY) {
+                       log(lfp, "need to install: %s\n", target);
+                       goto dospecial;
+               }
+               log(lfp, "installing: %s\n", target);
+               opts &= ~(COMPARE|REMOVE);
+       }
+
+       switch (stb.st_mode & S_IFMT) {
+       case S_IFDIR:
+               if ((d = opendir(target)) == NULL) {
+                       error("%s: %s\n", target, strerror(errno));
+                       return;
+               }
+               (void) sprintf(buf, "D%o %04o 0 0 %s %s %s\n", opts,
+                       stb.st_mode & 07777, protoname(), protogroup(), rname);
+               if (debug)
+                       printf("buf = %s", buf);
+               (void) write(rem, buf, strlen(buf));
+               if (response() < 0) {
+                       closedir(d);
+                       return;
+               }
+
+               if (opts & REMOVE)
+                       rmchk(opts);
+
+               otp = tp;
+               len = tp - target;
+               while (dp = readdir(d)) {
+                       if (!strcmp(dp->d_name, ".") ||
+                           !strcmp(dp->d_name, ".."))
+                               continue;
+                       if (len + 1 + strlen(dp->d_name) >= BUFSIZ - 1) {
+                               error("%s/%s: Name too long\n", target,
+                                       dp->d_name);
+                               continue;
+                       }
+                       tp = otp;
+                       *tp++ = '/';
+                       cp = dp->d_name;
+                       while (*tp++ = *cp++)
+                               ;
+                       tp--;
+                       sendf(dp->d_name, opts);
+               }
+               closedir(d);
+               (void) write(rem, "E\n", 2);
+               (void) response();
+               tp = otp;
+               *tp = '\0';
+               return;
+
+       case S_IFLNK:
+               if (u != 1)
+                       opts |= COMPARE;
+               if (stb.st_nlink > 1) {
+                       struct linkbuf *lp;
+
+                       if ((lp = savelink(&stb)) != NULL) {
+                               /* install link */
+                               if (*lp->target == 0)
+                               (void) sprintf(buf, "k%o %s %s\n", opts,
+                                       lp->pathname, rname);
+                               else
+                               (void) sprintf(buf, "k%o %s/%s %s\n", opts,
+                                       lp->target, lp->pathname, rname);
+                               if (debug)
+                                       printf("buf = %s", buf);
+                               (void) write(rem, buf, strlen(buf));
+                               (void) response();
+                               return;
+                       }
+               }
+               (void) sprintf(buf, "K%o %o %ld %ld %s %s %s\n", opts,
+                       stb.st_mode & 07777, stb.st_size, stb.st_mtime,
+                       protoname(), protogroup(), rname);
+               if (debug)
+                       printf("buf = %s", buf);
+               (void) write(rem, buf, strlen(buf));
+               if (response() < 0)
+                       return;
+               sizerr = (readlink(target, buf, BUFSIZ) != stb.st_size);
+               (void) write(rem, buf, stb.st_size);
+               if (debug)
+                       printf("readlink = %.*s\n", (int)stb.st_size, buf);
+               goto done;
+
+       case S_IFREG:
+               break;
+
+       default:
+               error("%s: not a file or directory\n", target);
+               return;
+       }
+
+       if (u == 2) {
+               if (opts & VERIFY) {
+                       log(lfp, "need to update: %s\n", target);
+                       goto dospecial;
+               }
+               log(lfp, "updating: %s\n", target);
+       }
+
+       if (stb.st_nlink > 1) {
+               struct linkbuf *lp;
+
+               if ((lp = savelink(&stb)) != NULL) {
+                       /* install link */
+                       if (*lp->target == 0)
+                       (void) sprintf(buf, "k%o %s %s\n", opts,
+                               lp->pathname, rname);
+                       else
+                       (void) sprintf(buf, "k%o %s/%s %s\n", opts,
+                               lp->target, lp->pathname, rname);
+                       if (debug)
+                               printf("buf = %s", buf);
+                       (void) write(rem, buf, strlen(buf));
+                       (void) response();
+                       return;
+               }
+       }
+
+       if ((f = open(target, 0)) < 0) {
+               error("%s: %s\n", target, strerror(errno));
+               return;
+       }
+       (void) sprintf(buf, "R%o %o %ld %ld %s %s %s\n", opts,
+               stb.st_mode & 07777, stb.st_size, stb.st_mtime,
+               protoname(), protogroup(), rname);
+       if (debug)
+               printf("buf = %s", buf);
+       (void) write(rem, buf, strlen(buf));
+       if (response() < 0) {
+               (void) close(f);
+               return;
+       }
+       sizerr = 0;
+       for (i = 0; i < stb.st_size; i += BUFSIZ) {
+               int amt = BUFSIZ;
+               if (i + amt > stb.st_size)
+                       amt = stb.st_size - i;
+               if (sizerr == 0 && read(f, buf, amt) != amt)
+                       sizerr = 1;
+               (void) write(rem, buf, amt);
+       }
+       (void) close(f);
+done:
+       if (sizerr) {
+               error("%s: file changed size\n", target);
+               err();
+       } else
+               ack();
+       f = response();
+       if (f < 0 || f == 0 && (opts & COMPARE))
+               return;
+dospecial:
+       for (sc = subcmds; sc != NULL; sc = sc->sc_next) {
+               if (sc->sc_type != SPECIAL)
+                       continue;
+               if (sc->sc_args != NULL && !inlist(sc->sc_args, target))
+                       continue;
+               log(lfp, "special \"%s\"\n", sc->sc_name);
+               if (opts & VERIFY)
+                       continue;
+               (void) sprintf(buf, "SFILE=%s;%s\n", target, sc->sc_name);
+               if (debug)
+                       printf("buf = %s", buf);
+               (void) write(rem, buf, strlen(buf));
+               while (response() > 0)
+                       ;
+       }
+}
+
+struct linkbuf *
+savelink(stp)
+       struct stat *stp;
+{
+       struct linkbuf *lp;
+       int found = 0;
+
+       for (lp = ihead; lp != NULL; lp = lp->nextp)
+               if (lp->inum == stp->st_ino && lp->devnum == stp->st_dev) {
+                       lp->count--;
+                       return(lp);
+               }
+       lp = (struct linkbuf *) malloc(sizeof(*lp));
+       if (lp == NULL)
+               log(lfp, "out of memory, link information lost\n");
+       else {
+               lp->nextp = ihead;
+               ihead = lp;
+               lp->inum = stp->st_ino;
+               lp->devnum = stp->st_dev;
+               lp->count = stp->st_nlink - 1;
+               strcpy(lp->pathname, target);
+               if (Tdest)
+                       strcpy(lp->target, Tdest);
+               else
+                       *lp->target = 0;
+       }
+       return(NULL);
+}
+
+/*
+ * Check to see if file needs to be updated on the remote machine.
+ * Returns 0 if no update, 1 if remote doesn't exist, 2 if out of date
+ * and 3 if comparing binaries to determine if out of date.
+ */
+update(rname, opts, stp)
+       char *rname;
+       int opts;
+       struct stat *stp;
+{
+       register char *cp, *s;
+       register off_t size;
+       register time_t mtime;
+
+       if (debug) 
+               printf("update(%s, %x, %x)\n", rname, opts, stp);
+
+       /*
+        * Check to see if the file exists on the remote machine.
+        */
+       (void) sprintf(buf, "Q%s\n", rname);
+       if (debug)
+               printf("buf = %s", buf);
+       (void) write(rem, buf, strlen(buf));
+again:
+       cp = s = buf;
+       do {
+               if (read(rem, cp, 1) != 1)
+                       lostconn();
+       } while (*cp++ != '\n' && cp < &buf[BUFSIZ]);
+
+       switch (*s++) {
+       case 'Y':
+               break;
+
+       case 'N':  /* file doesn't exist so install it */
+               return(1);
+
+       case '\1':
+               nerrs++;
+               if (*s != '\n') {
+                       if (!iamremote) {
+                               fflush(stdout);
+                               (void) write(2, s, cp - s);
+                       }
+                       if (lfp != NULL)
+                               (void) fwrite(s, 1, cp - s, lfp);
+               }
+               return(0);
+
+       case '\3':
+               *--cp = '\0';
+               if (lfp != NULL) 
+                       log(lfp, "update: note: %s\n", s);
+               goto again;
+
+       default:
+               *--cp = '\0';
+               error("update: unexpected response '%s'\n", s);
+               return(0);
+       }
+
+       if (*s == '\n')
+               return(2);
+
+       if (opts & COMPARE)
+               return(3);
+
+       size = 0;
+       while (isdigit(*s))
+               size = size * 10 + (*s++ - '0');
+       if (*s++ != ' ') {
+               error("update: size not delimited\n");
+               return(0);
+       }
+       mtime = 0;
+       while (isdigit(*s))
+               mtime = mtime * 10 + (*s++ - '0');
+       if (*s != '\n') {
+               error("update: mtime not delimited\n");
+               return(0);
+       }
+       /*
+        * File needs to be updated?
+        */
+       if (opts & YOUNGER) {
+               if (stp->st_mtime == mtime)
+                       return(0);
+               if (stp->st_mtime < mtime) {
+                       log(lfp, "Warning: %s: remote copy is newer\n", target);
+                       return(0);
+               }
+       } else if (stp->st_mtime == mtime && stp->st_size == size)
+               return(0);
+       return(2);
+}
+
+/*
+ * Query. Check to see if file exists. Return one of the following:
+ *     N\n             - doesn't exist
+ *     Ysize mtime\n   - exists and its a regular file (size & mtime of file)
+ *     Y\n             - exists and its a directory or symbolic link
+ *     ^Aerror message\n
+ */
+query(name)
+       char *name;
+{
+       struct stat stb;
+
+       if (catname)
+               (void) sprintf(tp, "/%s", name);
+
+       if (lstat(target, &stb) < 0) {
+               if (errno == ENOENT)
+                       (void) write(rem, "N\n", 2);
+               else
+                       error("%s:%s: %s\n", host, target, strerror(errno));
+               *tp = '\0';
+               return;
+       }
+
+       switch (stb.st_mode & S_IFMT) {
+       case S_IFREG:
+               (void) sprintf(buf, "Y%ld %ld\n", stb.st_size, stb.st_mtime);
+               (void) write(rem, buf, strlen(buf));
+               break;
+
+       case S_IFLNK:
+       case S_IFDIR:
+               (void) write(rem, "Y\n", 2);
+               break;
+
+       default:
+               error("%s: not a file or directory\n", name);
+               break;
+       }
+       *tp = '\0';
+}
+
+recvf(cmd, type)
+       char *cmd;
+       int type;
+{
+       register char *cp;
+       int f, mode, opts, wrerr, olderrno;
+       off_t i, size;
+       time_t mtime;
+       struct stat stb;
+       struct timeval tvp[2];
+       char *owner, *group;
+       char new[BUFSIZ];
+       extern char *tempname;
+
+       cp = cmd;
+       opts = 0;
+       while (*cp >= '0' && *cp <= '7')
+               opts = (opts << 3) | (*cp++ - '0');
+       if (*cp++ != ' ') {
+               error("recvf: options not delimited\n");
+               return;
+       }
+       mode = 0;
+       while (*cp >= '0' && *cp <= '7')
+               mode = (mode << 3) | (*cp++ - '0');
+       if (*cp++ != ' ') {
+               error("recvf: mode not delimited\n");
+               return;
+       }
+       size = 0;
+       while (isdigit(*cp))
+               size = size * 10 + (*cp++ - '0');
+       if (*cp++ != ' ') {
+               error("recvf: size not delimited\n");
+               return;
+       }
+       mtime = 0;
+       while (isdigit(*cp))
+               mtime = mtime * 10 + (*cp++ - '0');
+       if (*cp++ != ' ') {
+               error("recvf: mtime not delimited\n");
+               return;
+       }
+       owner = cp;
+       while (*cp && *cp != ' ')
+               cp++;
+       if (*cp != ' ') {
+               error("recvf: owner name not delimited\n");
+               return;
+       }
+       *cp++ = '\0';
+       group = cp;
+       while (*cp && *cp != ' ')
+               cp++;
+       if (*cp != ' ') {
+               error("recvf: group name not delimited\n");
+               return;
+       }
+       *cp++ = '\0';
+
+       if (type == S_IFDIR) {
+               if (catname >= sizeof(stp)) {
+                       error("%s:%s: too many directory levels\n",
+                               host, target);
+                       return;
+               }
+               stp[catname] = tp;
+               if (catname++) {
+                       *tp++ = '/';
+                       while (*tp++ = *cp++)
+                               ;
+                       tp--;
+               }
+               if (opts & VERIFY) {
+                       ack();
+                       return;
+               }
+               if (lstat(target, &stb) == 0) {
+                       if (ISDIR(stb.st_mode)) {
+                               if ((stb.st_mode & 07777) == mode) {
+                                       ack();
+                                       return;
+                               }
+                               buf[0] = '\0';
+                               (void) sprintf(buf + 1,
+                                       "%s: Warning: remote mode %o != local mode %o\n",
+                                       target, stb.st_mode & 07777, mode);
+                               (void) write(rem, buf, strlen(buf + 1) + 1);
+                               return;
+                       }
+                       errno = ENOTDIR;
+               } else if (errno == ENOENT && (mkdir(target, mode) == 0 ||
+                   chkparent(target) == 0 && mkdir(target, mode) == 0)) {
+                       if (chog(target, owner, group, mode) == 0)
+                               ack();
+                       return;
+               }
+               error("%s:%s: %s\n", host, target, strerror(errno));
+               tp = stp[--catname];
+               *tp = '\0';
+               return;
+       }
+
+       if (catname)
+               (void) sprintf(tp, "/%s", cp);
+       cp = rindex(target, '/');
+       if (cp == NULL)
+               strcpy(new, tempname);
+       else if (cp == target)
+               (void) sprintf(new, "/%s", tempname);
+       else {
+               *cp = '\0';
+               (void) sprintf(new, "%s/%s", target, tempname);
+               *cp = '/';
+       }
+
+       if (type == S_IFLNK) {
+               int j;
+
+               ack();
+               cp = buf;
+               for (i = 0; i < size; i += j) {
+                       if ((j = read(rem, cp, size - i)) <= 0)
+                               cleanup();
+                       cp += j;
+               }
+               *cp = '\0';
+               if (response() < 0) {
+                       err();
+                       return;
+               }
+               if (symlink(buf, new) < 0) {
+                       if (errno != ENOENT || chkparent(new) < 0 ||
+                           symlink(buf, new) < 0)
+                               goto badn;
+               }
+               mode &= 0777;
+               if (opts & COMPARE) {
+                       char tbuf[BUFSIZ];
+
+                       if ((i = readlink(target, tbuf, BUFSIZ)) >= 0 &&
+                           i == size && strncmp(buf, tbuf, size) == 0) {
+                               (void) unlink(new);
+                               ack();
+                               return;
+                       }
+                       if (opts & VERIFY)
+                               goto differ;
+               }
+               goto fixup;
+       }
+
+       if ((f = creat(new, mode)) < 0) {
+               if (errno != ENOENT || chkparent(new) < 0 ||
+                   (f = creat(new, mode)) < 0)
+                       goto badn;
+       }
+
+       ack();
+       wrerr = 0;
+       for (i = 0; i < size; i += BUFSIZ) {
+               int amt = BUFSIZ;
+
+               cp = buf;
+               if (i + amt > size)
+                       amt = size - i;
+               do {
+                       int j = read(rem, cp, amt);
+
+                       if (j <= 0) {
+                               (void) close(f);
+                               (void) unlink(new);
+                               cleanup();
+                       }
+                       amt -= j;
+                       cp += j;
+               } while (amt > 0);
+               amt = BUFSIZ;
+               if (i + amt > size)
+                       amt = size - i;
+               if (wrerr == 0 && write(f, buf, amt) != amt) {
+                       olderrno = errno;
+                       wrerr++;
+               }
+       }
+       (void) close(f);
+       if (response() < 0) {
+               err();
+               (void) unlink(new);
+               return;
+       }
+       if (wrerr) {
+               error("%s:%s: %s\n", host, new, strerror(errno));
+               (void) unlink(new);
+               return;
+       }
+       if (opts & COMPARE) {
+               FILE *f1, *f2;
+               int c;
+
+               if ((f1 = fopen(target, "r")) == NULL)
+                       goto badt;
+               if ((f2 = fopen(new, "r")) == NULL) {
+               badn:
+                       error("%s:%s: %s\n", host, new, strerror(errno));
+                       (void) unlink(new);
+                       return;
+               }
+               while ((c = getc(f1)) == getc(f2))
+                       if (c == EOF) {
+                               (void) fclose(f1);
+                               (void) fclose(f2);
+                               (void) unlink(new);
+                               ack();
+                               return;
+                       }
+               (void) fclose(f1);
+               (void) fclose(f2);
+               if (opts & VERIFY) {
+               differ:
+                       (void) unlink(new);
+                       buf[0] = '\0';
+                       (void) sprintf(buf + 1, "need to update: %s\n",target);
+                       (void) write(rem, buf, strlen(buf + 1) + 1);
+                       return;
+               }
+       }
+
+       /*
+        * Set last modified time
+        */
+       tvp[0].tv_sec = stb.st_atime;   /* old atime from target */
+       tvp[0].tv_usec = 0;
+       tvp[1].tv_sec = mtime;
+       tvp[1].tv_usec = 0;
+       if (utimes(new, tvp) < 0) {
+               note("%s:utimes failed %s: %s\n", host, new, strerror(errno));
+       }
+       if (chog(new, owner, group, mode) < 0) {
+               (void) unlink(new);
+               return;
+       }
+fixup:
+       if (rename(new, target) < 0) {
+badt:
+               error("%s:%s: %s\n", host, target, strerror(errno));
+               (void) unlink(new);
+               return;
+       }
+       if (opts & COMPARE) {
+               buf[0] = '\0';
+               (void) sprintf(buf + 1, "updated %s\n", target);
+               (void) write(rem, buf, strlen(buf + 1) + 1);
+       } else
+               ack();
+}
+
+/*
+ * Creat a hard link to existing file.
+ */
+hardlink(cmd)
+       char *cmd;
+{
+       register char *cp;
+       struct stat stb;
+       char *oldname;
+       int opts, exists = 0;
+
+       cp = cmd;
+       opts = 0;
+       while (*cp >= '0' && *cp <= '7')
+               opts = (opts << 3) | (*cp++ - '0');
+       if (*cp++ != ' ') {
+               error("hardlink: options not delimited\n");
+               return;
+       }
+       oldname = cp;
+       while (*cp && *cp != ' ')
+               cp++;
+       if (*cp != ' ') {
+               error("hardlink: oldname name not delimited\n");
+               return;
+       }
+       *cp++ = '\0';
+
+       if (catname) {
+               (void) sprintf(tp, "/%s", cp);
+       }
+       if (lstat(target, &stb) == 0) {
+               int mode = stb.st_mode & S_IFMT;
+               if (mode != S_IFREG && mode != S_IFLNK) {
+                       error("%s:%s: not a regular file\n", host, target);
+                       return;
+               }
+               exists = 1;
+       }
+       if (chkparent(target) < 0 ) {
+               error("%s:%s: %s (no parent)\n",
+                       host, target, strerror(errno));
+               return;
+       }
+       if (exists && (unlink(target) < 0)) {
+               error("%s:%s: %s (unlink)\n",
+                       host, target, strerror(errno));
+               return;
+       }
+       if (link(oldname, target) < 0) {
+               error("%s:can't link %s to %s\n",
+                       host, target, oldname);
+               return;
+       }
+       ack();
+}
+
+/*
+ * Check to see if parent directory exists and create one if not.
+ */
+chkparent(name)
+       char *name;
+{
+       register char *cp;
+       struct stat stb;
+
+       cp = rindex(name, '/');
+       if (cp == NULL || cp == name)
+               return(0);
+       *cp = '\0';
+       if (lstat(name, &stb) < 0) {
+               if (errno == ENOENT && chkparent(name) >= 0 &&
+                   mkdir(name, 0777 & ~oumask) >= 0) {
+                       *cp = '/';
+                       return(0);
+               }
+       } else if (ISDIR(stb.st_mode)) {
+               *cp = '/';
+               return(0);
+       }
+       *cp = '/';
+       return(-1);
+}
+
+/*
+ * Change owner, group and mode of file.
+ */
+chog(file, owner, group, mode)
+       char *file, *owner, *group;
+       int mode;
+{
+       register int i;
+       int uid, gid;
+       extern char user[];
+       extern int userid;
+
+       uid = userid;
+       if (userid == 0) {
+               if (*owner == ':') {
+                       uid = atoi(owner + 1);
+               } else if (pw == NULL || strcmp(owner, pw->pw_name) != 0) {
+                       if ((pw = getpwnam(owner)) == NULL) {
+                               if (mode & 04000) {
+                                       note("%s:%s: unknown login name, clearing setuid",
+                                               host, owner);
+                                       mode &= ~04000;
+                                       uid = 0;
+                               }
+                       } else
+                               uid = pw->pw_uid;
+               } else
+                       uid = pw->pw_uid;
+               if (*group == ':') {
+                       gid = atoi(group + 1);
+                       goto ok;
+               }
+       } else if ((mode & 04000) && strcmp(user, owner) != 0)
+               mode &= ~04000;
+       gid = -1;
+       if (gr == NULL || strcmp(group, gr->gr_name) != 0) {
+               if ((*group == ':' && (getgrgid(gid = atoi(group + 1)) == NULL))
+                  || ((gr = getgrnam(group)) == NULL)) {
+                       if (mode & 02000) {
+                               note("%s:%s: unknown group", host, group);
+                               mode &= ~02000;
+                       }
+               } else
+                       gid = gr->gr_gid;
+       } else
+               gid = gr->gr_gid;
+       if (userid && gid >= 0) {
+               if (gr) for (i = 0; gr->gr_mem[i] != NULL; i++)
+                       if (!(strcmp(user, gr->gr_mem[i])))
+                               goto ok;
+               mode &= ~02000;
+               gid = -1;
+       }
+ok:
+       if (userid)
+               setreuid(userid, 0);
+       if (chown(file, uid, gid) < 0 ||
+           (mode & 07000) && chmod(file, mode) < 0) {
+               note("%s: chown or chmod failed: file %s:  %s",
+                            host, file, strerror(errno));
+       }
+       if (userid)
+               setreuid(0, userid);
+       return(0);
+}
+
+/*
+ * Check for files on the machine being updated that are not on the master
+ * machine and remove them.
+ */
+rmchk(opts)
+       int opts;
+{
+       register char *cp, *s;
+       struct stat stb;
+
+       if (debug)
+               printf("rmchk()\n");
+
+       /*
+        * Tell the remote to clean the files from the last directory sent.
+        */
+       (void) sprintf(buf, "C%o\n", opts & VERIFY);
+       if (debug)
+               printf("buf = %s", buf);
+       (void) write(rem, buf, strlen(buf));
+       if (response() < 0)
+               return;
+       for (;;) {
+               cp = s = buf;
+               do {
+                       if (read(rem, cp, 1) != 1)
+                               lostconn();
+               } while (*cp++ != '\n' && cp < &buf[BUFSIZ]);
+
+               switch (*s++) {
+               case 'Q': /* Query if file should be removed */
+                       /*
+                        * Return the following codes to remove query.
+                        * N\n -- file exists - DON'T remove.
+                        * Y\n -- file doesn't exist - REMOVE.
+                        */
+                       *--cp = '\0';
+                       (void) sprintf(tp, "/%s", s);
+                       if (debug)
+                               printf("check %s\n", target);
+                       if (except(target))
+                               (void) write(rem, "N\n", 2);
+                       else if (lstat(target, &stb) < 0)
+                               (void) write(rem, "Y\n", 2);
+                       else
+                               (void) write(rem, "N\n", 2);
+                       break;
+
+               case '\0':
+                       *--cp = '\0';
+                       if (*s != '\0')
+                               log(lfp, "%s\n", s);
+                       break;
+
+               case 'E':
+                       *tp = '\0';
+                       ack();
+                       return;
+
+               case '\1':
+               case '\2':
+                       nerrs++;
+                       if (*s != '\n') {
+                               if (!iamremote) {
+                                       fflush(stdout);
+                                       (void) write(2, s, cp - s);
+                               }
+                               if (lfp != NULL)
+                                       (void) fwrite(s, 1, cp - s, lfp);
+                       }
+                       if (buf[0] == '\2')
+                               lostconn();
+                       break;
+
+               default:
+                       error("rmchk: unexpected response '%s'\n", buf);
+                       err();
+               }
+       }
+}
+
+/*
+ * Check the current directory (initialized by the 'T' command to server())
+ * for extraneous files and remove them.
+ */
+clean(cp)
+       register char *cp;
+{
+       DIR *d;
+       register struct direct *dp;
+       struct stat stb;
+       char *otp;
+       int len, opts;
+
+       opts = 0;
+       while (*cp >= '0' && *cp <= '7')
+               opts = (opts << 3) | (*cp++ - '0');
+       if (*cp != '\0') {
+               error("clean: options not delimited\n");
+               return;
+       }
+       if ((d = opendir(target)) == NULL) {
+               error("%s:%s: %s\n", host, target, strerror(errno));
+               return;
+       }
+       ack();
+
+       otp = tp;
+       len = tp - target;
+       while (dp = readdir(d)) {
+               if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, ".."))
+                       continue;
+               if (len + 1 + strlen(dp->d_name) >= BUFSIZ - 1) {
+                       error("%s:%s/%s: Name too long\n",
+                               host, target, dp->d_name);
+                       continue;
+               }
+               tp = otp;
+               *tp++ = '/';
+               cp = dp->d_name;;
+               while (*tp++ = *cp++)
+                       ;
+               tp--;
+               if (lstat(target, &stb) < 0) {
+                       error("%s:%s: %s\n", host, target, strerror(errno));
+                       continue;
+               }
+               (void) sprintf(buf, "Q%s\n", dp->d_name);
+               (void) write(rem, buf, strlen(buf));
+               cp = buf;
+               do {
+                       if (read(rem, cp, 1) != 1)
+                               cleanup();
+               } while (*cp++ != '\n' && cp < &buf[BUFSIZ]);
+               *--cp = '\0';
+               cp = buf;
+               if (*cp != 'Y')
+                       continue;
+               if (opts & VERIFY) {
+                       cp = buf;
+                       *cp++ = '\0';
+                       (void) sprintf(cp, "need to remove: %s\n", target);
+                       (void) write(rem, buf, strlen(cp) + 1);
+               } else
+                       removeit(&stb);
+       }
+       closedir(d);
+       (void) write(rem, "E\n", 2);
+       (void) response();
+       tp = otp;
+       *tp = '\0';
+}
+
+/*
+ * Remove a file or directory (recursively) and send back an acknowledge
+ * or an error message.
+ */
+removeit(stp)
+       struct stat *stp;
+{
+       DIR *d;
+       struct direct *dp;
+       register char *cp;
+       struct stat stb;
+       char *otp;
+       int len;
+
+       switch (stp->st_mode & S_IFMT) {
+       case S_IFREG:
+       case S_IFLNK:
+               if (unlink(target) < 0)
+                       goto bad;
+               goto removed;
+
+       case S_IFDIR:
+               break;
+
+       default:
+               error("%s:%s: not a plain file\n", host, target);
+               return;
+       }
+
+       if ((d = opendir(target)) == NULL)
+               goto bad;
+
+       otp = tp;
+       len = tp - target;
+       while (dp = readdir(d)) {
+               if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, ".."))
+                       continue;
+               if (len + 1 + strlen(dp->d_name) >= BUFSIZ - 1) {
+                       error("%s:%s/%s: Name too long\n",
+                               host, target, dp->d_name);
+                       continue;
+               }
+               tp = otp;
+               *tp++ = '/';
+               cp = dp->d_name;;
+               while (*tp++ = *cp++)
+                       ;
+               tp--;
+               if (lstat(target, &stb) < 0) {
+                       error("%s:%s: %s\n", host, target, strerror(errno));
+                       continue;
+               }
+               removeit(&stb);
+       }
+       closedir(d);
+       tp = otp;
+       *tp = '\0';
+       if (rmdir(target) < 0) {
+bad:
+               error("%s:%s: %s\n", host, target, strerror(errno));
+               return;
+       }
+removed:
+       cp = buf;
+       *cp++ = '\0';
+       (void) sprintf(cp, "removed %s\n", target);
+       (void) write(rem, buf, strlen(cp) + 1);
+}
+
+/*
+ * Execute a shell command to handle special cases.
+ */
+dospecial(cmd)
+       char *cmd;
+{
+       int fd[2], status, pid, i;
+       register char *cp, *s;
+       char sbuf[BUFSIZ];
+       extern int userid, groupid;
+
+       if (pipe(fd) < 0) {
+               error("%s\n", strerror(errno));
+               return;
+       }
+       if ((pid = fork()) == 0) {
+               /*
+                * Return everything the shell commands print.
+                */
+               (void) close(0);
+               (void) close(1);
+               (void) close(2);
+               (void) open(_PATH_DEVNULL, O_RDONLY);
+               (void) dup(fd[1]);
+               (void) dup(fd[1]);
+               (void) close(fd[0]);
+               (void) close(fd[1]);
+               setgid(groupid);
+               setuid(userid);
+               execl(_PATH_BSHELL, "sh", "-c", cmd, 0);
+               _exit(127);
+       }
+       (void) close(fd[1]);
+       s = sbuf;
+       *s++ = '\0';
+       while ((i = read(fd[0], buf, sizeof(buf))) > 0) {
+               cp = buf;
+               do {
+                       *s++ = *cp++;
+                       if (cp[-1] != '\n') {
+                               if (s < &sbuf[sizeof(sbuf)-1])
+                                       continue;
+                               *s++ = '\n';
+                       }
+                       /*
+                        * Throw away blank lines.
+                        */
+                       if (s == &sbuf[2]) {
+                               s--;
+                               continue;
+                       }
+                       (void) write(rem, sbuf, s - sbuf);
+                       s = &sbuf[1];
+               } while (--i);
+       }
+       if (s > &sbuf[1]) {
+               *s++ = '\n';
+               (void) write(rem, sbuf, s - sbuf);
+       }
+       while ((i = wait(&status)) != pid && i != -1)
+               ;
+       if (i == -1)
+               status = -1;
+       (void) close(fd[0]);
+       if (status)
+               error("shell returned %d\n", status);
+       else
+               ack();
+}
+
+/*VARARGS2*/
+log(fp, fmt, a1, a2, a3)
+       FILE *fp;
+       char *fmt;
+       int a1, a2, a3;
+{
+       /* Print changes locally if not quiet mode */
+       if (!qflag)
+               printf(fmt, a1, a2, a3);
+
+       /* Save changes (for mailing) if really updating files */
+       if (!(options & VERIFY) && fp != NULL)
+               fprintf(fp, fmt, a1, a2, a3);
+}
+
+/*VARARGS1*/
+error(fmt, a1, a2, a3)
+       char *fmt;
+       int a1, a2, a3;
+{
+       static FILE *fp;
+
+       ++nerrs;
+       if (!fp && !(fp = fdopen(rem, "w")))
+               return;
+       if (iamremote) {
+               (void)fprintf(fp, "%crdist: ", 0x01);
+               (void)fprintf(fp, fmt, a1, a2, a3);
+               fflush(fp);
+       }
+       else {
+               fflush(stdout);
+               (void)fprintf(stderr, "rdist: ");
+               (void)fprintf(stderr, fmt, a1, a2, a3);
+               fflush(stderr);
+       }
+       if (lfp != NULL) {
+               (void)fprintf(lfp, "rdist: ");
+               (void)fprintf(lfp, fmt, a1, a2, a3);
+               fflush(lfp);
+       }
+}
+
+/*VARARGS1*/
+fatal(fmt, a1, a2,a3)
+       char *fmt;
+       int a1, a2, a3;
+{
+       static FILE *fp;
+
+       ++nerrs;
+       if (!fp && !(fp = fdopen(rem, "w")))
+               return;
+       if (iamremote) {
+               (void)fprintf(fp, "%crdist: ", 0x02);
+               (void)fprintf(fp, fmt, a1, a2, a3);
+               fflush(fp);
+       }
+       else {
+               fflush(stdout);
+               (void)fprintf(stderr, "rdist: ");
+               (void)fprintf(stderr, fmt, a1, a2, a3);
+               fflush(stderr);
+       }
+       if (lfp != NULL) {
+               (void)fprintf(lfp, "rdist: ");
+               (void)fprintf(lfp, fmt, a1, a2, a3);
+               fflush(lfp);
+       }
+       cleanup();
+}
+
+response()
+{
+       char *cp, *s;
+       char resp[BUFSIZ];
+
+       if (debug)
+               printf("response()\n");
+
+       cp = s = resp;
+       do {
+               if (read(rem, cp, 1) != 1)
+                       lostconn();
+       } while (*cp++ != '\n' && cp < &resp[BUFSIZ]);
+
+       switch (*s++) {
+       case '\0':
+               *--cp = '\0';
+               if (*s != '\0') {
+                       log(lfp, "%s\n", s);
+                       return(1);
+               }
+               return(0);
+       case '\3':
+               *--cp = '\0';
+               log(lfp, "Note: %s\n",s);
+               return(response());
+
+       default:
+               s--;
+               /* fall into... */
+       case '\1':
+       case '\2':
+               nerrs++;
+               if (*s != '\n') {
+                       if (!iamremote) {
+                               fflush(stdout);
+                               (void) write(2, s, cp - s);
+                       }
+                       if (lfp != NULL)
+                               (void) fwrite(s, 1, cp - s, lfp);
+               }
+               if (resp[0] == '\2')
+                       lostconn();
+               return(-1);
+       }
+}
+
+/*
+ * Remove temporary files and do any cleanup operations before exiting.
+ */
+void
+cleanup()
+{
+       (void) unlink(tempfile);
+       exit(1);
+}
+
+note(fmt, a1, a2, a3)
+       char *fmt;
+       int a1, a2, a3;
+{
+       static char buf[BUFSIZ];
+       sprintf(buf, fmt, a1, a2, a3);
+       comment(buf);
+}
+
+comment(s)
+char *s;
+{
+       char c = '\3';
+       write(rem, &c, 1);
+       write(rem, s, strlen(s));
+       c = '\n';
+       write(rem, &c, 1);
+}