new version from Dave Hitz of Auspex
authorKeith Bostic <bostic@ucbvax.Berkeley.EDU>
Thu, 5 Jan 1989 07:25:42 +0000 (23:25 -0800)
committerKeith Bostic <bostic@ucbvax.Berkeley.EDU>
Thu, 5 Jan 1989 07:25:42 +0000 (23:25 -0800)
SCCS-vsn: bin/cp/cp.c 5.1

usr/src/bin/cp/cp.c

index 98872f0..f69e467 100644 (file)
 /*
 /*
- * Copyright (c) 1983 Regents of the University of California.
- * All rights reserved.  The Berkeley software License Agreement
- * specifies the terms and conditions for redistribution.
+ * Cp copies source files to target files.
+ * 
+ * The global path_t structures "to" and "from" always contain paths to the
+ * current source and target files, respectively.  Since cp does not
+ * change directories, these paths can be either absolute or
+ * dot-realative.
+ * 
+ * The basic algorithm is to initialize "to" and "from", and then call the
+ * recursive copy() function to do the actual work.  If "from" is a file,
+ * copy copies the data.  If "from" is a directory, copy creates the
+ * corresponding "to" directory, and calls itself recursively on all of
+ * the entries in the "from" directory.
+ * 
+ * Instead of handling directory entires in the order they appear one disk,
+ * copy() does non-directory files before directory files.
+ * 
+ * There are two reasons to do directories last. The first is efficiency.
+ * Files tend to be in the same cylinder group as their parent, whereas
+ * directories tend not to be. Copying files all at once reduces seeking.
+ * 
+ * Second, deeply nested tree's could use up all the file descriptors if we
+ * didn't close one directory before recursivly starting on the next.
  */
 
  */
 
-#ifndef lint
-char copyright[] =
-"@(#) Copyright (c) 1983 Regents of the University of California.\n\
- All rights reserved.\n";
-#endif not lint
-
-#ifndef lint
-static char sccsid[] = "@(#)cp.c       4.14 (Berkeley) %G%";
-#endif not lint
-
-/*
- * cp
- */
 #include <stdio.h>
 #include <stdio.h>
-#include <sys/param.h>
+#include <errno.h>
+#include <strings.h>
+
+#include <sys/types.h>
 #include <sys/stat.h>
 #include <sys/stat.h>
+#include <sys/file.h>
 #include <sys/dir.h>
 #include <sys/time.h>
 
 #include <sys/dir.h>
 #include <sys/time.h>
 
-int    iflag;
-int    rflag;
-int    pflag;
-char   *rindex();
+#include <sys/param.h>
+
+
+#define TRUE   (1)
+#define FALSE  (0)
+
+typedef struct {
+    char           *p_path;            /* Pointer to the start of a path. */
+    char           *p_end;             /* Pointer to NULL at end of path. */
+}               path_t;
+
+int             path_set();
+char           *path_append();
+char           *path_basename();
+void           path_restore();
+
+
+int             exit_val;
+int             my_umask;
+int             interactive_flag,
+                preserve_flag,
+                recursive_flag;
+
+char           *buf;           /* I/O buffer -- malloc for best alignment. */
+
+char            from_buf[MAXPATHLEN + 1],      /* Source path buffer. */
+                to_buf[MAXPATHLEN + 1];                /* Target path buffer. */
+
+path_t          from = {from_buf, from_buf};
+path_t          to = {to_buf, to_buf};
+
+
+
+usage()
+{
+    (void) fprintf(stderr,
+                  "usage: cp [-irp] f1 f2; or: cp [-irp] f1 ... fn d\n");
+    exit(1);
+}
+
+
 
 main(argc, argv)
 
 main(argc, argv)
