don't redefine _PATH_TMP; don't assume it begins "/tmp/";
[unix-history] / usr / src / usr.bin / rdist / server.c
index cab204b..534bf09 100644 (file)
@@ -1,21 +1,33 @@
+/*
+ * 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.13 (Berkeley) 84/01/03";
-#endif
+static char sccsid[] = "@(#)server.c   5.13 (Berkeley) %G%";
+#endif /* not lint */
 
 #include "defs.h"
 
 #define        ack()   (void) write(rem, "\0\n", 2)
 #define        err()   (void) write(rem, "\1\n", 2)
 
 
 #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   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 */
 
+int    cleanup();
+struct linkbuf *savelink();
+
 /*
  * Server routine to read requests and process them.
  * Commands are:
 /*
  * Server routine to read requests and process them.
  * Commands are:
@@ -28,6 +40,13 @@ server()
        char cmdbuf[BUFSIZ];
        register char *cp;
 
        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));
        oumask = umask(0);
        (void) sprintf(buf, "V%d\n", VERSION);
        (void) write(rem, buf, strlen(buf));
@@ -42,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;
@@ -62,12 +81,20 @@ server()
                        ack();
                        continue;
 
                        ack();
                        continue;
 
-               case 'R':  /* Receive. Transfer file. */
-                       recvf(cp, 0);
+               case 'R':  /* Transfer a regular file. */
+                       recvf(cp, S_IFREG);
                        continue;
 
                        continue;
 
-               case 'D':  /* Directory. Transfer file. */
-                       recvf(cp, 1);
+               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) */
                        continue;
 
                case 'E':  /* End. (of directory) */
@@ -137,7 +164,7 @@ server()
 #endif
 
                case '\1':
 #endif
 
                case '\1':
-                       errs++;
+                       nerrs++;
                        continue;
 
                case '\2':
                        continue;
 
                case '\2':
@@ -161,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 */
@@ -211,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.
@@ -222,34 +257,44 @@ sendf(rname, opts)
        char *rname;
        int opts;
 {
        char *rname;
        int opts;
 {
-       register struct block *c;
+       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;
-       extern struct block *special;
+       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 (inlist(except, target))
+       if (except(target))
                return;
                return;
-       if (access(target, 4) < 0 || lstat(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 ((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 (opts & VERIFY) {
                }
        if (u == 1) {
                if (opts & VERIFY) {
@@ -261,19 +306,90 @@ sendf(rname, opts)
        }
 
        switch (stb.st_mode & S_IFMT) {
        }
 
        switch (stb.st_mode & S_IFMT) {
-       case S_IFREG:
-               break;
+       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;
+               }
 
 
-       case S_IFLNK:
-               error("%s: cannot install soft links - use 'special'\n", target);
-               return;
+               if (opts & REMOVE)
+                       rmchk(opts);
 
 
-       case S_IFDIR:
-               rsendf(rname, opts, &stb, pw->pw_name, gr->gr_name);
+               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;
        }
 
                return;
        }
 
@@ -284,16 +400,33 @@ sendf(rname, opts)
                }
                log(lfp, "updating: %s\n", target);
        }
                }
                log(lfp, "updating: %s\n", target);
        }
-       if (stb.st_nlink != 1)
-               log(lfp, "%s: Warning: more than one hard link\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) {
 
        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));
@@ -311,23 +444,25 @@ sendf(rname, opts)
                (void) write(rem, buf, amt);
        }
        (void) close(f);
                (void) write(rem, buf, amt);
        }
        (void) close(f);
+done:
        if (sizerr) {
                error("%s: file changed size\n", target);
                err();
        } else
                ack();
        if (sizerr) {
                error("%s: file changed size\n", target);
                err();
        } else
                ack();
-       if (response() == 0 && (opts & COMPARE))
+       f = response();
+       if (f < 0 || f == 0 && (opts & COMPARE))
                return;
 dospecial:
                return;
 dospecial:
