don't redefine _PATH_TMP; don't assume it begins "/tmp/";
[unix-history] / usr / src / usr.bin / rdist / server.c
index a8d28aa..534bf09 100644 (file)
@@ -1,21 +1,32 @@
+/*
+ * Copyright (c) 1983 Regents of the University of California.
+ * All rights reserved.
+ *
+ * %sccs.include.redist.c%
+ */
+
 #ifndef lint
 #ifndef lint
-static char *sccsid = "@(#)server.c    4.7 (Berkeley) 83/11/01";
-#endif
+static char sccsid[] = "@(#)server.c   5.13 (Berkeley) %G%";
+#endif /* not lint */
 
 #include "defs.h"
 
 
 #include "defs.h"
 
-#define        ga()    (void) write(rem, "\0\n", 2)
+#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   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 */
 
 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 */
 
-extern char *exptilde();
+int    cleanup();
+struct linkbuf *savelink();
 
 /*
  * Server routine to read requests and process them.
 
 /*
  * Server routine to read requests and process them.
@@ -28,11 +39,17 @@ server()
 {
        char cmdbuf[BUFSIZ];
        register char *cp;
 {
        char cmdbuf[BUFSIZ];
        register char *cp;
-       register struct block *bp = NULL;
-       int opts;
 
 
+       signal(SIGHUP, cleanup);
+       signal(SIGINT, cleanup);
+       signal(SIGQUIT, cleanup);
+       signal(SIGTERM, cleanup);
+       signal(SIGPIPE, cleanup);
+
+       rem = 0;
        oumask = umask(0);
        oumask = umask(0);
-       ga();
+       (void) sprintf(buf, "V%d\n", VERSION);
+       (void) write(rem, buf, strlen(buf));
 
        for (;;) {
                cp = cmdbuf;
 
        for (;;) {
                cp = cmdbuf;
@@ -44,7 +61,7 @@ server()
                }
                do {
                        if (read(rem, cp, 1) != 1)
                }
                do {
                        if (read(rem, cp, 1) != 1)
-                               lostconn();
+                               cleanup();
                } while (*cp++ != '\n' && cp < &cmdbuf[BUFSIZ]);
                *--cp = '\0';
                cp = cmdbuf;
                } while (*cp++ != '\n' && cp < &cmdbuf[BUFSIZ]);
                *--cp = '\0';
                cp = cmdbuf;
@@ -61,15 +78,23 @@ server()
                        tp = target;
                        while (*tp)
                                tp++;
                        tp = target;
                        while (*tp)
                                tp++;
-                       ga();
+                       ack();
+                       continue;
+
+               case 'R':  /* Transfer a regular file. */
+                       recvf(cp, S_IFREG);
                        continue;
 
                        continue;
 
-               case 'R':  /* Receive. Transfer file. */
-                       recvf(cp, 0);
+               case 'D':  /* Transfer a directory. */
+                       recvf(cp, S_IFDIR);
                        continue;
 
                        continue;
 
-               case 'D':  /* Directory. Transfer file. */
-                       recvf(cp, 1);
+               case 'K':  /* Transfer symbolic link. */
+                       recvf(cp, S_IFLNK);
+                       continue;
+
+               case 'k':  /* Transfer hard link. */
+                       hardlink(cp);
                        continue;
 
                case 'E':  /* End. (of directory) */
                        continue;
 
                case 'E':  /* End. (of directory) */
@@ -80,23 +105,21 @@ server()
                        }
                        tp = stp[--catname];
                        *tp = '\0';
                        }
                        tp = stp[--catname];
                        *tp = '\0';
-                       ga();
+                       ack();
                        continue;
 
                case 'C':  /* Clean. Cleanup a directory */
                        continue;
 
                case 'C':  /* Clean. Cleanup a directory */
-                       opts = 0;
-                       while (*cp >= '0' && *cp <= '7')
-                               opts = (opts << 3) | (*cp++ - '0');
-                       if (*cp++ != ' ')
-                               error("server: options not delimited\n");
-                       else
-                               clean(cp, opts, 1);
+                       clean(cp);
                        continue;
 
                case 'Q':  /* Query. Does the file/directory exist? */
                        query(cp);
                        continue;
 
                        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.
 #ifdef notdef
                /*
                 * These entries are reserved but not currently used.
@@ -107,7 +130,7 @@ server()
                        except = bp = NULL;
                case 'x':  /* add name to list of files to exclude */
                        if (*cp == '\0') {
                        except = bp = NULL;
                case 'x':  /* add name to list of files to exclude */
                        if (*cp == '\0') {
-                               ga();
+                               ack();
                                continue;
                        }
                        if (*cp == '~') {
                                continue;
                        }
                        if (*cp == '~') {
@@ -116,15 +139,15 @@ server()
                                cp = buf;
                        }
                        if (bp == NULL)
                                cp = buf;
                        }
                        if (bp == NULL)
-                               except = bp = expand(makeblock(NAME, cp), 0);
+                               except = bp = expand(makeblock(NAME, cp), E_VARS);
                        else
                        else
-                               bp->b_next = expand(makeblock(NAME, cp), 0);
+                               bp->b_next = expand(makeblock(NAME, cp), E_VARS);
                        while (bp->b_next != NULL)
                                bp = bp->b_next;
                        while (bp->b_next != NULL)
                                bp = bp->b_next;
-                       ga();
+                       ack();
                        continue;
 
                        continue;
 
-               case 'S':  /* Send. Transfer file if out of date. */
+               case 'I':  /* Install. Transfer file if out of date. */
                        opts = 0;
                        while (*cp >= '0' && *cp <= '7')
                                opts = (opts << 3) | (*cp++ - '0');
                        opts = 0;
                        while (*cp >= '0' && *cp <= '7')
                                opts = (opts << 3) | (*cp++ - '0');
@@ -132,7 +155,7 @@ server()
                                error("server: options not delimited\n");
                                return;
                        }
                                error("server: options not delimited\n");
                                return;
                        }