-       int argc;
-       char **argv;
+    int             argc;
+    char          **argv;
 {
 {
-       struct stat stb;
-       int rc, i;
-
-       argc--, argv++;
-       while (argc > 0 && **argv == '-') {
-               (*argv)++;
-               while (**argv) switch (*(*argv)++) {
-
-               case 'i':
-                       iflag++; break;
-
-               case 'R':
-               case 'r':
-                       rflag++; break;
-
-               case 'p':       /* preserve mtimes, atimes, and modes */
-                       pflag++;
-                       (void) umask(0);
-                       break;
-
-               default:
-                       goto usage;
-               }
-               argc--; argv++;
-       }
-       if (argc < 2) 
-               goto usage;
-       if (argc > 2) {
-               if (stat(argv[argc-1], &stb) < 0)
-                       goto usage;
-               if ((stb.st_mode&S_IFMT) != S_IFDIR) 
-                       goto usage;
+    extern int      optind;
+    extern int      opterr;
+    extern char    *optarg;
+    struct stat     to_stat;
+    char           *old_to;
+    int             c;
+    int             r;
+
+    opterr = 0;
+    while ((c = getopt(argc, argv, "Ripr")) != EOF) {
+       switch ((char) c) {
+       case 'i':
+           interactive_flag = isatty(fileno(stdin));
+           break;
+       case 'p':
+           preserve_flag = 1;
+           (void) umask(0);
+           break;
+       case 'R':
+       case 'r':
+           recursive_flag = 1;
+           break;
+       case '?':
+       default:
+           usage();
+           break;
        }
        }
-       rc = 0;
-       for (i = 0; i < argc-1; i++)
-               rc |= copy(argv[i], argv[argc-1]);
-       exit(rc);
-usage:
-       fprintf(stderr,
-           "Usage: cp [-ip] f1 f2; or: cp [-irp] f1 ... fn d2\n");
+    }
+
+    argc -= optind;            /* argc is count of remaining arguments. */
+    argv += optind;            /* argv[0] is first remaining argument. */
+
+    if (argc < 2) {
+       usage();
+    }
+
+    my_umask = umask(0);
+    (void) umask(my_umask);
+
+    buf = (char *) malloc(MAXBSIZE);
+    if (!buf) {
+       fprintf(stderr, "cp: Can't allocate memory for I/O buffer.\n");
+       exit(1);
+    }
+
+    /*
+     * Consume last argument first.
+     */
+    if ( !path_set(&to, argv[--argc]) )
+       exit(exit_val);
+
+    /*
+     * Cp has two distinct cases:
+     * 
+     * Case (1)          $ cp [-rip] source target
+     * 
+     * Case (2)          $ cp [-rip] source1 ... directory
+     * 
+     * In both cases, source can be either a file or a directory.
+     * 
+     * In (1), the target becomes a copy of the source. That is, if the
+     * source is a file, the target will be a file, and likewise for
+     * directories.
+     * 
+     * In (2), the real target is not directory, but "directory/source".
+     */
+    r = stat(to.p_path, &to_stat);
+    if (r == -1 && errno != ENOENT) {
+       error(to.p_path);
        exit(1);
        exit(1);
+    }
+
+    if (r == -1 || (to_stat.st_mode & S_IFMT) != S_IFDIR) {
+        /*
+         * Case (1).  Target is not a directory.
+         */
+       if (argc > 1) {
+           usage();
+           exit(1);
+       }
+
+       if ( !path_set(&from, *argv))
+           exit(exit_val);
+
+       copy();
+    }
+    else {
+       /*
+        * Case (2).  Target is a directory.
+        */
+       for (; argc; --argc, ++argv) {
+           if ( !path_set(&from, *argv) )
+               continue;
+
+           old_to = path_append(&to, path_basename(&from), -1);
+           if ( !old_to )
+               continue;
+
+           copy();
+           path_restore(&to, old_to);
+       }
+    }
+    exit(exit_val);
 }
 
 }
 