-       for (c = special; c != NULL; c = c->b_next) {
-               if (c->b_type != SPECIAL)
+       for (sc = subcmds; sc != NULL; sc = sc->sc_next) {
+               if (sc->sc_type != SPECIAL)
                        continue;
                        continue;
-               if (!inlist(c->b_args, target))
+               if (sc->sc_args != NULL && !inlist(sc->sc_args, target))
                        continue;
                        continue;
-               log(lfp, "special \"%s\"\n", c->b_name);
+               log(lfp, "special \"%s\"\n", sc->sc_name);
                if (opts & VERIFY)
                        continue;
                if (opts & VERIFY)
                        continue;
-               (void) sprintf(buf, "S%s\n", c->b_name);
+               (void) sprintf(buf, "SFILE=%s;%s\n", target, sc->sc_name);
                if (debug)
                        printf("buf = %s", buf);
                (void) write(rem, buf, strlen(buf));
                if (debug)
                        printf("buf = %s", buf);
                (void) write(rem, buf, strlen(buf));
@@ -336,60 +471,34 @@ dospecial:
        }
 }
 
        }
 }
 
-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);
-
-       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 & 0777, owner, group, rname);
-       if (debug)
-               printf("buf = %s", buf);
-       (void) write(rem, buf, strlen(buf));
-       if (response() < 0) {
-               closedir(d);
-               return;
-       }
-
-       if (opts & REMOVE)
-               rmchk(opts);
+       struct linkbuf *lp;
+       int found = 0;
 
 
-       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);
 }
 
 /*
 }
 
 /*
@@ -397,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.
@@ -416,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)
@@ -430,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);
@@ -441,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);
        }
 
@@ -472,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);
 }
@@ -487,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)
@@ -498,32 +613,36 @@ 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;
        int f, mode, opts, wrerr, olderrno;
 {
        register char *cp;
        int f, mode, opts, wrerr, olderrno;
@@ -531,9 +650,9 @@ recvf(cmd, isdir)
        time_t mtime;
        struct stat stb;
        struct timeval tvp[2];
        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;
@@ -581,9 +700,10 @@ recvf(cmd, isdir)
        }
        *cp++ = '\0';
 
        }
        *cp++ = '\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;
@@ -597,57 +717,88 @@ recvf(cmd, isdir)
                        ack();
                        return;
                }
                        ack();
                        return;
                }
-               if (stat(target, &stb) == 0) {
+               if (lstat(target, &stb) == 0) {
                        if (ISDIR(stb.st_mode)) {
                        if (ISDIR(stb.st_mode)) {
-                               if (stb.st_mode & 0777 == mode) {
+                               if ((stb.st_mode & 07777) == mode) {
                                        ack();
                                        return;
                                }
                                buf[0] = '\0';
                                (void) sprintf(buf + 1,
                                        ack();
                                        return;
                                }
                                buf[0] = '\0';
                                (void) sprintf(buf + 1,
-                                       "%s:%s: Warning: mode %o != %o\n",
-                                       host, target, stb.st_mode & 0777, mode);
+                                       "%s: Warning: remote mode %o != local mode %o\n",
+                                       target, stb.st_mode & 07777, mode);
                                (void) write(rem, buf, strlen(buf + 1) + 1);
                                return;
                        }
                                (void) write(rem, buf, strlen(buf + 1) + 1);
                                return;
                        }
-                       error("%s: %s\n", target, sys_errlist[ENOTDIR]);
-               } else if (chkparent(target) < 0 || mkdir(target, mode) < 0)
-                       error("%s: %s\n", target, sys_errlist[errno]);
-               else if (chog(target, owner, group, mode) == 0) {
-                       ack();
+                       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;
                }
                        return;
                }
+               error("%s:%s: %s\n", host, target, strerror(errno));
                tp = stp[--catname];
                *tp = '\0';
                return;
        }
 
                tp = stp[--catname];
                *tp = '\0';
                return;
        }
 
-       new[0] = '\0';
        if (catname)
                (void) sprintf(tp, "/%s", cp);
        if (catname)
                (void) sprintf(tp, "/%s", cp);
-       if (stat(target, &stb) == 0 && (stb.st_mode & S_IFMT) != S_IFREG) {
-               error("%s: not a regular file\n", target);
-               return;
-       }
-       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;
-       ack();
+       }
+
+       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;
@@ -675,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;
        }
@@ -686,9 +841,13 @@ 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);
                while ((c = getc(f1)) == getc(f2))
                        if (c == EOF) {
                                (void) fclose(f1);
@@ -700,10 +859,10 @@ recvf(cmd, isdir)
                (void) fclose(f1);
                (void) fclose(f2);
                if (opts & VERIFY) {
                (void) fclose(f1);
                (void) fclose(f2);
                if (opts & VERIFY) {
+               differ:
                        (void) unlink(new);
                        buf[0] = '\0';
                        (void) unlink(new);
                        buf[0] = '\0';
-                       (void) sprintf(buf + 1, "need to update %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;
                }
@@ -712,80 +871,113 @@ 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]);
-               (void) unlink(new);
-               return;
+               note("%s:utimes failed %s: %s\n", host, new, strerror(errno));
        }
        if (chog(new, owner, group, mode) < 0) {
                (void) unlink(new);
                return;
        }
        }
        if (chog(new, owner, group, mode) < 0) {
                (void) unlink(new);
                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';
-               (void) 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
                ack();
 }
 
 /*
                (void) write(rem, buf, strlen(buf + 1) + 1);
        } else
                ack();
 }
 
 /*
- * Check parent directory for write permission and create if it doesn't
- * exist.
+ * 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;
 {
  */
 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);
 }
 