-                       sendf(cp, opts);
+                       install(cp, opts);
                        continue;
 
                case 'L':  /* Log. save message in log file */
                        continue;
 
                case 'L':  /* Log. save message in log file */
@@ -141,7 +164,7 @@ server()
 #endif
 
                case '\1':
 #endif
 
                case '\1':
-                       errs++;
+                       nerrs++;
                        continue;
 
                case '\2':
                        continue;
 
                case '\2':
@@ -165,6 +188,7 @@ install(src, dest, destdir, opts)
        int destdir, opts;
 {
        char *rname;
        int destdir, opts;
 {
        char *rname;
+       char destcopy[BUFSIZ];
 
        if (dest == NULL) {
                opts &= ~WHOLE; /* WHOLE mode only useful if renaming */
 
        if (dest == NULL) {
                opts &= ~WHOLE; /* WHOLE mode only useful if renaming */
@@ -176,7 +200,7 @@ install(src, dest, destdir, opts)
                        opts & WHOLE ? " -w" : "",
                        opts & YOUNGER ? " -y" : "",
                        opts & COMPARE ? " -b" : "",
                        opts & WHOLE ? " -w" : "",
                        opts & YOUNGER ? " -y" : "",
                        opts & COMPARE ? " -b" : "",
-                       opts & REMOVE ? " -r" : "", src, dest);
+                       opts & REMOVE ? " -R" : "", src, dest);
                if (nflag)
                        return;
        }
                if (nflag)
                        return;
        }