-                       /* I/O buffer; guarantee long-word alignment */
-static char    buf[MAXBSIZE];
 
 
-copy(from, to)
-       char *from, *to;
+
+/*
+ * Copy file or directory at "from" to "to".
+ */
+copy()
 {
 {
-       int fold, fnew, n, exists;
-       char *last, destname[MAXPATHLEN + 1];
-       struct stat stfrom, stto;
-
-       fold = open(from, 0);
-       if (fold < 0) {
-               Perror(from);
-               return (1);
+    struct stat     from_stat;
+    struct stat     to_stat;
+    int             new_target_dir = 0;
+
+    if (stat(from.p_path, &from_stat) == -1) {
+       error(from.p_path);
+       return;
+    }
+
+    if (stat(to.p_path, &to_stat) == -1) {
+       /*
+        * This is not an error, but we need to remember that it happened.
+        */
+       to_stat.st_ino = -1;
+    }
+    else {
+       if (to_stat.st_dev == from_stat.st_dev && 
+               to_stat.st_ino == from_stat.st_ino) {
+           fprintf(stderr, 
+               "cp: \"%s\" and \"%s\" are identical (not copied).\n",
+               to.p_path, from.p_path);
+           exit_val = 1;
+           return;
        }
        }
-       if (fstat(fold, &stfrom) < 0) {
-               Perror(from);
-               (void) close(fold);
-               return (1);
+    }
+
+    if ((from_stat.st_mode & S_IFMT) != S_IFDIR) {
+       if (!copy_file(from_stat.st_mode))
+           return;
+    }
+    else {
+       if (!recursive_flag) {
+           (void) fprintf(stderr, 
+                          "cp: \"%s\" is a directory (not copied).\n",
+                          from.p_path);
+           exit_val = 1;
+           return;
        }
        }
-       if (stat(to, &stto) >= 0 &&
-          (stto.st_mode&S_IFMT) == S_IFDIR) {
-               last = rindex(from, '/');
-               if (last) last++; else last = from;
-               if (strlen(to) + strlen(last) >= sizeof destname - 1) {
-                       fprintf(stderr, "cp: %s/%s: Name too long", to, last);
-                       (void) close(fold);
-                       return(1);
-               }
-               (void) sprintf(destname, "%s/%s", to, last);
-               to = destname;
+
+       if (to_stat.st_ino == -1) {
+           if (mkdir(to.p_path, 0777) < 0) {
+               error(to.p_path);
+               return;
+           }
+           new_target_dir = TRUE;
        }
        }
-       if (rflag && (stfrom.st_mode&S_IFMT) == S_IFDIR) {
-               int fixmode = 0;        /* cleanup mode after rcopy */
-
-               (void) close(fold);
-               if (stat(to, &stto) < 0) {
-                       if (mkdir(to, (stfrom.st_mode & 07777) | 0700) < 0) {
-                               Perror(to);
-                               return (1);
-                       }
-                       fixmode = 1;
-               } else if ((stto.st_mode&S_IFMT) != S_IFDIR) {
-                       fprintf(stderr, "cp: %s: Not a directory.\n", to);
-                       return (1);
-               } else if (pflag)
-                       fixmode = 1;
-               n = rcopy(from, to);
-               if (fixmode)
-                       (void) chmod(to, stfrom.st_mode & 07777);
-               return (n);
+       else if ((to_stat.st_mode & S_IFMT) != S_IFDIR) {
+           (void) fprintf(stderr, 
+                          "cp: \"%s\": not a directory.\n",
+                          to.p_path);
+           return;
        }
 
        }
 
-       if ((stfrom.st_mode&S_IFMT) == S_IFDIR)
-               fprintf(stderr,
-                       "cp: %s: Is a directory (copying as plain file).\n",
-                               from);
-
-       exists = stat(to, &stto) == 0;
-       if (exists) {
-               if (stfrom.st_dev == stto.st_dev &&
-                  stfrom.st_ino == stto.st_ino) {
-                       fprintf(stderr,
-                               "cp: %s and %s are identical (not copied).\n",
-                                       from, to);
-                       (void) close(fold);
-                       return (1);
-               }
-               if (iflag && isatty(fileno(stdin))) {
-                       int i, c;
-
-                       fprintf (stderr, "overwrite %s? ", to);
-                       i = c = getchar();
-                       while (c != '\n' && c != EOF)
-                               c = getchar();
-                       if (i != 'y') {
-                               (void) close(fold);
-                               return(1);
-                       }
-               }
-       }
-       fnew = creat(to, stfrom.st_mode & 07777);
-       if (fnew < 0) {
-               Perror(to);
-               (void) close(fold); return(1);
+       copy_dir();
+    }
+
+
+    /*
+     * Preserve old times/modes if necessary.
+     */
+    if (preserve_flag)
+       (void) chmod(to.p_path, (int) from_stat.st_mode);
+    else if (new_target_dir)
+       (void) chmod(to.p_path, (int) from_stat.st_mode & ~my_umask);
+
+    if (preserve_flag || new_target_dir) {
+       static struct timeval tv[2];
+
+       tv[0].tv_sec = from_stat.st_atime;
+       tv[1].tv_sec = from_stat.st_mtime;
+       if (utimes(to.p_path, tv)) {
+           error(to.p_path);
        }
        }
-       if (exists && pflag)
-               (void) fchmod(fnew, stfrom.st_mode & 07777);
-                       
-       for (;;) {
-               n = read(fold, buf, sizeof buf);
-               if (n == 0)
-                       break;
-               if (n < 0) {
-                       Perror(from);
-                       (void) close(fold); (void) close(fnew); return (1);
-               }
-               if (write(fnew, buf, n) != n) {
-                       Perror(to);
-                       (void) close(fold); (void) close(fnew); return (1);
-               }
+    }
+}
+
+
+
+copy_file(mode)
+    u_short         mode;      /* Permissions for new file. */
+{
+    int             from_fd;
+    int             to_fd;
+    int             rcount;
+    int             wcount;
+    int             r;
+    char            c;
+
+    from_fd = open(from.p_path, O_RDONLY, 0);
+    if (from_fd == -1) {
+       error(from.p_path);
+       (void) close(from_fd);
+       return 0;
+    }
+
+    /*
+     * In the interactive case, use O_EXCL to notice existing files. If
+     * the file exists, verify with the user.
+     */
+    to_fd = open(to.p_path,
+            (interactive_flag ? O_EXCL : 0) | O_WRONLY | O_CREAT | O_TRUNC,
+                mode);
+
+    if (to_fd == -1 && errno == EEXIST && interactive_flag) {
+       (void) fprintf(stderr, "overwrite \"%s\"? ", to.p_path);
+       r = scanf("%1s", &c);
+       if (r != 1 || c != 'y')
+           return FALSE;
+
+       /* Try again. */
+       to_fd = open(to.p_path, O_WRONLY | O_CREAT | O_TRUNC, mode);
+    }
+
+    if (to_fd == -1) {
+       error(to.p_path);
+       (void) close(from_fd);
+       return FALSE;
+    }
+
+    while ((rcount = read(from_fd, buf, MAXBSIZE)) > 0) {
+       wcount = write(to_fd, buf, rcount);
+       if (rcount != wcount || wcount == -1) {
+           error(from.p_path);
+           break;
        }
        }
-       (void) close(fold); (void) close(fnew); 
-       if (pflag)
-               return (setimes(to, &stfrom));
-       return (0);
+    }
+
+    (void) close(from_fd);
+    (void) close(to_fd);
+    return TRUE;
 }
 
 }
 
