fix error message when recv() can't write new file.
[unix-history] / usr / src / usr.bin / rdist / server.c
index 00b34af..f35ca2b 100644 (file)
@@ -1,5 +1,5 @@
 #ifndef lint
 #ifndef lint
-static char *sccsid = "@(#)server.c    4.10 (Berkeley) 83/12/01";
+static char *sccsid = "@(#)server.c    4.23 (Berkeley) 85/02/14";
 #endif
 
 #include "defs.h"
 #endif
 
 #include "defs.h"
@@ -7,6 +7,7 @@ static  char *sccsid = "@(#)server.c    4.10 (Berkeley) 83/12/01";
 #define        ack()   (void) write(rem, "\0\n", 2)
 #define        err()   (void) write(rem, "\1\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 */
@@ -16,6 +17,9 @@ int   oumask;                 /* old umask for creating files */
 
 extern FILE *lfp;              /* log file for mailing changes */
 
 
 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:
@@ -27,9 +31,14 @@ 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);
        (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));
@@ -44,7 +53,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;
@@ -64,12 +73,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;
+
+               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) */
@@ -139,7 +156,7 @@ server()
 #endif
 
                case '\1':
 #endif
 
                case '\1':
-                       errs++;
+                       nerrs++;
                        continue;
 
                case '\2':
                        continue;
 
                case '\2':
@@ -224,24 +241,29 @@ sendf(rname, opts)
        char *rname;
        int opts;
 {
        char *rname;
        int opts;
 {
-       register char *cp;
-       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;
 
        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) {
+       if ((opts & FOLLOW ? stat(target, &stb) : lstat(target, &stb)) < 0) {
                error("%s: %s\n", target, sys_errlist[errno]);
                return;
        }
                error("%s: %s\n", target, sys_errlist[errno]);
                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) {
@@ -264,19 +286,72 @@ 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, sys_errlist[errno]);
+                       return;
+               }
+               (void) sprintf(buf, "D%o %04o 0 0 %s %s %s\n", opts,
+                       stb.st_mode & 07777, pw->pw_name, gr->gr_name, 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;
+               (void) sprintf(buf, "K%o %o %ld %ld %s %s %s\n", opts,
+                       stb.st_mode & 07777, stb.st_size, stb.st_mtime,
+                       pw->pw_name, gr->gr_name, 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", 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;
        }
 
@@ -287,14 +362,27 @@ 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 */
+                       (void) sprintf(buf, "k%o %s %s\n", opts,
+                               lp->pathname, rname);
+                       if (debug)
+                               printf("buf = %s", buf);
+                       (void) write(rem, buf, strlen(buf));
+                       (void) response();
+                       return;
+               }
+       }
 
        if ((f = open(target, 0)) < 0) {
                error("%s: %s\n", target, sys_errlist[errno]);
                return;
        }
 
        if ((f = open(target, 0)) < 0) {
                error("%s: %s\n", target, sys_errlist[errno]);
                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,
                pw->pw_name, gr->gr_name, rname);
        if (debug)
                stb.st_mode & 07777, stb.st_size, stb.st_mtime,
                pw->pw_name, gr->gr_name, rname);
        if (debug)
@@ -314,23 +402,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));
@@ -339,60 +429,30 @@ 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);
        }
        }