@@ -189,19 +213,13 @@ install(src, dest, destdir, opts)
                tp++;
        /*
         * If we are renaming a directory and we want to preserve
                tp++;
        /*
         * If we are renaming a directory and we want to preserve
-        * the directory heirarchy (-w), we must strip off the first
+        * the directory heirarchy (-w), we must strip off the leading
         * directory name and preserve the rest.
         */
        if (opts & WHOLE) {
         * directory name and preserve the rest.
         */
        if (opts & WHOLE) {
-               if (!destdir) {
-                       rname = index(rname, '/');
-                       if (rname == NULL)
-                               rname = tp; /* doesn't matter what rname is */
-                       else {
-                               destdir++; /* cat rname to dest */
-                               rname++;
-                       }
-               }
+               while (*rname == '/')
+                       rname++;
+               destdir = 1;
        } else {
                rname = rindex(target, '/');
                if (rname == NULL)
        } else {
                rname = rindex(target, '/');
                if (rname == NULL)
@@ -221,9 +239,16 @@ install(src, dest, destdir, opts)
        if (response() < 0)
                return;
 
        if (response() < 0)
                return;
 
+       if (destdir) {
+               strcpy(destcopy, dest);
+               Tdest = destcopy;
+       }
        sendf(rname, opts);
        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.
 /*
  * Transfer the file or directory in target[].
  * rname is the name of the file on the remote host.
@@ -232,72 +257,176 @@ sendf(rname, opts)
        char *rname;
        int opts;
 {
        char *rname;
        int opts;
 {
-       register char *cp;
+       register struct subcmd *sc;
        struct stat stb;
        struct stat stb;
-       int sizerr, f, u;
+       int sizerr, f, u, len;
        off_t i;
        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 (debug)
                printf("sendf(%s, %x)\n", rname, opts);
 
-       if (exclude(target))
+       if (except(target))
                return;
                return;
-       if (access(target, 4) < 0 || stat(target, &stb) < 0) {
-               error("%s: %s\n", target, sys_errlist[errno]);
+       if ((opts & FOLLOW ? stat(target, &stb) : lstat(target, &stb)) < 0) {
+               error("%s: %s\n", target, strerror(errno));
                return;
        }
                return;
        }
-       if (opts & REMOVE) {
-               opts &= ~REMOVE;
-               if (ISDIR(stb.st_mode))
-                       rmchk(rname, opts);
-       }
-       if ((u = update(rname, opts, &stb)) == 0)
+       if ((u = update(rname, opts, &stb)) == 0) {
+               if ((stb.st_mode & S_IFMT) == S_IFREG && stb.st_nlink > 1)
+                       (void) savelink(&stb);
                return;
                return;
+       }
 
        if (pw == NULL || pw->pw_uid != stb.st_uid)
                if ((pw = getpwuid(stb.st_uid)) == NULL) {
 
        if (pw == NULL || pw->pw_uid != stb.st_uid)
                if ((pw = getpwuid(stb.st_uid)) == NULL) {
-                       error("%s: no password entry for uid %d\n", target,
-                               stb.st_uid);
-                       return;
+                       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) {
                }
        if (gr == NULL || gr->gr_gid != stb.st_gid)
                if ((gr = getgrgid(stb.st_gid)) == NULL) {
-                       error("%s: no name for group %d\n", target, stb.st_gid);
-                       return;
+                       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 (u == 1) {
+               if (opts & VERIFY) {
+                       log(lfp, "need to install: %s\n", target);
+                       goto dospecial;
+               }
                log(lfp, "installing: %s\n", target);
                log(lfp, "installing: %s\n", target);
-               if (opts & VERIFY)
-                       return;
-               opts &= ~COMPARE;
+               opts &= ~(COMPARE|REMOVE);
        }
 
        switch (stb.st_mode & S_IFMT) {
        }
 
        switch (stb.st_mode & S_IFMT) {
-       case S_IFREG:
-               break;
-
        case S_IFDIR:
        case S_IFDIR:
-               rsendf(rname, opts, &stb, pw->pw_name, gr->gr_name);
+               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;
 
                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:
        default:
-               error("%s: not a plain file\n", target);
+               error("%s: not a file or directory\n", target);
                return;
        }
 
        if (u == 2) {
                return;
        }
 
        if (u == 2) {
+               if (opts & VERIFY) {
+                       log(lfp, "need to update: %s\n", target);
+                       goto dospecial;
+               }
                log(lfp, "updating: %s\n", target);
                log(lfp, "updating: %s\n", target);
-               if (opts & VERIFY)
+       }
+
+       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;
                        return;
+               }
        }
 
        if ((f = open(target, 0)) < 0) {
        }
 
        if ((f = open(target, 0)) < 0) {
-               error("%s: %s\n", target, sys_errlist[errno]);
+               error("%s: %s\n", target, strerror(errno));
                return;
        }
                return;
        }
-       (void) sprintf(buf, "R%o %04o %D %D %s %s %s\n", opts,
+       (void) sprintf(buf, "R%o %o %ld %ld %s %s %s\n", opts,
                stb.st_mode & 07777, stb.st_size, stb.st_mtime,
                stb.st_mode & 07777, stb.st_size, stb.st_mtime,
-               pw->pw_name, gr->gr_name, rname);
+               protoname(), protogroup(), rname);
        if (debug)
                printf("buf = %s", buf);
        (void) write(rem, buf, strlen(buf));
        if (debug)
                printf("buf = %s", buf);
        (void) write(rem, buf, strlen(buf));
@@ -315,63 +444,61 @@ sendf(rname, opts)
                (void) write(rem, buf, amt);
        }
        (void) close(f);
                (void) write(rem, buf, amt);
        }
        (void) close(f);
-       if (sizerr)
+done:
+       if (sizerr) {
                error("%s: file changed size\n", target);
                error("%s: file changed size\n", target);
-       else
-               ga();
-       (void) response();
+               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)
+                       ;
+       }
 }
 
 }
 
-rsendf(rname, opts, st, owner, group)
-       char *rname;
-       int opts;
-       struct stat *st;
-       char *owner, *group;
+struct linkbuf *
+savelink(stp)
+       struct stat *stp;
 {
 {
-       DIR *d;
-       struct direct *dp;
-       char *otp, *cp;
-       int len;
-
-       if (debug)
-               printf("rsendf(%s, %x, %x, %s, %s)\n", rname, opts, st,
-                       owner, group);
+       struct linkbuf *lp;
+       int found = 0;
 
 
-       if ((d = opendir(target)) == NULL) {
-               error("%s: %s\n", target, sys_errlist[errno]);
-               return;
-       }
-       (void) sprintf(buf, "D%o %04o 0 0 %s %s %s\n", opts,
-               st->st_mode & 07777, owner, group, rname);
-       if (debug)
-               printf("buf = %s", buf);
-       (void) write(rem, buf, strlen(buf));
-       if (response() < 0) {
-               closedir(d);
-               return;
-       }
-       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;
+       for (lp = ihead; lp != NULL; lp = lp->nextp)
+               if (lp->inum == stp->st_ino && lp->devnum == stp->st_dev) {
+                       lp->count--;
+                       return(lp);
                }
                }
-               tp = otp;
-               *tp++ = '/';
-               cp = dp->d_name;
-               while (*tp++ = *cp++)
-                       ;
-               tp--;
-               sendf(dp->d_name, opts);
+       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;
        }
        }
-       closedir(d);
-       (void) write(rem, "E\n", 2);
-       (void) response();
-       tp = otp;
-       *tp = '\0';
+       return(NULL);
 }
 
 /*
 }
 
 /*
@@ -379,17 +506,17 @@ rsendf(rname, opts, st, owner, group)
  * 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.
  */
  * 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, st)
+update(rname, opts, stp)
        char *rname;
        int opts;
        char *rname;
        int opts;
-       struct stat *st;
+       struct stat *stp;
 {
        register char *cp, *s;
        register off_t size;
        register time_t mtime;
 
        if (debug) 
 {
        register char *cp, *s;
        register off_t size;
        register time_t mtime;
 
        if (debug) 
-               printf("update(%s, %x, %x)\n", rname, opts, st);
+               printf("update(%s, %x, %x)\n", rname, opts, stp);
 
        /*
         * Check to see if the file exists on the remote machine.
 
        /*
         * Check to see if the file exists on the remote machine.
@@ -398,6 +525,7 @@ update(rname, opts, st)
        if (debug)
                printf("buf = %s", buf);
        (void) write(rem, buf, strlen(buf));
        if (debug)
                printf("buf = %s", buf);
        (void) write(rem, buf, strlen(buf));
+again:
        cp = s = buf;
        do {
                if (read(rem, cp, 1) != 1)
        cp = s = buf;
        do {
                if (read(rem, cp, 1) != 1)
@@ -412,7 +540,7 @@ update(rname, opts, st)
                return(1);
 
        case '\1':
                return(1);
 
        case '\1':
-               errs++;
+               nerrs++;
                if (*s != '\n') {
                        if (!iamremote) {
                                fflush(stdout);
                if (*s != '\n') {
                        if (!iamremote) {
                                fflush(stdout);
@@ -423,10 +551,15 @@ update(rname, opts, st)
                }
                return(0);
 
                }
                return(0);
 
+       case '\3':
+               *--cp = '\0';
+               if (lfp != NULL) 
+                       log(lfp, "update: note: %s\n", s);
+               goto again;
+
        default:
        default:
-               printf("buf = ");
-               fwrite(buf, 1, cp - s, stdout);
-               error("update: unexpected response '%c'\n", buf[0]);
+               *--cp = '\0';
+               error("update: unexpected response '%s'\n", s);
                return(0);
        }
 
                return(0);
        }
 
@@ -454,13 +587,13 @@ update(rname, opts, st)
         * File needs to be updated?
         */
        if (opts & YOUNGER) {
         * File needs to be updated?
         */
        if (opts & YOUNGER) {
-               if (st->st_mtime == mtime)
+               if (stp->st_mtime == mtime)
                        return(0);
                        return(0);
-               if (st->st_mtime < mtime) {
+               if (stp->st_mtime < mtime) {
                        log(lfp, "Warning: %s: remote copy is newer\n", target);
                        return(0);
                }
                        log(lfp, "Warning: %s: remote copy is newer\n", target);
                        return(0);
                }
-       } else if (st->st_mtime == mtime && st->st_size == size)
+       } else if (stp->st_mtime == mtime && stp->st_size == size)
                return(0);
        return(2);
 }
                return(0);
        return(2);
 }
@@ -469,7 +602,7 @@ update(rname, opts, st)
  * 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)
  * 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