-rcopy(from, to)
-       char *from, *to;
+
+
+copy_dir()
 {
 {
-       DIR *fold = opendir(from);
-       struct direct *dp;
-       struct stat statb;
-       int errs = 0;
-       char fromname[MAXPATHLEN + 1];
-
-       if (fold == 0 || (pflag && fstat(fold->dd_fd, &statb) < 0)) {
-               Perror(from);
-               return (1);
+    struct stat     from_stat;
+    char           *old_from;
+    char           *old_to;
+    struct direct  *dp;
+    struct direct **dir_list;
+    int             dir_cnt;
+    int             i;
+
+    dir_cnt = scandir(from.p_path, &dir_list, NULL, NULL);
+    if (dir_cnt == -1) {
+       (void) fprintf(stderr, "cp: Can't read directory \"%s\".\n",
+                      from.p_path);
+       exit_val = 1;
+    }
+
+    /*
+     * Copy files first.
+     */
+    for (i = 0; i < dir_cnt; ++i) {
+       dp = dir_list[i];
+
+       if (dp->d_namlen <= 2 && dp->d_name[0] == '.'
+           && (dp->d_name[1] == NULL || dp->d_name[1] == '.')) {
+           free((char *) dp);
+           dir_list[i] = NULL;
+           continue;
+       }
+
+       old_from = path_append(&from, dp->d_name, (int) dp->d_namlen);
+       if ( !old_from ) {
+           dir_list[i] = NULL;
+           free((char *) dp);
+           continue;
+       }
+
+       if (stat(from.p_path, &from_stat) < 0) {
+           error(dp->d_name);
+           path_restore(&from, old_from);
+           continue;
+       }
+
+       if ((from_stat.st_mode & S_IFMT) != S_IFDIR) {
+           old_to = path_append(&to, dp->d_name, (int) dp->d_namlen);
+           if ( !old_to ) {
+               dir_list[i] = NULL;
+               free((char *) dp);
+               continue;
+           }
+
+           copy();
+
+           path_restore(&to, old_to);
+
+           dir_list[i] = NULL;
+           free((char *) dp);
+       }
+       path_restore(&from, old_from);
+    }
+
+    /*
+     * Then copy directories.
+     */
+    for (i = 0; i < dir_cnt; ++i) {
+       dp = dir_list[i];
+       if (!dp)
+           continue;
+
+       old_from = path_append(&from, dp->d_name, (int) dp->d_namlen);
+       if ( !old_from ) {
+           free((char *) dp);
+           continue;
        }
        }
-       for (;;) {
-               dp = readdir(fold);
-               if (dp == 0) {
-                       closedir(fold);
-                       if (pflag)
-                               return (setimes(to, &statb) + errs);
-                       return (errs);
-               }
-               if (dp->d_ino == 0)
-                       continue;
-               if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, ".."))
-                       continue;
-               if (strlen(from)+1+strlen(dp->d_name) >= sizeof fromname - 1) {
-                       fprintf(stderr, "cp: %s/%s: Name too long.\n",
-                           from, dp->d_name);
-                       errs++;
-                       continue;
-               }
-               (void) sprintf(fromname, "%s/%s", from, dp->d_name);
-               errs += copy(fromname, to);
+
+       old_to = path_append(&to, dp->d_name, (int) dp->d_namlen);
+       if ( !old_to ) {
+           free((char *) dp);
+           path_restore(&from, old_from);
+           continue;
        }
        }
+
+       copy();
+       free((char *) dp);
+
+       path_restore(&from, old_from);
+       path_restore(&to, old_to);
+    }
+
+    free((char *) dir_list);
 }
 
 }
 