-       closedir(d);
-       (void) write(rem, "E\n", 2);
-       (void) response();
-       tp = otp;
-       *tp = '\0';
+       return(NULL);
 }
 
 /*
 }
 
 /*
@@ -400,17 +460,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.
@@ -433,7 +493,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);
@@ -445,9 +505,8 @@ update(rname, opts, st)
                return(0);
 
        default:
                return(0);
 
        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", buf);
                return(0);
        }
 
                return(0);
        }
 
@@ -475,13 +534,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);
 }
@@ -490,7 +549,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)
@@ -501,40 +560,44 @@ 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, sys_errlist[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];
        extern char *tmpname;
 
        char new[BUFSIZ];
        extern char *tmpname;
 
@@ -584,9 +647,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;
@@ -600,53 +664,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)) {
-                               ack();
+                               if ((stb.st_mode & 07777) == mode) {
+                                       ack();
+                                       return;
+                               }
+                               buf[0] = '\0';
+                               (void) sprintf(buf + 1,
+                                       "%s:%s: Warning: remote mode %o != local mode %o\n",
+                                       host, target, stb.st_mode & 07777, mode);
+                               (void) write(rem, buf, strlen(buf + 1) + 1);
                                return;
                        }
                                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, sys_errlist[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) {
-               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, tmpname);
+       else if (cp == target)
+               (void) sprintf(new, "/%s", tmpname);
+       else {
                *cp = '\0';
                *cp = '\0';
-       }
-       (void) sprintf(new, "%s/%s", dir, tmpname);
-       if (cp != NULL)
+               (void) sprintf(new, "%s/%s", target, tmpname);
                *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;
@@ -674,9 +773,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, sys_errlist[olderrno]);
                (void) unlink(new);
                return;
        }
                (void) unlink(new);
                return;
        }
@@ -685,9 +788,9 @@ recvf(cmd, isdir)
                int c;
 
                if ((f1 = fopen(target, "r")) == NULL)
                int c;
 
                if ((f1 = fopen(target, "r")) == NULL)
-                       goto bad;
+                       goto badt;
                if ((f2 = fopen(new, "r")) == NULL)
                if ((f2 = fopen(new, "r")) == NULL)
-                       goto bad1;
+                       goto badn;
                while ((c = getc(f1)) == getc(f2))
                        if (c == EOF) {
                                (void) fclose(f1);
                while ((c = getc(f1)) == getc(f2))
                        if (c == EOF) {
                                (void) fclose(f1);
@@ -699,9 +802,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';
-                       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;
                }
@@ -710,13 +814,13 @@ 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]);
+badn:
+               error("%s:%s: %s\n", host, new, sys_errlist[errno]);
                (void) unlink(new);
                return;
        }
                (void) unlink(new);
                return;
        }
@@ -724,66 +828,91 @@ bad1:
                (void) unlink(new);
                return;
        }
                (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, sys_errlist[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, "%s: updated %s\n", host, 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) {
+               if ((stb.st_mode & S_IFMT) != S_IFREG) {
+                       error("%s:%s: not a regular file\n", host, target);
+                       return;
+               }
+               exists = 1;
+       }
+       if (chkparent(target) < 0 ||
+           exists && unlink(target) < 0 ||
+           link(oldname, target) < 0) {
+               error("%s:%s: %s\n", host, target, sys_errlist[errno]);
+               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);
 }
 
@@ -794,17 +923,18 @@ 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) {
                if (pw == NULL || strcmp(owner, pw->pw_name) != 0) {
                        if ((pw = getpwnam(owner)) == NULL) {
                                if (mode & 04000) {
 
        uid = userid;
        if (userid == 0) {
                if (pw == NULL || strcmp(owner, pw->pw_name) != 0) {
                        if ((pw = getpwnam(owner)) == NULL) {
                                if (mode & 04000) {
-                                       error("%s: unknown login name\n", owner);
+                                       error("%s:%s: unknown login name\n",
+                                               host, owner);
                                        return(-1);
                                }
                        } else
                                        return(-1);
                                }
                        } else
@@ -813,36 +943,36 @@ chog(file, owner, group, mode)
                        uid = pw->pw_uid;
        } else if ((mode & 04000) && strcmp(user, owner) != 0)
                mode &= ~04000;
                        uid = pw->pw_uid;
        } 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 = getgrnam(group)) == NULL) {
                        if (mode & 02000) {
        if (gr == NULL || strcmp(group, gr->gr_name) != 0) {
                if ((gr = getgrnam(group)) == NULL) {
                        if (mode & 02000) {
-                               error("%s: unknown group\n", group);
+                               error("%s:%s: unknown group\n", host, group);
                                return(-1);
                        }
                } else
                        gid = gr->gr_gid;
        } else
                gid = gr->gr_gid;
                                return(-1);
                        }
                } else
                        gid = gr->gr_gid;
        } else
                gid = gr->gr_gid;
-       if (userid && groupid != gid) {
+       if (userid && gid >= 0) {
                for (i = 0; gr->gr_mem[i] != NULL; i++)
                        if (!(strcmp(user, gr->gr_mem[i])))
                                goto ok;
                mode &= ~02000;
                for (i = 0; gr->gr_mem[i] != NULL; i++)
                        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]);
+       if (userid)
+               setreuid(userid, 0);
+       if (chown(file, uid, gid) < 0 ||
+           (mode & 06000) && chmod(file, mode) < 0) {
+               if (userid)
+                       setreuid(0, userid);
+               error("%s:%s: %s\n", host, file, sys_errlist[errno]);
                return(-1);
        }
                return(-1);
        }
+       if (userid)
+               setreuid(0, userid);
        return(0);
 }
 
        return(0);
 }
 
@@ -886,9 +1016,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);
@@ -907,7 +1037,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);
@@ -917,7 +1047,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:
@@ -931,11 +1061,11 @@ rmchk(opts)
  * Check the current directory (initialized by the 'T' command to server())
  * for extraneous files and remove them.
  */
  * Check the current directory (initialized by the 'T' command to server())
  * for extraneous files and remove them.
  */