+ *     Y\n             - exists and its a directory or symbolic link
  *     ^Aerror message\n
  */
 query(name)
  *     ^Aerror message\n
  */
 query(name)
@@ -480,42 +613,46 @@ query(name)
        if (catname)
                (void) sprintf(tp, "/%s", name);
 
        if (catname)
                (void) sprintf(tp, "/%s", name);
 
-       if (stat(target, &stb) < 0) {
-               (void) write(rem, "N\n", 2);
+       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:
                *tp = '\0';
                return;
        }
 
        switch (stb.st_mode & S_IFMT) {
        case S_IFREG:
-               (void) sprintf(buf, "Y%D %D\n", stb.st_size, stb.st_mtime);
+               (void) sprintf(buf, "Y%ld %ld\n", stb.st_size, stb.st_mtime);
                (void) write(rem, buf, strlen(buf));
                break;
 
                (void) write(rem, buf, strlen(buf));
                break;
 
+       case S_IFLNK:
        case S_IFDIR:
                (void) write(rem, "Y\n", 2);
                break;
 
        default:
        case S_IFDIR:
                (void) write(rem, "Y\n", 2);
                break;
 
        default:
-               error("%s: not a plain file\n", name);
+               error("%s: not a file or directory\n", name);
                break;
        }
        *tp = '\0';
 }
 
                break;
        }
        *tp = '\0';
 }
 
-recvf(cmd, isdir)
+recvf(cmd, type)
        char *cmd;
        char *cmd;
-       int isdir;
+       int type;
 {
        register char *cp;
 {
        register char *cp;
-       int f, mode, opts, wrerr, olderrno, u;
+       int f, mode, opts, wrerr, olderrno;
        off_t i, size;
        time_t mtime;
        struct stat stb;
        struct timeval tvp[2];
        off_t i, size;
        time_t mtime;
        struct stat stb;
        struct timeval tvp[2];
-       char *owner, *group, *dir;
+       char *owner, *group;
        char new[BUFSIZ];
        char new[BUFSIZ];
-       extern char *tmpname;
+       extern char *tempname;
 
        cp = cmd;
        opts = 0;
 
        cp = cmd;
        opts = 0;
@@ -563,10 +700,10 @@ recvf(cmd, isdir)
        }
        *cp++ = '\0';
 
        }
        *cp++ = '\0';
 
-       new[0] = '\0';
-       if (isdir) {
+       if (type == S_IFDIR) {
                if (catname >= sizeof(stp)) {
                if (catname >= sizeof(stp)) {
-                       error("%s: too many directory levels\n", target);
+                       error("%s:%s: too many directory levels\n",
+                               host, target);
                        return;
                }
                stp[catname] = tp;
                        return;
                }
                stp[catname] = tp;
@@ -577,60 +714,91 @@ recvf(cmd, isdir)
                        tp--;
                }
                if (opts & VERIFY) {
                        tp--;
                }
                if (opts & VERIFY) {
-                       ga();
+                       ack();
                        return;
                }
                        return;
                }
-               if (stat(target, &stb) == 0) {
-                       if (!ISDIR(stb.st_mode)) {
-                               errno = ENOTDIR;
-                               goto bad;
-                       }
-               } else {
-                       if (chkparent(target) < 0)
-                               goto bad;
-                       if (mkdir(target, mode) < 0)
-                               goto bad;
-                       if (chog(target, owner, group, mode) < 0)
+               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;
                                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;
                }
                }
-               ga();
+               error("%s:%s: %s\n", host, target, strerror(errno));
+               tp = stp[--catname];
+               *tp = '\0';
                return;
        }
 
        if (catname)
                (void) sprintf(tp, "/%s", cp);
                return;
        }
 
        if (catname)
                (void) sprintf(tp, "/%s", cp);
-       if (stat(target, &stb) == 0) {
-               if ((stb.st_mode & S_IFMT) != S_IFREG) {
-                       error("%s: not a regular file\n", target);
-                       return;
-               }
-               u = 2;
-       } else
-               u = 1;
-       if (chkparent(target) < 0)
-               goto bad;
        cp = rindex(target, '/');
        if (cp == NULL)
        cp = rindex(target, '/');
        if (cp == NULL)
-               dir = ".";
-       else if (cp == target) {
-               dir = "";
-               cp = NULL;
-       } else {
-               dir = target;
+               strcpy(new, tempname);
+       else if (cp == target)
+               (void) sprintf(new, "/%s", tempname);
+       else {
                *cp = '\0';
                *cp = '\0';
-       }
-       (void) sprintf(new, "%s/%s", dir, tmpname);
-       if (cp != NULL)
+               (void) sprintf(new, "%s/%s", target, tempname);
                *cp = '/';
                *cp = '/';
-       if ((f = creat(new, mode)) < 0)
-               goto bad1;
-       if (chog(new, owner, group, mode) < 0) {
-               (void) close(f);
-               (void) unlink(new);
-               return;
        }
        }
-       ga();
 
 
+       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;
        wrerr = 0;
        for (i = 0; i < size; i += BUFSIZ) {
                int amt = BUFSIZ;
@@ -658,9 +826,13 @@ recvf(cmd, isdir)
                }
        }
        (void) close(f);
                }
        }
        (void) close(f);
-       (void) response();
+       if (response() < 0) {
+               err();
+               (void) unlink(new);
+               return;
+       }
        if (wrerr) {
        if (wrerr) {
-               error("%s: %s\n", cp, sys_errlist[olderrno]);
+               error("%s:%s: %s\n", host, new, strerror(errno));
                (void) unlink(new);
                return;
        }
                (void) unlink(new);
                return;
        }
