| /* $NetBSD: cp.c,v 1.58 2012/01/04 15:58:37 christos Exp $ */ |
| |
| /* |
| * Copyright (c) 1988, 1993, 1994 |
| * The Regents of the University of California. All rights reserved. |
| * |
| * This code is derived from software contributed to Berkeley by |
| * David Hitz of Auspex Systems Inc. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * 3. Neither the name of the University nor the names of its contributors |
| * may be used to endorse or promote products derived from this software |
| * without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND |
| * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE |
| * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
| * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS |
| * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
| * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
| * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
| * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
| * SUCH DAMAGE. |
| */ |
| |
| #include <sys/cdefs.h> |
| #ifndef lint |
| __COPYRIGHT( |
| "@(#) Copyright (c) 1988, 1993, 1994\ |
| The Regents of the University of California. All rights reserved."); |
| #endif /* not lint */ |
| |
| #ifndef lint |
| #if 0 |
| static char sccsid[] = "@(#)cp.c 8.5 (Berkeley) 4/29/95"; |
| #else |
| __RCSID("$NetBSD: cp.c,v 1.58 2012/01/04 15:58:37 christos Exp $"); |
| #endif |
| #endif /* not lint */ |
| |
| /* |
| * Cp copies source files to target files. |
| * |
| * The global PATH_T structure "to" always contains the path to the |
| * current target file. Since fts(3) does not change directories, |
| * this path can be either absolute or dot-relative. |
| * |
| * The basic algorithm is to initialize "to" and use fts(3) to traverse |
| * the file hierarchy rooted in the argument list. A trivial case is the |
| * case of 'cp file1 file2'. The more interesting case is the case of |
| * 'cp file1 file2 ... fileN dir' where the hierarchy is traversed and the |
| * path (relative to the root of the traversal) is appended to dir (stored |
| * in "to") to form the final target path. |
| */ |
| |
| #include <sys/param.h> |
| #include <sys/stat.h> |
| |
| #include <assert.h> |
| #include <err.h> |
| #include <errno.h> |
| #include <fts.h> |
| #include <locale.h> |
| #include <signal.h> |
| #include <stdlib.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <unistd.h> |
| |
| #include "extern.h" |
| |
| #define STRIP_TRAILING_SLASH(p) { \ |
| while ((p).p_end > (p).p_path + 1 && (p).p_end[-1] == '/') \ |
| *--(p).p_end = '\0'; \ |
| } |
| |
| static char empty[] = ""; |
| PATH_T to = { .p_end = to.p_path, .target_end = empty }; |
| |
| uid_t myuid; |
| int Hflag, Lflag, Rflag, Pflag, fflag, iflag, lflag, pflag, rflag, vflag, Nflag; |
| mode_t myumask; |
| sig_atomic_t pinfo; |
| |
| enum op { FILE_TO_FILE, FILE_TO_DIR, DIR_TO_DNE }; |
| |
| static int copy(char *[], enum op, int); |
| |
| #ifndef ANDROID |
| static void |
| progress(int sig __unused) |
| { |
| |
| pinfo++; |
| } |
| #endif |
| |
| int |
| cp_main(int argc, char *argv[]) |
| { |
| struct stat to_stat, tmp_stat; |
| enum op type; |
| int ch, fts_options, r, have_trailing_slash; |
| char *target, **src; |
| |
| #ifndef ANDROID |
| setprogname(argv[0]); |
| #endif |
| (void)setlocale(LC_ALL, ""); |
| |
| Hflag = Lflag = Pflag = Rflag = 0; |
| while ((ch = getopt(argc, argv, "HLNPRfailprv")) != -1) |
| switch (ch) { |
| case 'H': |
| Hflag = 1; |
| Lflag = Pflag = 0; |
| break; |
| case 'L': |
| Lflag = 1; |
| Hflag = Pflag = 0; |
| break; |
| case 'N': |
| Nflag = 1; |
| break; |
| case 'P': |
| Pflag = 1; |
| Hflag = Lflag = 0; |
| break; |
| case 'R': |
| Rflag = 1; |
| break; |
| case 'a': |
| Pflag = 1; |
| pflag = 1; |
| Rflag = 1; |
| Hflag = Lflag = 0; |
| break; |
| case 'f': |
| fflag = 1; |
| iflag = 0; |
| break; |
| case 'i': |
| iflag = isatty(fileno(stdin)); |
| fflag = 0; |
| break; |
| case 'l': |
| lflag = 1; |
| break; |
| case 'p': |
| pflag = 1; |
| break; |
| case 'r': |
| rflag = 1; |
| break; |
| case 'v': |
| vflag = 1; |
| break; |
| case '?': |
| default: |
| cp_usage(); |
| /* NOTREACHED */ |
| } |
| argc -= optind; |
| argv += optind; |
| |
| if (argc < 2) |
| cp_usage(); |
| |
| fts_options = FTS_NOCHDIR | FTS_PHYSICAL; |
| if (rflag) { |
| if (Rflag) { |
| errx(EXIT_FAILURE, |
| "the -R and -r options may not be specified together."); |
| /* NOTREACHED */ |
| } |
| if (Hflag || Lflag || Pflag) { |
| errx(EXIT_FAILURE, |
| "the -H, -L, and -P options may not be specified with the -r option."); |
| /* NOTREACHED */ |
| } |
| fts_options &= ~FTS_PHYSICAL; |
| fts_options |= FTS_LOGICAL; |
| } |
| |
| if (Rflag) { |
| if (Hflag) |
| fts_options |= FTS_COMFOLLOW; |
| if (Lflag) { |
| fts_options &= ~FTS_PHYSICAL; |
| fts_options |= FTS_LOGICAL; |
| } |
| } else if (!Pflag) { |
| fts_options &= ~FTS_PHYSICAL; |
| fts_options |= FTS_LOGICAL | FTS_COMFOLLOW; |
| } |
| |
| myuid = getuid(); |
| |
| /* Copy the umask for explicit mode setting. */ |
| myumask = umask(0); |
| (void)umask(myumask); |
| |
| /* Save the target base in "to". */ |
| target = argv[--argc]; |
| if (strlcpy(to.p_path, target, sizeof(to.p_path)) >= sizeof(to.p_path)) |
| errx(EXIT_FAILURE, "%s: name too long", target); |
| to.p_end = to.p_path + strlen(to.p_path); |
| have_trailing_slash = (to.p_end[-1] == '/'); |
| if (have_trailing_slash) |
| STRIP_TRAILING_SLASH(to); |
| to.target_end = to.p_end; |
| |
| /* Set end of argument list for fts(3). */ |
| argv[argc] = NULL; |
| |
| #ifndef ANDROID |
| (void)signal(SIGINFO, progress); |
| #endif |
| |
| /* |
| * Cp has two distinct cases: |
| * |
| * cp [-R] source target |
| * cp [-R] source1 ... sourceN 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". |
| */ |
| if (Pflag) |
| r = lstat(to.p_path, &to_stat); |
| else |
| r = stat(to.p_path, &to_stat); |
| if (r == -1 && errno != ENOENT) { |
| err(EXIT_FAILURE, "%s", to.p_path); |
| /* NOTREACHED */ |
| } |
| if (r == -1 || !S_ISDIR(to_stat.st_mode)) { |
| /* |
| * Case (1). Target is not a directory. |
| */ |
| if (argc > 1) |
| cp_usage(); |
| /* |
| * Need to detect the case: |
| * cp -R dir foo |
| * Where dir is a directory and foo does not exist, where |
| * we want pathname concatenations turned on but not for |
| * the initial mkdir(). |
| */ |
| if (r == -1) { |
| if (rflag || (Rflag && (Lflag || Hflag))) |
| r = stat(*argv, &tmp_stat); |
| else |
| r = lstat(*argv, &tmp_stat); |
| if (r == -1) { |
| err(EXIT_FAILURE, "%s", *argv); |
| /* NOTREACHED */ |
| } |
| |
| if (S_ISDIR(tmp_stat.st_mode) && (Rflag || rflag)) |
| type = DIR_TO_DNE; |
| else |
| type = FILE_TO_FILE; |
| } else |
| type = FILE_TO_FILE; |
| |
| if (have_trailing_slash && type == FILE_TO_FILE) { |
| if (r == -1) |
| errx(1, "directory %s does not exist", |
| to.p_path); |
| else |
| errx(1, "%s is not a directory", to.p_path); |
| } |
| } else { |
| /* |
| * Case (2). Target is a directory. |
| */ |
| type = FILE_TO_DIR; |
| } |
| |
| /* |
| * make "cp -rp src/ dst" behave like "cp -rp src dst" not |
| * like "cp -rp src/. dst" |
| */ |
| for (src = argv; *src; src++) { |
| size_t len = strlen(*src); |
| while (len-- > 1 && (*src)[len] == '/') |
| (*src)[len] = '\0'; |
| } |
| |
| exit(copy(argv, type, fts_options)); |
| /* NOTREACHED */ |
| } |
| |
| static int dnestack[MAXPATHLEN]; /* unlikely we'll have more nested dirs */ |
| static ssize_t dnesp; |
| static void |
| pushdne(int dne) |
| { |
| |
| dnestack[dnesp++] = dne; |
| assert(dnesp < MAXPATHLEN); |
| } |
| |
| static int |
| popdne(void) |
| { |
| int rv; |
| |
| rv = dnestack[--dnesp]; |
| assert(dnesp >= 0); |
| return rv; |
| } |
| |
| static int |
| copy(char *argv[], enum op type, int fts_options) |
| { |
| struct stat to_stat; |
| FTS *ftsp; |
| FTSENT *curr; |
| int base, dne, sval; |
| int this_failed, any_failed; |
| size_t nlen; |
| char *p, *target_mid; |
| |
| base = 0; /* XXX gcc -Wuninitialized (see comment below) */ |
| |
| if ((ftsp = fts_open(argv, fts_options, NULL)) == NULL) |
| err(EXIT_FAILURE, "%s", argv[0]); |
| /* NOTREACHED */ |
| for (any_failed = 0; (curr = fts_read(ftsp)) != NULL;) { |
| this_failed = 0; |
| switch (curr->fts_info) { |
| case FTS_NS: |
| case FTS_DNR: |
| case FTS_ERR: |
| warnx("%s: %s", curr->fts_path, |
| strerror(curr->fts_errno)); |
| this_failed = any_failed = 1; |
| continue; |
| case FTS_DC: /* Warn, continue. */ |
| warnx("%s: directory causes a cycle", curr->fts_path); |
| this_failed = any_failed = 1; |
| continue; |
| } |
| |
| /* |
| * If we are in case (2) or (3) above, we need to append the |
| * source name to the target name. |
| */ |
| if (type != FILE_TO_FILE) { |
| if ((curr->fts_namelen + |
| to.target_end - to.p_path + 1) > MAXPATHLEN) { |
| warnx("%s/%s: name too long (not copied)", |
| to.p_path, curr->fts_name); |
| this_failed = any_failed = 1; |
| continue; |
| } |
| |
| /* |
| * Need to remember the roots of traversals to create |
| * correct pathnames. If there's a directory being |
| * copied to a non-existent directory, e.g. |
| * cp -R a/dir noexist |
| * the resulting path name should be noexist/foo, not |
| * noexist/dir/foo (where foo is a file in dir), which |
| * is the case where the target exists. |
| * |
| * Also, check for "..". This is for correct path |
| * concatentation for paths ending in "..", e.g. |
| * cp -R .. /tmp |
| * Paths ending in ".." are changed to ".". This is |
| * tricky, but seems the easiest way to fix the problem. |
| * |
| * XXX |
| * Since the first level MUST be FTS_ROOTLEVEL, base |
| * is always initialized. |
| */ |
| if (curr->fts_level == FTS_ROOTLEVEL) { |
| if (type != DIR_TO_DNE) { |
| p = strrchr(curr->fts_path, '/'); |
| base = (p == NULL) ? 0 : |
| (int)(p - curr->fts_path + 1); |
| |
| if (!strcmp(&curr->fts_path[base], |
| "..")) |
| base += 1; |
| } else |
| base = curr->fts_pathlen; |
| } |
| |
| p = &curr->fts_path[base]; |
| nlen = curr->fts_pathlen - base; |
| target_mid = to.target_end; |
| if (*p != '/' && target_mid[-1] != '/') |
| *target_mid++ = '/'; |
| *target_mid = 0; |
| |
| if (target_mid - to.p_path + nlen >= PATH_MAX) { |
| warnx("%s%s: name too long (not copied)", |
| to.p_path, p); |
| this_failed = any_failed = 1; |
| continue; |
| } |
| (void)strncat(target_mid, p, nlen); |
| to.p_end = target_mid + nlen; |
| *to.p_end = 0; |
| STRIP_TRAILING_SLASH(to); |
| } |
| |
| sval = Pflag ? lstat(to.p_path, &to_stat) : stat(to.p_path, &to_stat); |
| /* Not an error but need to remember it happened */ |
| if (sval == -1) |
| dne = 1; |
| else { |
| if (to_stat.st_dev == curr->fts_statp->st_dev && |
| to_stat.st_ino == curr->fts_statp->st_ino) { |
| warnx("%s and %s are identical (not copied).", |
| to.p_path, curr->fts_path); |
| this_failed = any_failed = 1; |
| if (S_ISDIR(curr->fts_statp->st_mode)) |
| (void)fts_set(ftsp, curr, FTS_SKIP); |
| continue; |
| } |
| if (!S_ISDIR(curr->fts_statp->st_mode) && |
| S_ISDIR(to_stat.st_mode)) { |
| warnx("cannot overwrite directory %s with non-directory %s", |
| to.p_path, curr->fts_path); |
| this_failed = any_failed = 1; |
| continue; |
| } |
| dne = 0; |
| } |
| |
| switch (curr->fts_statp->st_mode & S_IFMT) { |
| case S_IFLNK: |
| /* Catch special case of a non dangling symlink */ |
| if((fts_options & FTS_LOGICAL) || |
| ((fts_options & FTS_COMFOLLOW) && curr->fts_level == 0)) { |
| if (copy_file(curr, dne)) |
| this_failed = any_failed = 1; |
| } else { |
| if (copy_link(curr, !dne)) |
| this_failed = any_failed = 1; |
| } |
| break; |
| case S_IFDIR: |
| if (!Rflag && !rflag) { |
| if (curr->fts_info == FTS_D) |
| warnx("%s is a directory (not copied).", |
| curr->fts_path); |
| (void)fts_set(ftsp, curr, FTS_SKIP); |
| this_failed = any_failed = 1; |
| break; |
| } |
| |
| /* |
| * Directories get noticed twice: |
| * In the first pass, create it if needed. |
| * In the second pass, after the children have been copied, set the permissions. |
| */ |
| if (curr->fts_info == FTS_D) /* First pass */ |
| { |
| /* |
| * If the directory doesn't exist, create the new |
| * one with the from file mode plus owner RWX bits, |
| * modified by the umask. Trade-off between being |
| * able to write the directory (if from directory is |
| * 555) and not causing a permissions race. If the |
| * umask blocks owner writes, we fail.. |
| */ |
| pushdne(dne); |
| if (dne) { |
| if (mkdir(to.p_path, |
| curr->fts_statp->st_mode | S_IRWXU) < 0) |
| err(EXIT_FAILURE, "%s", |
| to.p_path); |
| /* NOTREACHED */ |
| } else if (!S_ISDIR(to_stat.st_mode)) { |
| errno = ENOTDIR; |
| err(EXIT_FAILURE, "%s", |
| to.p_path); |
| /* NOTREACHED */ |
| } |
| } |
| else if (curr->fts_info == FTS_DP) /* Second pass */ |
| { |
| /* |
| * If not -p and directory didn't exist, set it to be |
| * the same as the from directory, umodified by the |
| * umask; arguably wrong, but it's been that way |
| * forever. |
| */ |
| if (pflag && setfile(curr->fts_statp, 0)) |
| this_failed = any_failed = 1; |
| else if ((dne = popdne())) |
| (void)chmod(to.p_path, |
| curr->fts_statp->st_mode); |
| } |
| else |
| { |
| warnx("directory %s encountered when not expected.", |
| curr->fts_path); |
| this_failed = any_failed = 1; |
| break; |
| } |
| |
| break; |
| case S_IFBLK: |
| case S_IFCHR: |
| if (Rflag) { |
| if (copy_special(curr->fts_statp, !dne)) |
| this_failed = any_failed = 1; |
| } else |
| if (copy_file(curr, dne)) |
| this_failed = any_failed = 1; |
| break; |
| case S_IFIFO: |
| if (Rflag) { |
| if (copy_fifo(curr->fts_statp, !dne)) |
| this_failed = any_failed = 1; |
| } else |
| if (copy_file(curr, dne)) |
| this_failed = any_failed = 1; |
| break; |
| default: |
| if (copy_file(curr, dne)) |
| this_failed = any_failed = 1; |
| break; |
| } |
| if (vflag && !this_failed) |
| (void)printf("%s -> %s\n", curr->fts_path, to.p_path); |
| } |
| if (errno) { |
| err(EXIT_FAILURE, "fts_read"); |
| /* NOTREACHED */ |
| } |
| (void)fts_close(ftsp); |
| return (any_failed); |
| } |