@@ -796,55 +988,62 @@ chog(file, owner, group, mode)
        char *file, *owner, *group;
        int 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;
+               if (*group == ':') {
+                       gid = atoi(group + 1);
+                       goto ok;
+               }
        } else if ((mode & 04000) && strcmp(user, owner) != 0)
                mode &= ~04000;
        } else if ((mode & 04000) && strcmp(user, owner) != 0)
                mode &= ~04000;
-       gid = groupid;
+       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;
                mode &= ~02000;
                        if (!(strcmp(user, gr->gr_mem[i])))
                                goto ok;
                mode &= ~02000;
-               gid = groupid;
+               gid = -1;
        }
 ok:
        }
 ok:
-       if (chown(file, uid, gid) < 0) {
-               error("%s: %s\n", file, sys_errlist[errno]);
-               return(-1);
-       }
-       /*
-        * Restore set-user-id or set-group-id bit if appropriate.
-        */
-       if ((mode & 06000) && chmod(file, mode) < 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);
 }
 
@@ -888,9 +1087,9 @@ rmchk(opts)
                        (void) sprintf(tp, "/%s", s);
                        if (debug)
                                printf("check %s\n", target);
                        (void) sprintf(tp, "/%s", s);
                        if (debug)
                                printf("check %s\n", target);
-                       if (inlist(except, target))
+                       if (except(target))
                                (void) write(rem, "N\n", 2);
                                (void) write(rem, "N\n", 2);
-                       else if (stat(target, &stb) < 0)
+                       else if (lstat(target, &stb) < 0)
                                (void) write(rem, "Y\n", 2);
                        else
                                (void) write(rem, "N\n", 2);
                                (void) write(rem, "Y\n", 2);
                        else
                                (void) write(rem, "N\n", 2);
@@ -909,7 +1108,7 @@ rmchk(opts)
 
                case '\1':
                case '\2':
 
                case '\1':
                case '\2':