@@ -669,23 +841,28 @@ recvf(cmd, isdir)
                int c;
 
                if ((f1 = fopen(target, "r")) == NULL)
                int c;
 
                if ((f1 = fopen(target, "r")) == NULL)
-                       goto bad;
-               if ((f2 = fopen(new, "r")) == NULL)
-                       goto bad1;
+                       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);
                while ((c = getc(f1)) == getc(f2))
                        if (c == EOF) {
                                (void) fclose(f1);
                                (void) fclose(f2);
                                (void) unlink(new);
-                               ga();
+                               ack();
                                return;
                        }
                (void) fclose(f1);
                (void) fclose(f2);
                if (opts & VERIFY) {
                                return;
                        }
                (void) fclose(f1);
                (void) fclose(f2);
                if (opts & VERIFY) {
+               differ:
                        (void) unlink(new);
                        buf[0] = '\0';
                        (void) unlink(new);
                        buf[0] = '\0';
-                       sprintf(buf + 1, "updating %s:%s\n", host, target);
+                       (void) sprintf(buf + 1, "need to update: %s\n",target);
                        (void) write(rem, buf, strlen(buf + 1) + 1);
                        return;
                }
                        (void) write(rem, buf, strlen(buf + 1) + 1);
                        return;
                }
@@ -694,127 +871,179 @@ recvf(cmd, isdir)
        /*
         * Set last modified time
         */
        /*
         * Set last modified time
         */
-       tvp[0].tv_sec = stb.st_atime;   /* old accessed time from target */
+       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) {
        tvp[0].tv_usec = 0;
        tvp[1].tv_sec = mtime;
        tvp[1].tv_usec = 0;
        if (utimes(new, tvp) < 0) {
-bad1:
-               error("%s: %s\n", new, sys_errlist[errno]);
-               if (new[0])
-                       (void) unlink(new);
+               note("%s:utimes failed %s: %s\n", host, new, strerror(errno));
+       }
+       if (chog(new, owner, group, mode) < 0) {
+               (void) unlink(new);
                return;
        }
                return;
        }
-       
+fixup:
        if (rename(new, target) < 0) {
        if (rename(new, target) < 0) {
-bad:
-               error("%s: %s\n", target, sys_errlist[errno]);
-               if (new[0])
-                       (void) unlink(new);
+badt:
+               error("%s:%s: %s\n", host, target, strerror(errno));
+               (void) unlink(new);
                return;
        }
        if (opts & COMPARE) {
                buf[0] = '\0';
                return;
        }
        if (opts & COMPARE) {
                buf[0] = '\0';
-               sprintf(buf + 1, "updated %s:%s\n", host, target);
+               (void) sprintf(buf + 1, "updated %s\n", target);
                (void) write(rem, buf, strlen(buf + 1) + 1);
        } else
                (void) write(rem, buf, strlen(buf + 1) + 1);
        } else
-               ga();
+               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 parent directory for write permission and create if it doesn't
- * exist.
+ * Check to see if parent directory exists and create one if not.
  */
 chkparent(name)
        char *name;
 {
  */
 chkparent(name)
        char *name;
 {
-       register char *cp, *dir;
-       extern int userid, groupid;
+       register char *cp;
+       struct stat stb;
 
        cp = rindex(name, '/');
 
        cp = rindex(name, '/');
-       if (cp == NULL)
-               dir = ".";
-       else if (cp == name) {
-               dir = "/";
-               cp = NULL;
-       } else {
-               dir = name;
-               *cp = '\0';
-       }
-       if (access(dir, 2) == 0) {
-               if (cp != NULL)
-                       *cp = '/';
+       if (cp == NULL || cp == name)
                return(0);
                return(0);
-       }
-       if (errno == ENOENT) {
-               if (rindex(dir, '/') != NULL && chkparent(dir) < 0)
-                       goto bad;
-               if (!strcmp(dir, ".") || !strcmp(dir, "/"))
-                       goto bad;
-               if (mkdir(dir, 0777 & ~oumask) < 0)
-                       goto bad;
-               if (chown(dir, userid, groupid) < 0) {
-                       (void) unlink(dir);
-                       goto bad;
-               }
-               if (cp != NULL)
+       *cp = '\0';
+       if (lstat(name, &stb) < 0) {
+               if (errno == ENOENT && chkparent(name) >= 0 &&
+                   mkdir(name, 0777 & ~oumask) >= 0) {
                        *cp = '/';
                        *cp = '/';
+                       return(0);
+               }
+       } else if (ISDIR(stb.st_mode)) {
+               *cp = '/';
                return(0);
        }
                return(0);
        }
-
-bad:
-       if (cp != NULL)
-               *cp = '/';
+       *cp = '/';
        return(-1);
 }
 
 /*
        return(-1);
 }
 
 /*
- * Change owner and group of file.
+ * Change owner, group and mode of file.
  */
 chog(file, owner, group, mode)
        char *file, *owner, *group;
        int mode;
 {
  */
 chog(file, owner, group, mode)
        char *file, *owner, *group;
        int mode;
 {
-       extern int userid, groupid;
-       extern char user[];
        register int i;
        int uid, gid;
        register int i;
        int uid, gid;
+       extern char user[];
+       extern int userid;
 
        uid = userid;
        if (userid == 0) {
 
        uid = userid;
        if (userid == 0) {
-               if (pw == NULL || strcmp(owner, pw->pw_name) != 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) {
                        if ((pw = getpwnam(owner)) == NULL) {
                                if (mode & 04000) {
-                                       error("%s: unknown login name\n", owner);
-                                       return(-1);
+                                       note("%s:%s: unknown login name, clearing setuid",
+                                               host, owner);
+                                       mode &= ~04000;
+                                       uid = 0;
                                }
                        } else
                                uid = pw->pw_uid;
                } else
                        uid = pw->pw_uid;
                                }
                        } else
                                uid = pw->pw_uid;
                } else
                        uid = pw->pw_uid;
-       }
-       gid = groupid;
+               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 (gr == NULL || strcmp(group, gr->gr_name) != 0) {
-               if ((gr = getgrnam(group)) == NULL) {
+               if ((*group == ':' && (getgrgid(gid = atoi(group + 1)) == NULL))
+                  || ((gr = getgrnam(group)) == NULL)) {
                        if (mode & 02000) {
                        if (mode & 02000) {
-                               error("%s: unknown group\n", group);
-                               return(-1);
+                               note("%s:%s: unknown group", host, group);
+                               mode &= ~02000;
                        }
                } else
                        gid = gr->gr_gid;
        } else
                gid = gr->gr_gid;
                        }
                } else
                        gid = gr->gr_gid;
        } else
                gid = gr->gr_gid;