-int
-setimes(path, statp)
-       char *path;
-       struct stat *statp;
+
+
+error(s)
+    char           *s;
 {
 {
-       struct timeval tv[2];
-       
-       tv[0].tv_sec = statp->st_atime;
-       tv[1].tv_sec = statp->st_mtime;
-       tv[0].tv_usec = tv[1].tv_usec = 0;
-       if (utimes(path, tv) < 0) {
-               Perror(path);
-               return (1);
-       }
-       return (0);
+    exit_val = 1;
+    (void) fprintf(stderr, "cp: ");
+    perror(s);
 }
 
 }
 
-Perror(s)
-       char *s;
+
+
+/********************************************************************
+ * Path Manipulation Routines.
+ ********************************************************************/
+
+/*
+ * These functions manipulate paths in "path_t" structures.
+ * 
+ * They eliminate multiple slashes in paths when they notice them, and keep
+ * the path non-slash terminated.
+ *
+ * Both path_set() and path_append() return FALSE if the requested name
+ * would be too long.
+ */
+
+#define STRIP_TRAILING_SLASH(p) \
+    while ((p)->p_end > (p)->p_path && (p)->p_end[-1] == '/') \
+       { *--(p)->p_end = 0; };
+
+/*
+ * Move specified string into path.  Convert "" to "." to handle BSD
+ * semantics for a null path.  Strip trailing slashes.
+ */
+path_set(p, string)
+    path_t         *p;
+    char           *string;
+{
+    int len;
+
+    if (strlen(string) > MAXPATHLEN) {
+       fprintf(stderr, "cp: \"%s\": Name too long.\n", string);
+       exit_val = 1;
+       return FALSE;
+    }
+
+    (void) strcpy(p->p_path, string);
+    p->p_end = p->p_path + strlen(p->p_path);
+
+    if (p->p_path == p->p_end) {
+       *p->p_end++ = '.';
+       *p->p_end = 0;
+    }
+
+    STRIP_TRAILING_SLASH(p);
+    
+    return TRUE;
+}
+
+/*
+ * Append specified string to path, inserting '/' if necessary.  Return a
+ * pointer to the old end of path for restoration.
+ */
+char           *
+path_append(p, name, len)
+    path_t         *p;
+    char           *name;
+    int             len;
 {
 {
+    char           *old;
+
+    old = p->p_end;
+
+    if (len == -1)
+       len = strlen(name);
+
+    /*
+     * The final "+ 1" accounts for the '/' between old path and name.
+     */
+    if ( (len + p->p_end - p->p_path + 1) > MAXPATHLEN ) {
+       fprintf(stderr, "cp: \"%s/%s\": Name too long.\n", p->p_path, name);
+       exit_val = 1;
+       return FALSE;
+    }
+
+    /*
+     * This code should always be executed, since paths shouldn't
+     * end in '/'.
+     */
+    if (p->p_end[-1] != '/') {
+       *p->p_end++ = '/';
+       *p->p_end = 0;
+    }
+
+    (void) strncat(p->p_end, name, len);
+    p->p_end += len;
+    *p->p_end = 0;
+
+    STRIP_TRAILING_SLASH(p);
+
+    return old;
+}
+
+
+/*
+ * Restore path to previous value.  (As returned by path_append.)
+ */
+void
+path_restore(p, old)
+    path_t         *p;
+    char           *old;
+{
+    p->p_end = old;
+    *p->p_end = 0;
+}
+
+
+/*
+ * Return basename of path.  (Like basename(1).)
+ */
+char           *
+path_basename(p)
+    path_t         *p;
+{
+    char           *basename;
+
+    basename = rindex(p->p_path, '/');
+
+    if (!basename)
+       basename = p->p_path;
 
 
-       fprintf(stderr, "cp: ");
-       perror(s);
+    return basename;
 }
 }