-                       errs++;
+                       nerrs++;
                        if (*s != '\n') {
                                if (!iamremote) {
                                        fflush(stdout);
                        if (*s != '\n') {
                                if (!iamremote) {
                                        fflush(stdout);
@@ -919,7 +1118,7 @@ rmchk(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:
@@ -949,8 +1148,8 @@ clean(cp)
                error("clean: options not delimited\n");
                return;
        }
                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 ((d = opendir(target)) == NULL) {
+               error("%s:%s: %s\n", host, target, strerror(errno));
                return;
        }
        ack();
                return;
        }
        ack();
@@ -961,7 +1160,8 @@ clean(cp)
                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;
@@ -970,8 +1170,8 @@ clean(cp)
                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;
                }
                (void) sprintf(buf, "Q%s\n", dp->d_name);
                        continue;
                }
                (void) sprintf(buf, "Q%s\n", dp->d_name);
@@ -979,7 +1179,7 @@ clean(cp)
                cp = buf;
                do {
                        if (read(rem, cp, 1) != 1)
                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;
@@ -988,7 +1188,7 @@ clean(cp)
                if (opts & VERIFY) {
                        cp = buf;
                        *cp++ = '\0';
                if (opts & VERIFY) {
                        cp = buf;
                        *cp++ = '\0';
-                       (void) sprintf(cp, "need to remove %s\n", target);
+                       (void) sprintf(cp, "need to remove: %s\n", target);
                        (void) write(rem, buf, strlen(cp) + 1);
                } else
                        remove(&stb);
                        (void) write(rem, buf, strlen(cp) + 1);
                } else
                        remove(&stb);
@@ -1004,8 +1204,8 @@ clean(cp)
  * 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;
@@ -1014,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;
@@ -1024,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;
@@ -1037,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;
@@ -1046,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);
@@ -1057,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:
@@ -1076,9 +1278,10 @@ dospecial(cmd)
        int fd[2], status, pid, i;
        register char *cp, *s;
        char sbuf[BUFSIZ];
        int fd[2], status, pid, i;
        register char *cp, *s;
        char sbuf[BUFSIZ];
+       extern int userid, groupid;
 
        if (pipe(fd) < 0) {
 
        if (pipe(fd) < 0) {
-               error("%s\n", sys_errlist[errno]);
+               error("%s\n", strerror(errno));
                return;
        }
        if ((pid = fork()) == 0) {
                return;
        }
        if ((pid = fork()) == 0) {
@@ -1088,12 +1291,14 @@ dospecial(cmd)
                (void) close(0);
                (void) close(1);
                (void) close(2);
                (void) close(0);
                (void) close(1);
                (void) close(2);
-               (void) open("/dev/null", 0);
+               (void) open(_PATH_DEVNULL, O_RDONLY);
                (void) dup(fd[1]);
                (void) dup(fd[1]);
                (void) close(fd[0]);
                (void) close(fd[1]);
                (void) dup(fd[1]);
                (void) dup(fd[1]);
                (void) close(fd[0]);
                (void) close(fd[1]);
-               execl("/bin/sh", "sh", "-c", cmd, 0);
+               setgid(groupid);
+               setuid(userid);
+               execl(_PATH_BSHELL, "sh", "-c", cmd, 0);
                _exit(127);
        }
        (void) close(fd[1]);
                _exit(127);
        }
        (void) close(fd[1]);
@@ -1127,6 +1332,7 @@ dospecial(cmd)
                ;
        if (i == -1)
                status = -1;
                ;
        if (i == -1)
                status = -1;
+       (void) close(fd[0]);
        if (status)
                error("shell returned %d\n", status);
        else
        if (status)
                error("shell returned %d\n", status);
        else
@@ -1153,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);
-       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));
-       } else
-               (void) write(rem, buf, strlen(buf));
-       if (lfp != NULL)
-               (void) fwrite(buf+1, 1, strlen(buf+1), lfp);
+               (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*/
 }
 
 /*VARARGS1*/
@@ -1170,31 +1387,43 @@ 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);
-       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));
-       } else
-               (void) write(rem, buf, strlen(buf));
-       if (lfp != NULL)
-               (void) fwrite(buf+1, 1, strlen(buf+1), lfp);
+               (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;
        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':
 
        switch (*s++) {
        case '\0':
@@ -1204,13 +1433,17 @@ response()
                        return(1);
                }
                return(0);
                        return(1);
                }
                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);
@@ -1219,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);
 }
 }