-       if (userid && groupid != gid) {
-               for (i = 0; gr->gr_mem[i] != NULL; i++)
+       if (userid && gid >= 0) {
+               if (gr) for (i = 0; gr->gr_mem[i] != NULL; i++)
                        if (!(strcmp(user, gr->gr_mem[i])))
                                goto ok;
                        if (!(strcmp(user, gr->gr_mem[i])))
                                goto ok;
-               gid = groupid;
+               mode &= ~02000;
+               gid = -1;
        }
 ok:
        }
 ok:
-       if (chown(file, uid, gid) < 0) {
-               error("%s: %s\n", file, sys_errlist[errno]);
-               return(-1);
+       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);
 }
 
        return(0);
 }
 
@@ -822,27 +1051,24 @@ ok:
  * Check for files on the machine being updated that are not on the master
  * machine and remove them.
  */
  * Check for files on the machine being updated that are not on the master
  * machine and remove them.
  */
-rmchk(rname, opts)
-       char *rname;
+rmchk(opts)
        int opts;
 {
        register char *cp, *s;
        struct stat stb;
 
        if (debug)
        int opts;
 {
        register char *cp, *s;
        struct stat stb;
 
        if (debug)
-               printf("rmchk(%s, %x)\n", rname, opts);
+               printf("rmchk()\n");
 
        /*
         * Tell the remote to clean the files from the last directory sent.
         */
 
        /*
         * Tell the remote to clean the files from the last directory sent.
         */
-       (void) sprintf(buf, "C%o %s\n", opts & VERIFY, rname);
+       (void) sprintf(buf, "C%o\n", opts & VERIFY);
        if (debug)
                printf("buf = %s", buf);
        (void) write(rem, buf, strlen(buf));
        if (response() < 0)
                return;
        if (debug)
                printf("buf = %s", buf);
        (void) write(rem, buf, strlen(buf));
        if (response() < 0)
                return;
-       stp[0] = tp;
-       catname = 1;
        for (;;) {
                cp = s = buf;
                do {
        for (;;) {
                cp = s = buf;
                do {
@@ -851,45 +1077,22 @@ rmchk(rname, opts)
                } while (*cp++ != '\n' && cp < &buf[BUFSIZ]);
 
                switch (*s++) {
                } while (*cp++ != '\n' && cp < &buf[BUFSIZ]);
 
                switch (*s++) {
-               case 'Q': /* its a directory on the remote end */
-               case 'q': /* its a regular file on the remote end */
+               case 'Q': /* Query if file should be removed */
                        /*
                         * Return the following codes to remove query.
                        /*
                         * Return the following codes to remove query.
-                        * N\n -- file does not exisit, remove.
-                        * Y\n -- file exists and is a directory.
-                        * y\n -- file exists and is a regular file.
+                        * 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);
                         */
                        *--cp = '\0';
                        (void) sprintf(tp, "/%s", s);
                        if (debug)
                                printf("check %s\n", target);
-                       if (exclude(target))
-                               (void) write(rem, "y\n", 2);
-                       else if (stat(target, &stb) < 0)
+                       if (except(target))
                                (void) write(rem, "N\n", 2);
                                (void) write(rem, "N\n", 2);
-                       else if (buf[0] == 'Q' && ISDIR(stb.st_mode)) {
-                               if (catname >= sizeof(stp)) {
-                                       error("%s: too many directory levels\n", target);
-                                       break;
-                               }
+                       else if (lstat(target, &stb) < 0)
                                (void) write(rem, "Y\n", 2);
                                (void) write(rem, "Y\n", 2);
-                               if (response() < 0)
-                                       break;
-                               stp[catname++] = tp;
-                               while (*tp)
-                                       tp++;
-                       } else
-                               (void) write(rem, "y\n", 2);
-                       break;
-
-               case 'E':
-                       if (catname < 0)
-                               fatal("too many 'E's\n");
-                       ga();
-                       tp = stp[--catname];
-                       *tp = '\0';
-                       if (catname == 0)
-                               return;
+                       else
+                               (void) write(rem, "N\n", 2);
                        break;
 
                case '\0':
                        break;
 
                case '\0':
@@ -898,9 +1101,14 @@ rmchk(rname, opts)
                                log(lfp, "%s\n", s);
                        break;
 
                                log(lfp, "%s\n", s);
                        break;
 
+               case 'E':
+                       *tp = '\0';
+                       ack();
+                       return;
+
                case '\1':
                case '\2':
                case '\1':
                case '\2':
-                       errs++;
+                       nerrs++;
                        if (*s != '\n') {
                                if (!iamremote) {
                                        fflush(stdout);
                        if (*s != '\n') {
                                if (!iamremote) {
                                        fflush(stdout);
@@ -910,68 +1118,41 @@ rmchk(rname, opts)
                                        (void) fwrite(s, 1, cp - s, lfp);
                        }
                        if (buf[0] == '\2')
                                        (void) fwrite(s, 1, cp - s, lfp);
                        }
                        if (buf[0] == '\2')
-                               cleanup();
+                               lostconn();
                        break;
 
                default:
                        break;
 
                default:
-                       error("rmchk: unexpected response '%c'\n", buf[0]);
+                       error("rmchk: unexpected response '%s'\n", buf);
+                       err();
                }
        }
 }
 
 /*
                }
        }
 }
 
 /*
- * Check the directory initialized by the 'T' command to server()
+ * Check the current directory (initialized by the 'T' command to server())
  * for extraneous files and remove them.
  */
  * for extraneous files and remove them.
  */
-clean(name, opts, first)
-       char *name;
-       int opts, first;
+clean(cp)
+       register char *cp;
 {
        DIR *d;
 {
        DIR *d;
-       struct direct *dp;
-       register char *cp;
+       register struct direct *dp;
        struct stat stb;
        struct stat stb;
-       char *ootp, *otp;
-       int len;
+       char *otp;
+       int len, opts;
 
 
-       if (first) {
-               ootp = tp;
-               if (catname) {
-                       *tp++ = '/';
-                       cp = name;
-                       while (*tp++ = *cp++)
-                               ;
-                       tp--;
-               }
-               if (stat(target, &stb) < 0) {
-                       if (errno != ENOENT) {
-                               ga();
-                               goto done;
-                       }
-                       error("%s: %s\n", target, sys_errlist[errno]);
-                       tp = ootp;
-                       *tp = '\0';
-                       return;
-               }
-               /*
-                * This should be a directory because its a directory on the
-                * master machine. If not, let install complain about it.
-                */
-               if (!ISDIR(stb.st_mode)) {
-                       ga();
-                       goto done;
-               }
+       opts = 0;
+       while (*cp >= '0' && *cp <= '7')
+               opts = (opts << 3) | (*cp++ - '0');
+       if (*cp != '\0') {
+               error("clean: options not delimited\n");
+               return;
        }
        }
-
-       if (access(target, 6) < 0 || (d = opendir(target)) == NULL) {
-               error("%s: %s\n", target, sys_errlist[errno]);
-               if (first) {
-                       tp = ootp;
-                       *tp = '\0';
-               }
+       if ((d = opendir(target)) == NULL) {
+               error("%s:%s: %s\n", host, target, strerror(errno));
                return;
        }
                return;
        }
-       ga();
+       ack();
 
        otp = tp;
        len = tp - target;
 
        otp = tp;
        len = tp - target;
@@ -979,7 +1160,8 @@ clean(name, opts, first)
                if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, ".."))
                        continue;
                if (len + 1 + strlen(dp->d_name) >= BUFSIZ - 1) {
                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);
+                       error("%s:%s/%s: Name too long\n",
+                               host, target, dp->d_name);
                        continue;
                }
                tp = otp;
                        continue;
                }
                tp = otp;
@@ -988,38 +1170,33 @@ clean(name, opts, first)
                while (*tp++ = *cp++)
                        ;
                tp--;
                while (*tp++ = *cp++)
                        ;
                tp--;
-               if (stat(target, &stb) < 0) {
-                       error("%s: %s\n", target, sys_errlist[errno]);
+               if (lstat(target, &stb) < 0) {
+                       error("%s:%s: %s\n", host, target, strerror(errno));
                        continue;
                }
                        continue;
                }
-               (void) sprintf(buf, "%c%s\n", ISDIR(stb.st_mode) ? 'Q' : 'q',
-                       dp->d_name);
+               (void) sprintf(buf, "Q%s\n", dp->d_name);
                (void) write(rem, buf, strlen(buf));
                cp = buf;
                do {
                        if (read(rem, cp, 1) != 1)
                (void) write(rem, buf, strlen(buf));
                cp = buf;
                do {
                        if (read(rem, cp, 1) != 1)
-                               lostconn();
+                               cleanup();
                } while (*cp++ != '\n' && cp < &buf[BUFSIZ]);
                *--cp = '\0';
                cp = buf;
                } while (*cp++ != '\n' && cp < &buf[BUFSIZ]);
                *--cp = '\0';
                cp = buf;
-               if (*cp != 'N') {
-                       if (*cp == 'Y' && ISDIR(stb.st_mode))
-                               clean(dp->d_name, opts, 0);
+               if (*cp != 'Y')
                        continue;
                        continue;
-               }
                if (opts & VERIFY) {
                        cp = buf;
                        *cp++ = '\0';
                if (opts & VERIFY) {
                        cp = buf;
                        *cp++ = '\0';
-                       (void) sprintf(cp, "removing %s\n", target);
+                       (void) sprintf(cp, "need to remove: %s\n", target);
                        (void) write(rem, buf, strlen(cp) + 1);
                } else
                        remove(&stb);
        }
        closedir(d);
                        (void) write(rem, buf, strlen(cp) + 1);
                } else
                        remove(&stb);
        }
        closedir(d);
-done:
        (void) write(rem, "E\n", 2);
        (void) response();
        (void) write(rem, "E\n", 2);
        (void) response();
-       tp = (first) ? ootp : otp;
+       tp = otp;
        *tp = '\0';
 }
 
        *tp = '\0';
 }
 