-clean(opts)
+clean(cp)
+       register char *cp;
 {
        DIR *d;
        register struct direct *dp;
 {
        DIR *d;
        register struct direct *dp;
-       register char *cp;
        struct stat stb;
        char *otp;
        int len, opts;
        struct stat stb;
        char *otp;
        int len, opts;
@@ -947,8 +1077,8 @@ clean(opts)
                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, sys_errlist[errno]);
                return;
        }
        ack();
                return;
        }
        ack();
@@ -959,7 +1089,8 @@ clean(opts)
                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;
@@ -968,8 +1099,8 @@ clean(opts)
                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, sys_errlist[errno]);
                        continue;
                }
                (void) sprintf(buf, "Q%s\n", dp->d_name);
                        continue;
                }
                (void) sprintf(buf, "Q%s\n", dp->d_name);
@@ -977,7 +1108,7 @@ clean(opts)
                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;
@@ -986,7 +1117,7 @@ clean(opts)
                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);
@@ -1002,8 +1133,8 @@ clean(opts)
  * 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;
@@ -1012,8 +1143,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;
@@ -1022,11 +1154,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;
@@ -1035,7 +1167,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;
@@ -1044,8 +1177,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, sys_errlist[errno]);
                        continue;
                }
                remove(&stb);
                        continue;
                }
                remove(&stb);
@@ -1055,7 +1188,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, sys_errlist[errno]);
                return;
        }
 removed:
                return;
        }
 removed:
@@ -1074,6 +1207,7 @@ 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) {
                error("%s\n", sys_errlist[errno]);
 
        if (pipe(fd) < 0) {
                error("%s\n", sys_errlist[errno]);
@@ -1091,6 +1225,8 @@ dospecial(cmd)
                (void) dup(fd[1]);
                (void) close(fd[0]);
                (void) close(fd[1]);
                (void) dup(fd[1]);
                (void) close(fd[0]);
                (void) close(fd[1]);
+               setgid(groupid);
+               setuid(userid);
                execl("/bin/sh", "sh", "-c", cmd, 0);
                _exit(127);
        }
                execl("/bin/sh", "sh", "-c", cmd, 0);
                _exit(127);
        }
@@ -1125,6 +1261,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
@@ -1151,7 +1288,7 @@ error(fmt, a1, a2, a3)
        char *fmt;
        int a1, a2, a3;
 {
        char *fmt;
        int a1, a2, a3;
 {
-       errs++;
+       nerrs++;
        strcpy(buf, "\1rdist: ");
        (void) sprintf(buf+8, fmt, a1, a2, a3);
        if (!iamremote) {
        strcpy(buf, "\1rdist: ");
        (void) sprintf(buf+8, fmt, a1, a2, a3);
        if (!iamremote) {
@@ -1168,7 +1305,7 @@ fatal(fmt, a1, a2,a3)
        char *fmt;
        int a1, a2, a3;
 {
        char *fmt;
        int a1, a2, a3;
 {
-       errs++;
+       nerrs++;
        strcpy(buf, "\2rdist: ");
        (void) sprintf(buf+8, fmt, a1, a2, a3);
        if (!iamremote) {
        strcpy(buf, "\2rdist: ");
        (void) sprintf(buf+8, fmt, a1, a2, a3);
        if (!iamremote) {
@@ -1184,15 +1321,16 @@ fatal(fmt, a1, a2,a3)
 response()
 {
        char *cp, *s;
 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':
@@ -1208,7 +1346,7 @@ response()
                /* fall into... */
        case '\1':
        case '\2':
                /* fall into... */
        case '\1':
        case '\2':
-               errs++;
+               nerrs++;
                if (*s != '\n') {
                        if (!iamremote) {
                                fflush(stdout);
                if (*s != '\n') {
                        if (!iamremote) {
                                fflush(stdout);
@@ -1217,17 +1355,17 @@ 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(tmpfile);
+       exit(1);
 }
 }