@@ -1027,8 +1204,8 @@ done:
  * Remove a file or directory (recursively) and send back an acknowledge
  * or an error message.
  */
  * Remove a file or directory (recursively) and send back an acknowledge
  * or an error message.
  */
-remove(st)
-       struct stat *st;
+remove(stp)
+       struct stat *stp;
 {
        DIR *d;
        struct direct *dp;
 {
        DIR *d;
        struct direct *dp;
@@ -1037,8 +1214,9 @@ remove(st)
        char *otp;
        int len;
 
        char *otp;
        int len;
 
-       switch (st->st_mode & S_IFMT) {
+       switch (stp->st_mode & S_IFMT) {
        case S_IFREG:
        case S_IFREG:
+       case S_IFLNK:
                if (unlink(target) < 0)
                        goto bad;
                goto removed;
                if (unlink(target) < 0)
                        goto bad;
                goto removed;
@@ -1047,11 +1225,11 @@ remove(st)
                break;
 
        default:
                break;
 
        default:
-               error("%s: not a plain file\n", target);
+               error("%s:%s: not a plain file\n", host, target);
                return;
        }
 
                return;
        }
 
-       if (access(target, 6) < 0 || (d = opendir(target)) == NULL)
+       if ((d = opendir(target)) == NULL)
                goto bad;
 
        otp = tp;
                goto bad;
 
        otp = tp;
@@ -1060,7 +1238,8 @@ remove(st)
                if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, ".."))
                        continue;
                if (len + 1 + strlen(dp->d_name) >= BUFSIZ - 1) {
                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);
+                       error("%s:%s/%s: Name too long\n",
+                               host, target, dp->d_name);
                        continue;
                }
                tp = otp;
                        continue;
                }
                tp = otp;
@@ -1069,8 +1248,8 @@ remove(st)
                while (*tp++ = *cp++)
                        ;
                tp--;
                while (*tp++ = *cp++)
                        ;
                tp--;
-               if (stat(target, &stb) < 0) {
-                       error("%s: %s\n", target, sys_errlist[errno]);
+               if (lstat(target, &stb) < 0) {
+                       error("%s:%s: %s\n", host, target, strerror(errno));
                        continue;
                }
                remove(&stb);
                        continue;
                }
                remove(&stb);
@@ -1080,7 +1259,7 @@ remove(st)
        *tp = '\0';
        if (rmdir(target) < 0) {
 bad:
        *tp = '\0';
        if (rmdir(target) < 0) {
 bad:
-               error("%s: %s\n", target, sys_errlist[errno]);
+               error("%s:%s: %s\n", host, target, strerror(errno));
                return;
        }
 removed:
                return;
        }
 removed:
@@ -1090,6 +1269,76 @@ removed:
        (void) write(rem, buf, strlen(cp) + 1);
 }
 
        (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;
 /*VARARGS2*/
 log(fp, fmt, a1, a2, a3)
        FILE *fp;
@@ -1110,16 +1359,27 @@ error(fmt, a1, a2, a3)
        char *fmt;
        int a1, a2, a3;
 {
        char *fmt;
        int a1, a2, a3;
 {
-       errs++;
-       strcpy(buf, "\1rdist: ");
-       (void) sprintf(buf+8, fmt, a1, a2, a3);
-       (void) write(rem, buf, strlen(buf));
-       if (!iamremote) {
+       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);
                fflush(stdout);
-               (void) write(2, buf+1, strlen(buf+1));
+               (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);
        }
        }
-       if (lfp != NULL)
-               (void) fwrite(buf+1, 1, strlen(buf+1), lfp);
 }
 
 /*VARARGS1*/
 }
 
 /*VARARGS1*/
@@ -1127,45 +1387,63 @@ fatal(fmt, a1, a2,a3)
        char *fmt;
        int a1, a2, a3;
 {
        char *fmt;
        int a1, a2, a3;
 {
-       errs++;
-       strcpy(buf, "\2rdist: ");
-       (void) sprintf(buf+8, fmt, a1, a2, a3);
-       (void) write(rem, buf, strlen(buf));
-       if (!iamremote) {
+       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);
                fflush(stdout);
-               (void) write(2, buf+1, strlen(buf+1));
+               (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);
        }
        }
-       if (lfp != NULL)
-               (void) fwrite(buf+1, 1, strlen(buf+1), lfp);
        cleanup();
 }
 
 response()
 {
        char *cp, *s;
        cleanup();
 }
 
 response()
 {
        char *cp, *s;
+       char resp[BUFSIZ];
 
        if (debug)
                printf("response()\n");
 
 
        if (debug)
                printf("response()\n");
 
-       cp = s = buf;
+       cp = s = resp;
        do {
                if (read(rem, cp, 1) != 1)
                        lostconn();
        do {
                if (read(rem, cp, 1) != 1)
                        lostconn();
-       } while (*cp++ != '\n' && cp < &buf[BUFSIZ]);
+       } while (*cp++ != '\n' && cp < &resp[BUFSIZ]);
 
        switch (*s++) {
        case '\0':
                *--cp = '\0';
 
        switch (*s++) {
        case '\0':
                *--cp = '\0';
-               if (*s != '\0')
+               if (*s != '\0') {
                        log(lfp, "%s\n", s);
                        log(lfp, "%s\n", s);
+                       return(1);
+               }
                return(0);
                return(0);
+       case '\3':
+               *--cp = '\0';
+               log(lfp, "Note: %s\n",s);
+               return(response());
 
        default:
                s--;
                /* fall into... */
        case '\1':
        case '\2':
 
        default:
                s--;
                /* fall into... */
        case '\1':
        case '\2':
-               errs++;
+               nerrs++;
                if (*s != '\n') {
                        if (!iamremote) {
                                fflush(stdout);
                if (*s != '\n') {
                        if (!iamremote) {
                                fflush(stdout);
@@ -1174,17 +1452,34 @@ response()
                        if (lfp != NULL)
                                (void) fwrite(s, 1, cp - s, lfp);
                }
                        if (lfp != NULL)
                                (void) fwrite(s, 1, cp - s, lfp);
                }
-               if (buf[0] == '\2')
-                       cleanup();
+               if (resp[0] == '\2')
+                       lostconn();
                return(-1);
        }
 }
 
                return(-1);
        }
 }
 
-lostconn()
+/*
+ * Remove temporary files and do any cleanup operations before exiting.
+ */
+cleanup()
 {
 {
-       if (!iamremote) {
-               fflush(stdout);
-               fprintf(stderr, "rdist: lost connection\n");
-       }
-       cleanup();
+       (void) unlink(tempfile);
+       exit(1);
+}
+
+note(fmt, 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);
 }
 }