From 5da400ecaab1bd36c0df323a29551873e839afbd Mon Sep 17 00:00:00 2001 From: Zachary Storer Date: Sun, 27 Jul 2014 06:19:54 -0600 Subject: [PATCH] Import NetBSD cp command. Change-Id: I61943a2ef6d5945b9e10e520e5ebf85e99ac668a --- bin/Makefile | 2 +- bin/cp/Makefile | 7 + bin/cp/cp.1 | 254 +++++++ bin/cp/cp.c | 548 ++++++++++++++++ bin/cp/extern.h | 61 ++ bin/cp/utils.c | 429 ++++++++++++ commands/Makefile | 2 +- commands/cp/Makefile | 7 - commands/cp/cp.c | 1384 --------------------------------------- commands/setup/setup.sh | 9 +- man/man1/Makefile | 2 +- man/man1/cp.1 | 223 ------- man/man8/usage.8 | 6 +- 13 files changed, 1308 insertions(+), 1626 deletions(-) create mode 100644 bin/cp/Makefile create mode 100644 bin/cp/cp.1 create mode 100644 bin/cp/cp.c create mode 100644 bin/cp/extern.h create mode 100644 bin/cp/utils.c delete mode 100644 commands/cp/Makefile delete mode 100644 commands/cp/cp.c delete mode 100644 man/man1/cp.1 diff --git a/bin/Makefile b/bin/Makefile index dc696df05..7066ea3b7 100644 --- a/bin/Makefile +++ b/bin/Makefile @@ -1,7 +1,7 @@ # $NetBSD: Makefile,v 1.22 2007/12/31 15:31:24 ad Exp $ # @(#)Makefile 8.1 (Berkeley) 5/31/93 -SUBDIR= cat chmod date df echo ed expr hostname \ +SUBDIR= cat chmod cp date df echo ed expr hostname \ kill ksh ln ls mkdir mv pax pwd rm rmdir \ sleep stty sync test diff --git a/bin/cp/Makefile b/bin/cp/Makefile new file mode 100644 index 000000000..4760d2c92 --- /dev/null +++ b/bin/cp/Makefile @@ -0,0 +1,7 @@ +# $NetBSD: Makefile,v 1.9 1997/07/20 22:36:37 christos Exp $ +# @(#)Makefile 8.1 (Berkeley) 5/31/93 + +PROG= cp +SRCS= cp.c utils.c + +.include diff --git a/bin/cp/cp.1 b/bin/cp/cp.1 new file mode 100644 index 000000000..f71e457bc --- /dev/null +++ b/bin/cp/cp.1 @@ -0,0 +1,254 @@ +.\" $NetBSD: cp.1,v 1.42 2012/03/25 22:37:08 wiz Exp $ +.\" +.\" Copyright (c) 1989, 1990, 1993, 1994 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" This code is derived from software contributed to Berkeley by +.\" the Institute of Electrical and Electronics Engineers, 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. +.\" +.\" @(#)cp.1 8.3 (Berkeley) 4/18/94 +.\" +.Dd March 25, 2012 +.Dt CP 1 +.Os +.Sh NAME +.Nm cp +.Nd copy files +.Sh SYNOPSIS +.Nm +.Oo +.Fl R +.Op Fl H | Fl L | Fl P +.Oc +.Op Fl f | i +.Op Fl alNpv +.Ar source_file target_file +.Nm cp +.Oo +.Fl R +.Op Fl H | Fl L | Fl P +.Oc +.Op Fl f | i +.Op Fl alNpv +.Ar source_file ... target_directory +.Sh DESCRIPTION +In the first synopsis form, the +.Nm +utility copies the contents of the +.Ar source_file +to the +.Ar target_file . +In the second synopsis form, +the contents of each named +.Ar source_file +is copied to the destination +.Ar target_directory . +The names of the files themselves are not changed. +If +.Nm +detects an attempt to copy a file to itself, the copy will fail. +.Pp +The following options are available: +.Bl -tag -width flag +.It Fl a +Archive mode. +Same as +.Fl RpP . +.It Fl f +For each existing destination pathname, attempt to overwrite it. +If permissions do not allow copy to succeed, remove it and create a new +file, without prompting for confirmation. +(The +.Fl i +option is ignored if the +.Fl f +option is specified.) +.It Fl H +If the +.Fl R +option is specified, symbolic links on the command line are followed. +(Symbolic links encountered in the tree traversal are not followed.) +.It Fl i +Causes +.Nm +to write a prompt to the standard error output before copying a file +that would overwrite an existing file. +If the response from the standard input begins with the character +.Sq Li y , +the file copy is attempted. +.It Fl L +If the +.Fl R +option is specified, all symbolic links are followed. +.It Fl l +Create hard links to regular files in a hierarchy instead of copying. +.It Fl N +When used with +.Fl p , +don't copy file flags. +.It Fl P +No symbolic links are followed. +This is the default. +.It Fl p +Causes +.Nm +to preserve in the copy as many of the modification time, access time, +file flags, file mode, user ID, group ID, and extended attributes, +as allowed by permissions. +.Pp +If the user ID and group ID cannot be preserved, no error message +is displayed and the exit value is not altered. +.Pp +If the source file has its set user ID bit on and the user ID cannot +be preserved, the set user ID bit is not preserved +in the copy's permissions. +If the source file has its set group ID bit on and the group ID cannot +be preserved, the set group ID bit is not preserved +in the copy's permissions. +If the source file has both its set user ID and set group ID bits on, +and either the user ID or group ID cannot be preserved, neither +the set user ID or set group ID bits are preserved in the copy's +permissions. +.Pp +Extended attributes from all accessible namespaces are copied; +others are ignored. +If an error occurs during this copy, a message is displayed and +.Nm +skips the other extended attributes for that file. +.It Fl R +If +.Ar source_file +designates a directory, +.Nm +copies the directory and the entire subtree connected at that point. +This option also causes symbolic links to be copied, rather than +followed, and for +.Nm +to create special files rather than copying them as normal files. +Created directories have the same mode as the corresponding source +directory, unmodified by the process's umask. +.Pp +Note that +.Nm +copies hard linked files as separate files. +If you need to preserve hard links, consider using a utility like +.Xr pax 1 +instead. +.It Fl v +Causes +.Nm +to be verbose, showing files as they are copied. +.El +.Pp +For each destination file that already exists, its contents are +overwritten if permissions allow, but its mode, user ID, and group +ID are unchanged. +.Pp +In the second synopsis form, +.Ar target_directory +must exist unless there is only one named +.Ar source_file +which is a directory and the +.Fl R +flag is specified. +.Pp +If the destination file does not exist, the mode of the source file is +used as modified by the file mode creation mask +.Ic ( umask , +see +.Xr csh 1 ) . +If the source file has its set user ID bit on, that bit is removed +unless both the source file and the destination file are owned by the +same user. +If the source file has its set group ID bit on, that bit is removed +unless both the source file and the destination file are in the same +group and the user is a member of that group. +If both the set user ID and set group ID bits are set, all of the above +conditions must be fulfilled or both bits are removed. +.Pp +Appropriate permissions are required for file creation or overwriting. +.Pp +Symbolic links are always followed unless the +.Fl R +flag is set, in which case symbolic links are not followed, by default. +The +.Fl H +or +.Fl L +flags (in conjunction with the +.Fl R +flag), as well as the +.Fl P +flag cause symbolic links to be followed as described above. +The +.Fl H +and +.Fl L +options are ignored unless the +.Fl R +option is specified. +In addition, these options override each other and the +command's actions are determined by the last one specified. +.Sh EXIT STATUS +.Ex -std cp +.Sh COMPATIBILITY +Historic versions of the +.Nm +utility had a +.Fl r +option. +This implementation supports that option, however, its use is strongly +discouraged, as it does not correctly copy special files, symbolic links +or fifo's. +.Sh SEE ALSO +.Xr mv 1 , +.Xr pax 1 , +.Xr rcp 1 , +.Xr umask 2 , +.Xr fts 3 , +.Xr symlink 7 +.Sh STANDARDS +The +.Nm +utility is expected to be +.St -p1003.2 +compatible. +.Pp +The +.Fl a +and +.Fl l +flags are non-standard extensions. +They are intended to be compatible with the same options which +other implementations, namely GNU coreutils and +.Fx , +of this utility have. +.Pp +The +.Fl v +option is an extension to +.St -p1003.2 . diff --git a/bin/cp/cp.c b/bin/cp/cp.c new file mode 100644 index 000000000..4bbe1b7e3 --- /dev/null +++ b/bin/cp/cp.c @@ -0,0 +1,548 @@ +/* $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 +#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 +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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); + +static void +progress(int sig __unused) +{ + + pinfo++; +} + +int +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; + + setprogname(argv[0]); + (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: + usage(); + /* NOTREACHED */ + } + argc -= optind; + argv += optind; + + if (argc < 2) + 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; + + (void)signal(SIGINFO, progress); + + /* + * 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) + 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); +} diff --git a/bin/cp/extern.h b/bin/cp/extern.h new file mode 100644 index 000000000..e393844df --- /dev/null +++ b/bin/cp/extern.h @@ -0,0 +1,61 @@ +/* $NetBSD: extern.h,v 1.17 2012/01/04 15:58:37 christos Exp $ */ + +/*- + * Copyright (c) 1991, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * 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. + * + * @(#)extern.h 8.2 (Berkeley) 4/1/94 + */ + +#ifndef _EXTERN_H_ +#define _EXTERN_H_ + +typedef struct { + char *p_end; /* pointer to NULL at end of path */ + char *target_end; /* pointer to end of target base */ + char p_path[MAXPATHLEN + 1]; /* pointer to the start of a path */ +} PATH_T; + +extern PATH_T to; +extern uid_t myuid; +extern int Rflag, rflag, Hflag, Lflag, Pflag, fflag, iflag, lflag, pflag, Nflag; +extern mode_t myumask; +extern sig_atomic_t pinfo; + +#include + +__BEGIN_DECLS +int copy_fifo(struct stat *, int); +int copy_file(FTSENT *, int); +int copy_link(FTSENT *, int); +int copy_special(struct stat *, int); +int set_utimes(const char *, struct stat *); +int setfile(struct stat *, int); +void usage(void) __attribute__((__noreturn__)); +__END_DECLS + +#endif /* !_EXTERN_H_ */ diff --git a/bin/cp/utils.c b/bin/cp/utils.c new file mode 100644 index 000000000..88f8b3d3c --- /dev/null +++ b/bin/cp/utils.c @@ -0,0 +1,429 @@ +/* $NetBSD: utils.c,v 1.41 2012/01/04 15:58:37 christos Exp $ */ + +/*- + * Copyright (c) 1991, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * 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 +#ifndef lint +#if 0 +static char sccsid[] = "@(#)utils.c 8.3 (Berkeley) 4/1/94"; +#else +__RCSID("$NetBSD: utils.c,v 1.41 2012/01/04 15:58:37 christos Exp $"); +#endif +#endif /* not lint */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "extern.h" + +#define MMAP_MAX_SIZE (8 * 1048576) +#define MMAP_MAX_WRITE (64 * 1024) + +int +set_utimes(const char *file, struct stat *fs) +{ + static struct timeval tv[2]; + + TIMESPEC_TO_TIMEVAL(&tv[0], &fs->st_atimespec); + TIMESPEC_TO_TIMEVAL(&tv[1], &fs->st_mtimespec); + + if (lutimes(file, tv)) { + warn("lutimes: %s", file); + return (1); + } + return (0); +} + +struct finfo { + const char *from; + const char *to; + size_t size; +}; + +static void +progress(const struct finfo *fi, size_t written) +{ + int pcent = (int)((100.0 * written) / fi->size); + + pinfo = 0; + (void)fprintf(stderr, "%s => %s %zu/%zu bytes %d%% written\n", + fi->from, fi->to, written, fi->size, pcent); +} + +int +copy_file(FTSENT *entp, int dne) +{ + static char buf[MAXBSIZE]; + struct stat to_stat, *fs; + int ch, checkch, from_fd, rcount, rval, to_fd, tolnk, wcount; + char *p; + size_t ptotal = 0; + + if ((from_fd = open(entp->fts_path, O_RDONLY, 0)) == -1) { + warn("%s", entp->fts_path); + return (1); + } + + to_fd = -1; + fs = entp->fts_statp; + tolnk = ((Rflag && !(Lflag || Hflag)) || Pflag); + + /* + * If the file exists and we're interactive, verify with the user. + * If the file DNE, set the mode to be the from file, minus setuid + * bits, modified by the umask; arguably wrong, but it makes copying + * executables work right and it's been that way forever. (The + * other choice is 666 or'ed with the execute bits on the from file + * modified by the umask.) + */ + if (!dne) { + struct stat sb; + int sval; + + if (iflag) { + (void)fprintf(stderr, "overwrite %s? ", to.p_path); + checkch = ch = getchar(); + while (ch != '\n' && ch != EOF) + ch = getchar(); + if (checkch != 'y' && checkch != 'Y') { + (void)close(from_fd); + return (0); + } + } + + sval = tolnk ? + lstat(to.p_path, &sb) : stat(to.p_path, &sb); + if (sval == -1) { + warn("stat: %s", to.p_path); + (void)close(from_fd); + return (1); + } + + if (!(tolnk && S_ISLNK(sb.st_mode))) + to_fd = open(to.p_path, O_WRONLY | O_TRUNC, 0); + } else + to_fd = open(to.p_path, O_WRONLY | O_TRUNC | O_CREAT, + fs->st_mode & ~(S_ISUID | S_ISGID)); + + if (to_fd == -1 && (fflag || tolnk)) { + /* + * attempt to remove existing destination file name and + * create a new file + */ + (void)unlink(to.p_path); + to_fd = open(to.p_path, O_WRONLY | O_TRUNC | O_CREAT, + fs->st_mode & ~(S_ISUID | S_ISGID)); + } + + if (to_fd == -1) { + warn("%s", to.p_path); + (void)close(from_fd); + return (1); + } + + rval = 0; + + /* if hard linking then simply close the open fds, link and return */ + if (lflag) { + (void)close(from_fd); + (void)close(to_fd); + (void)unlink(to.p_path); + if (link(entp->fts_path, to.p_path)) { + warn("%s", to.p_path); + return (1); + } + return (0); + } + /* NOTREACHED */ + + /* + * There's no reason to do anything other than close the file + * now if it's empty, so let's not bother. + */ + if (fs->st_size > 0) { + struct finfo fi; + + fi.from = entp->fts_path; + fi.to = to.p_path; + fi.size = (size_t)fs->st_size; + + /* + * Mmap and write if less than 8M (the limit is so + * we don't totally trash memory on big files). + * This is really a minor hack, but it wins some CPU back. + */ + bool use_read; + + use_read = true; + if (fs->st_size <= MMAP_MAX_SIZE) { + size_t fsize = (size_t)fs->st_size; + p = mmap(NULL, fsize, PROT_READ, MAP_FILE|MAP_SHARED, + from_fd, (off_t)0); + if (p != MAP_FAILED) { + size_t remainder; + + use_read = false; + +#if !defined(__minix) + (void) madvise(p, (size_t)fs->st_size, + MADV_SEQUENTIAL); +#endif /* !defined(__minix) */ + + /* + * Write out the data in small chunks to + * avoid locking the output file for a + * long time if the reading the data from + * the source is slow. + */ + remainder = fsize; + do { + ssize_t chunk; + + chunk = (remainder > MMAP_MAX_WRITE) ? + MMAP_MAX_WRITE : remainder; + if (write(to_fd, &p[fsize - remainder], + chunk) != chunk) { + warn("%s", to.p_path); + rval = 1; + break; + } + remainder -= chunk; + ptotal += chunk; + if (pinfo) + progress(&fi, ptotal); + } while (remainder > 0); + + if (munmap(p, fsize) < 0) { + warn("%s", entp->fts_path); + rval = 1; + } + } + } + + if (use_read) { + while ((rcount = read(from_fd, buf, MAXBSIZE)) > 0) { + wcount = write(to_fd, buf, (size_t)rcount); + if (rcount != wcount || wcount == -1) { + warn("%s", to.p_path); + rval = 1; + break; + } + ptotal += wcount; + if (pinfo) + progress(&fi, ptotal); + } + if (rcount < 0) { + warn("%s", entp->fts_path); + rval = 1; + } + } + } + +#if !defined(__minix) + if (pflag && (fcpxattr(from_fd, to_fd) != 0)) + warn("%s: error copying extended attributes", to.p_path); +#endif /* !defined(__minix) */ + + (void)close(from_fd); + + if (rval == 1) { + (void)close(to_fd); + return (1); + } + + if (pflag && setfile(fs, to_fd)) + rval = 1; + /* + * If the source was setuid or setgid, lose the bits unless the + * copy is owned by the same user and group. + */ +#define RETAINBITS \ + (S_ISUID | S_ISGID | S_ISVTX | S_IRWXU | S_IRWXG | S_IRWXO) + if (!pflag && dne + && fs->st_mode & (S_ISUID | S_ISGID) && fs->st_uid == myuid) { + if (fstat(to_fd, &to_stat)) { + warn("%s", to.p_path); + rval = 1; + } else if (fs->st_gid == to_stat.st_gid && + fchmod(to_fd, fs->st_mode & RETAINBITS & ~myumask)) { + warn("%s", to.p_path); + rval = 1; + } + } + if (close(to_fd)) { + warn("%s", to.p_path); + rval = 1; + } + /* set the mod/access times now after close of the fd */ + if (pflag && set_utimes(to.p_path, fs)) { + rval = 1; + } + return (rval); +} + +int +copy_link(FTSENT *p, int exists) +{ + int len; + char target[MAXPATHLEN]; + + if ((len = readlink(p->fts_path, target, sizeof(target)-1)) == -1) { + warn("readlink: %s", p->fts_path); + return (1); + } + target[len] = '\0'; + if (exists && unlink(to.p_path)) { + warn("unlink: %s", to.p_path); + return (1); + } + if (symlink(target, to.p_path)) { + warn("symlink: %s", target); + return (1); + } + return (pflag ? setfile(p->fts_statp, 0) : 0); +} + +int +copy_fifo(struct stat *from_stat, int exists) +{ + if (exists && unlink(to.p_path)) { + warn("unlink: %s", to.p_path); + return (1); + } + if (mkfifo(to.p_path, from_stat->st_mode)) { + warn("mkfifo: %s", to.p_path); + return (1); + } + return (pflag ? setfile(from_stat, 0) : 0); +} + +int +copy_special(struct stat *from_stat, int exists) +{ + if (exists && unlink(to.p_path)) { + warn("unlink: %s", to.p_path); + return (1); + } + if (mknod(to.p_path, from_stat->st_mode, from_stat->st_rdev)) { + warn("mknod: %s", to.p_path); + return (1); + } + return (pflag ? setfile(from_stat, 0) : 0); +} + + +/* + * Function: setfile + * + * Purpose: + * Set the owner/group/permissions for the "to" file to the information + * in the stat structure. If fd is zero, also call set_utimes() to set + * the mod/access times. If fd is non-zero, the caller must do a utimes + * itself after close(fd). + */ +int +setfile(struct stat *fs, int fd) +{ + int rval, islink; + + rval = 0; + islink = S_ISLNK(fs->st_mode); + fs->st_mode &= S_ISUID | S_ISGID | S_IRWXU | S_IRWXG | S_IRWXO; + + /* + * Changing the ownership probably won't succeed, unless we're root + * or POSIX_CHOWN_RESTRICTED is not set. Set uid/gid before setting + * the mode; current BSD behavior is to remove all setuid bits on + * chown. If chown fails, lose setuid/setgid bits. + */ + if (fd ? fchown(fd, fs->st_uid, fs->st_gid) : + lchown(to.p_path, fs->st_uid, fs->st_gid)) { + if (errno != EPERM) { + warn("chown: %s", to.p_path); + rval = 1; + } + fs->st_mode &= ~(S_ISUID | S_ISGID); + } + if (fd ? fchmod(fd, fs->st_mode) : lchmod(to.p_path, fs->st_mode)) { + warn("chmod: %s", to.p_path); + rval = 1; + } + +#if !defined(__minix) + if (!islink && !Nflag) { + unsigned long fflags = fs->st_flags; + /* + * XXX + * NFS doesn't support chflags; ignore errors unless + * there's reason to believe we're losing bits. + * (Note, this still won't be right if the server + * supports flags and we were trying to *remove* flags + * on a file that we copied, i.e., that we didn't create.) + */ + errno = 0; + + if ((fd ? fchflags(fd, fflags) : + chflags(to.p_path, fflags)) == -1) + if (errno != EOPNOTSUPP || fs->st_flags != 0) { + warn("chflags: %s", to.p_path); + rval = 1; + } + } +#endif /* !defined(__minix) */ + + /* if fd is non-zero, caller must call set_utimes() after close() */ + if (fd == 0 && set_utimes(to.p_path, fs)) + rval = 1; + return (rval); +} + +void +usage(void) +{ + (void)fprintf(stderr, + "usage: %s [-R [-H | -L | -P]] [-f | -i] [-alNpv] src target\n" + " %s [-R [-H | -L | -P]] [-f | -i] [-alNpv] src1 ... srcN directory\n", + getprogname(), getprogname()); + exit(1); + /* NOTREACHED */ +} diff --git a/commands/Makefile b/commands/Makefile index 767fc4c55..1eb4cdee3 100644 --- a/commands/Makefile +++ b/commands/Makefile @@ -5,7 +5,7 @@ SUBDIR= add_route arp ash at backup btrace \ cawf cd cdprobe \ ci cleantmp cmp co \ - compress cp crc cron crontab \ + compress crc cron crontab \ dd decomp16 DESCRIBE devmand devsize dhcpd \ dhrystone diff diskctl \ eject fbdctl \ diff --git a/commands/cp/Makefile b/commands/cp/Makefile deleted file mode 100644 index 51fd07079..000000000 --- a/commands/cp/Makefile +++ /dev/null @@ -1,7 +0,0 @@ -# LSC For Now... -NOGCCERROR:= yes -PROG= cp -BINDIR= /bin -MAN= - -.include diff --git a/commands/cp/cp.c b/commands/cp/cp.c deleted file mode 100644 index 470a94bd7..000000000 --- a/commands/cp/cp.c +++ /dev/null @@ -1,1384 +0,0 @@ -/* cp 1.12 - copy files Author: Kees J. Bot - * mv - move files 20 Jul 1993 - * rm - remove files - * ln - make a link - * cpdir - copy a directory tree (cp -psmr) - * clone - make a link farm (ln -fmr) - */ -#define nil 0 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#ifndef DEBUG -#define DEBUG 0 -#define NDEBUG 1 -#endif -#include - -/* Copy files in this size chunks: */ -#if __minix && !__minix_vmd -#define CHUNK (8192 * sizeof(char *)) -#else -#define CHUNK (1024 << (sizeof(int) + sizeof(char *))) -#endif - - -#ifndef CONFORMING -#define CONFORMING 1 /* Precisely POSIX conforming. */ -#endif - - -#define arraysize(a) (sizeof(a) / sizeof((a)[0])) -#define arraylimit(a) ((a) + arraysize(a)) - -char *prog_name; /* Call name of this program. */ -int ex_code= 0; /* Final exit code. */ - -typedef enum identity { CP, MV, RM, LN, CPDIR, CLONE } identity_t; -typedef enum action { COPY, MOVE, REMOVE, LINK } action_t; - -identity_t identity; /* How did the user call me? */ -action_t action; /* Copying, moving, or linking. */ -int pflag= 0; /* -p/-s: Make orginal and copy the same. */ -int iflag= 0; /* -i: Interactive overwriting/deleting. */ -int fflag= 0; /* -f: Force. */ -int sflag= 0; /* -s: Make a symbolic link (ln/clone). */ -int Sflag= 0; /* -S: Make a symlink if across devices. */ -int mflag= 0; /* -m: Merge trees, no target dir trickery. */ -int rflag= 0; /* -r/-R: Recursively copy a tree. */ -int vflag= 0; /* -v: Verbose. */ -int xflag= 0; /* -x: Don't traverse past mount points. */ -int xdev= 0; /* Set when moving or linking cross-device. */ -int expand= 0; /* Expand symlinks, ignore links. */ -int conforming= CONFORMING; /* Sometimes standards are a pain. */ - -int fc_mask; /* File creation mask. */ -int uid, gid; /* Effective uid & gid. */ -int istty; /* Can have terminal input. */ - -#ifndef S_ISLNK -/* There were no symlinks in medieval times. */ -#define S_ISLNK(mode) (0) -#define lstat stat -#define symlink(path1, path2) (errno= ENOSYS, -1) -#define readlink(path, buf, len) (errno= ENOSYS, -1) -#endif - -void report(const char *label) -{ - if (action == REMOVE && fflag) return; - fprintf(stderr, "%s: %s: %s\n", prog_name, label, strerror(errno)); - ex_code= 1; -} - -void fatal(const char *label) -{ - report(label); - exit(1); -} - -void report2(const char *src, const char *dst) -{ - fprintf(stderr, "%s %s %s: %s\n", prog_name, src, dst, strerror(errno)); - ex_code= 1; -} - -#if DEBUG -size_t nchunks= 0; /* Number of allocated cells. */ -#endif - -void *allocate(void *mem, size_t size) -/* Like realloc, but with checking of the return value. */ -{ -#if DEBUG - if (mem == nil) nchunks++; -#endif - if ((mem= mem == nil ? malloc(size) : realloc(mem, size)) == nil) - fatal("malloc()"); - return mem; -} - -void deallocate(void *mem) -/* Release a chunk of memory. */ -{ - if (mem != nil) { -#if DEBUG - nchunks--; -#endif - free(mem); - } -} - -typedef struct pathname { - char *path; /* The actual pathname. */ - size_t idx; /* Index for the terminating null byte. */ - size_t lim; /* Actual length of the path array. */ -} pathname_t; - -void path_init(pathname_t *pp) -/* Initialize a pathname to the null string. */ -{ - pp->path= allocate(nil, pp->lim= NAME_MAX + 2); - pp->path[pp->idx= 0]= 0; -} - -void path_add(pathname_t *pp, const char *name) -/* Add a component to a pathname. */ -{ - size_t lim; - char *p; - - lim= pp->idx + strlen(name) + 2; - - if (lim > pp->lim) { - pp->lim= lim += lim/2; /* add an extra 50% growing space. */ - - pp->path= allocate(pp->path, lim); - } - - p= pp->path + pp->idx; - if (p > pp->path && p[-1] != '/') *p++ = '/'; - - while (*name != 0) { - if (*name != '/' || p == pp->path || p[-1] != '/') *p++ = *name; - name++; - } - *p = 0; - pp->idx= p - pp->path; -} - -void path_trunc(pathname_t *pp, size_t didx) -/* Delete part of a pathname to a remembered length. */ -{ - pp->path[pp->idx= didx]= 0; -} - -#if DEBUG -const char *path_name(const pathname_t *pp) -/* Return the actual name as a C string. */ -{ - return pp->path; -} - -size_t path_length(const pathname_t *pp) -/* The length of the pathname. */ -{ - return pp->idx; -} - -void path_drop(pathname_t *pp) -/* Release the storage occupied by the pathname. */ -{ - deallocate(pp->path); -} - -#else /* !DEBUG */ -#define path_name(pp) ((const char *) (pp)->path) -#define path_length(pp) ((pp)->idx) -#define path_drop(pp) deallocate((void *) (pp)->path) -#endif /* !DEBUG */ - -char *basename(const char *path) -/* Return the last component of a pathname. (Note: declassifies a const - * char * just like strchr. - */ -{ - const char *p= path; - - for (;;) { - while (*p == '/') p++; /* Trailing slashes? */ - - if (*p == 0) break; - - path= p; - while (*p != 0 && *p != '/') p++; /* Skip component. */ - } - return (char *) path; -} - -int affirmative(void) -/* Get a yes/no answer from the suspecting user. */ -{ - int c; - int ok; - - fflush(stdout); - fflush(stderr); - - while ((c= getchar()) == ' ') {} - ok= (c == 'y' || c == 'Y'); - while (c != EOF && c != '\n') c= getchar(); - - return ok; -} - -int writable(const struct stat *stp) -/* True iff the file with the given attributes allows writing. (And we have - * a terminal to ask if ok to overwrite.) - */ -{ - if (!istty || uid == 0) return 1; - if (stp->st_uid == uid) return stp->st_mode & S_IWUSR; - if (stp->st_gid == gid) return stp->st_mode & S_IWGRP; - return stp->st_mode & S_IWOTH; -} - -#ifndef PATH_MAX -#define PATH_MAX 1024 -#endif - -static char *link_islink(const struct stat *stp, const char *file) -{ - /* Tell if a file, which stat(2) information in '*stp', has been seen - * earlier by this function under a different name. If not return a - * null pointer with errno set to ENOENT, otherwise return the name of - * the link. Return a null pointer with an error code in errno for any - * error, using E2BIG for a too long file name. - * - * Use link_islink(nil, nil) to reset all bookkeeping. - * - * Call for a file twice to delete it from the store. - */ - - typedef struct link { /* In-memory link store. */ - struct link *next; /* Hash chain on inode number. */ - ino_t ino; /* File's inode number. */ - off_t off; /* Offset to more info in temp file. */ - } link_t; - typedef struct dlink { /* On-disk link store. */ - dev_t dev; /* Device number. */ - char file[PATH_MAX]; /* Name of earlier seen link. */ - } dlink_t; - static link_t *links[256]; /* Hash list of known links. */ - static int tfd= -1; /* Temp file for file name storage. */ - static dlink_t dlink; - link_t *lp, **plp; - size_t len; - off_t off; - - if (file == nil) { - /* Reset everything. */ - for (plp= links; plp < arraylimit(links); plp++) { - while ((lp= *plp) != nil) { - *plp= lp->next; - free(lp); - } - } - if (tfd != -1) close(tfd); - tfd= -1; - return nil; - } - - /* The file must be a non-directory with more than one link. */ - if (S_ISDIR(stp->st_mode) || stp->st_nlink <= 1) { - errno= ENOENT; - return nil; - } - - plp= &links[stp->st_ino % arraysize(links)]; - - while ((lp= *plp) != nil) { - if (lp->ino == stp->st_ino) { - /* May have seen this link before. Get it and check. */ - if (lseek(tfd, lp->off, SEEK_SET) == -1) return nil; - if (read(tfd, &dlink, sizeof(dlink)) < 0) return nil; - - /* Only need to check the device number. */ - if (dlink.dev == stp->st_dev) { - if (strcmp(file, dlink.file) == 0) { - /* Called twice. Forget about this link. */ - *plp= lp->next; - free(lp); - errno= ENOENT; - return nil; - } - - /* Return the name of the earlier link. */ - return dlink.file; - } - } - plp= &lp->next; - } - - /* First time I see this link. Add it to the store. */ - if (tfd == -1) { - for (;;) { - char *tmp; - - tmp= tmpnam(nil); - tfd= open(tmp, O_RDWR|O_CREAT|O_EXCL, 0600); - if (tfd < 0) { - if (errno != EEXIST) return nil; - } else { - (void) unlink(tmp); - break; - } - } - } - if ((len= strlen(file)) >= PATH_MAX) { - errno= E2BIG; - return nil; - } - - dlink.dev= stp->st_dev; - strcpy(dlink.file, file); - len += offsetof(dlink_t, file) + 1; - if ((off= lseek(tfd, 0, SEEK_END)) == -1) return nil; - if (write(tfd, &dlink, len) != len) return nil; - - if ((lp= malloc(sizeof(*lp))) == nil) return nil; - lp->next= nil; - lp->ino= stp->st_ino; - lp->off= off; - *plp= lp; - errno= ENOENT; - return nil; -} - -int trylink(const char *src, const char *dst, const struct stat *srcst, - const struct stat *dstst) -/* Keep the link structure intact if src has been seen before. */ -{ - char *olddst; - int linked; - - if (action == COPY && expand) return 0; - - if ((olddst= link_islink(srcst, dst)) == nil) { - /* if (errno != ENOENT) ... */ - return 0; - } - - /* Try to link the file copied earlier to the new file. */ - if (dstst->st_ino != 0) (void) unlink(dst); - - if ((linked= (link(olddst, dst) == 0)) && vflag) - printf("ln %s ..\n", olddst); - - return linked; -} - -int copy(const char *src, const char *dst, struct stat *srcst, - struct stat *dstst) -/* Copy one file to another and copy (some of) the attributes. */ -{ - char buf[CHUNK]; - int srcfd, dstfd; - ssize_t n; - - assert(srcst->st_ino != 0); - - if (dstst->st_ino == 0) { - /* The file doesn't exist yet. */ - - if (!S_ISREG(srcst->st_mode)) { - /* Making a new mode 666 regular file. */ - srcst->st_mode= (S_IFREG | 0666) & fc_mask; - } else - if (!pflag && conforming) { - /* Making a new file copying mode with umask applied. */ - srcst->st_mode &= fc_mask; - } - } else { - /* File exists, ask if ok to overwrite if '-i'. */ - - if (iflag || (action == MOVE && !fflag && !writable(dstst))) { - fprintf(stderr, "Overwrite %s? (mode = %03o) ", - dst, dstst->st_mode & 07777); - if (!affirmative()) return 0; - } - - if (action == MOVE) { - /* Don't overwrite, remove first. */ - if (unlink(dst) < 0 && errno != ENOENT) { - report(dst); - return 0; - } - } else { - /* Overwrite. */ - if (!pflag) { - /* Keep the existing mode and ownership. */ - srcst->st_mode= dstst->st_mode; - srcst->st_uid= dstst->st_uid; - srcst->st_gid= dstst->st_gid; - } - } - } - - /* Keep the link structure if possible. */ - if (trylink(src, dst, srcst, dstst)) return 1; - - if ((srcfd= open(src, O_RDONLY)) < 0) { - report(src); - return 0; - } - - dstfd= open(dst, O_WRONLY|O_CREAT|O_TRUNC, srcst->st_mode & 0777); - if (dstfd < 0 && fflag && errno == EACCES) { - /* Retry adding a "w" bit. */ - (void) chmod(dst, dstst->st_mode | S_IWUSR); - dstfd= open(dst, O_WRONLY|O_CREAT|O_TRUNC, 0); - } - if (dstfd < 0 && fflag && errno == EACCES) { - /* Retry after trying to delete. */ - (void) unlink(dst); - dstfd= open(dst, O_WRONLY|O_CREAT|O_TRUNC, 0); - } - if (dstfd < 0) { - report(dst); - close(srcfd); - return 0; - } - - /* Get current parameters. */ - if (fstat(dstfd, dstst) < 0) { - report(dst); - close(srcfd); - close(dstfd); - return 0; - } - - /* Copy the little bytes themselves. */ - while ((n= read(srcfd, buf, sizeof(buf))) > 0) { - char *bp = buf; - ssize_t r; - - while (n > 0 && (r= write(dstfd, bp, n)) > 0) { - bp += r; - n -= r; - } - if (r <= 0) { - if (r == 0) { - fprintf(stderr, - "%s: Warning: EOF writing to %s\n", - prog_name, dst); - break; - } - fatal(dst); - } - } - - if (n < 0) { - report(src); - close(srcfd); - close(dstfd); - return 0; - } - - close(srcfd); - close(dstfd); - - /* Copy the ownership. */ - if ((pflag || !conforming) - && S_ISREG(dstst->st_mode) - && (dstst->st_uid != srcst->st_uid - || dstst->st_gid != srcst->st_gid) - ) { - if (chmod(dst, 0) == 0) dstst->st_mode&= ~07777; - if (chown(dst, srcst->st_uid, srcst->st_gid) < 0) { - if (errno != EPERM) { - report(dst); - return 0; - } - } else { - dstst->st_uid= srcst->st_uid; - dstst->st_gid= srcst->st_gid; - } - } - - if (conforming && S_ISREG(dstst->st_mode) - && (dstst->st_uid != srcst->st_uid - || dstst->st_gid != srcst->st_gid) - ) { - /* Suid bits must be cleared in the holy name of - * security (and the assumed user stupidity). - */ - srcst->st_mode&= ~06000; - } - - /* Copy the mode. */ - if (S_ISREG(dstst->st_mode) && dstst->st_mode != srcst->st_mode) { - if (chmod(dst, srcst->st_mode) < 0) { - if (errno != EPERM) { - report(dst); - return 0; - } - fprintf(stderr, "%s: Can't change the mode of %s\n", - prog_name, dst); - } - } - - /* Copy the file modification time. */ - if ((pflag || !conforming) && S_ISREG(dstst->st_mode)) { - struct utimbuf ut; - - ut.actime= action == MOVE ? srcst->st_atime : time(nil); - ut.modtime= srcst->st_mtime; - if (utime(dst, &ut) < 0) { - if (errno != EPERM) { - report(dst); - return 0; - } - if (pflag) { - fprintf(stderr, - "%s: Can't set the time of %s\n", - prog_name, dst); - } - } - } - if (vflag) { - printf(action == COPY ? "cp %s ..\n" : "mv %s ..\n", src); - } - return 1; -} - -void copy1(const char *src, const char *dst, struct stat *srcst, - struct stat *dstst) -/* Inspect the source file and then copy it. Treatment of symlinks and - * special files is a bit complicated. The filetype and link-structure are - * ignored if (expand && !rflag), symlinks and link-structure are ignored - * if (expand && rflag), everything is copied precisely if !expand. - */ -{ - int r, linked; - - assert(srcst->st_ino != 0); - - if (srcst->st_ino == dstst->st_ino && srcst->st_dev == dstst->st_dev) { - fprintf(stderr, "%s: can't copy %s onto itself\n", - prog_name, src); - ex_code= 1; - return; - } - - /* You can forget it if the destination is a directory. */ - if (dstst->st_ino != 0 && S_ISDIR(dstst->st_mode)) { - errno= EISDIR; - report(dst); - return; - } - - if (S_ISREG(srcst->st_mode) || (expand && !rflag)) { - if (!copy(src, dst, srcst, dstst)) return; - - if (action == MOVE && unlink(src) < 0) { - report(src); - return; - } - return; - } - - if (dstst->st_ino != 0) { - if (iflag || (action == MOVE && !fflag && !writable(dstst))) { - fprintf(stderr, "Replace %s? (mode = %03o) ", - dst, dstst->st_mode & 07777); - if (!affirmative()) return; - } - if (unlink(dst) < 0) { - report(dst); - return; - } - dstst->st_ino= 0; - } - - /* Apply the file creation mask if so required. */ - if (!pflag && conforming) srcst->st_mode &= fc_mask; - - linked= 0; - - if (S_ISLNK(srcst->st_mode)) { - char buf[1024+1]; - - if ((r= readlink(src, buf, sizeof(buf)-1)) < 0) { - report(src); - return; - } - buf[r]= 0; - r= symlink(buf, dst); - if (vflag && r == 0) - printf("ln -s %s %s\n", buf, dst); - } else - if (trylink(src, dst, srcst, dstst)) { - linked= 1; - r= 0; - } else - if (S_ISFIFO(srcst->st_mode)) { - r= mkfifo(dst, srcst->st_mode); - if (vflag && r == 0) - printf("mkfifo %s\n", dst); - } else - if (S_ISBLK(srcst->st_mode) || S_ISCHR(srcst->st_mode)) { - r= mknod(dst, srcst->st_mode, srcst->st_rdev); - if (vflag && r == 0) { - printf("mknod %s %c %d %d\n", - dst, - S_ISBLK(srcst->st_mode) ? 'b' : 'c', - (srcst->st_rdev >> 8) & 0xFF, - (srcst->st_rdev >> 0) & 0xFF); - } - } else { - fprintf(stderr, "%s: %s: odd filetype %5o (not copied)\n", - prog_name, src, srcst->st_mode); - ex_code= 1; - return; - } - - if (r < 0 || lstat(dst, dstst) < 0) { - report(dst); - return; - } - - if (action == MOVE && unlink(src) < 0) { - report(src); - (void) unlink(dst); /* Don't want it twice. */ - return; - } - - if (linked) return; - - if (S_ISLNK(srcst->st_mode)) return; - - /* Copy the ownership. */ - if ((pflag || !conforming) - && (dstst->st_uid != srcst->st_uid - || dstst->st_gid != srcst->st_gid) - ) { - if (chown(dst, srcst->st_uid, srcst->st_gid) < 0) { - if (errno != EPERM) { - report(dst); - return; - } - } - } - - /* Copy the file modification time. */ - if (pflag || !conforming) { - struct utimbuf ut; - - ut.actime= action == MOVE ? srcst->st_atime : time(nil); - ut.modtime= srcst->st_mtime; - if (utime(dst, &ut) < 0) { - if (errno != EPERM) { - report(dst); - return; - } - fprintf(stderr, "%s: Can't set the time of %s\n", - prog_name, dst); - } - } -} - -void remove1(const char *src, const struct stat *srcst) -{ - if (iflag || (!fflag && !writable(srcst))) { - fprintf(stderr, "Remove %s? (mode = %03o) ", src, - srcst->st_mode & 07777); - if (!affirmative()) return; - } - if (unlink(src) < 0) { - report(src); - } else { - if (vflag) printf("rm %s\n", src); - } -} - -void link1(const char *src, const char *dst, const struct stat *srcst, - const struct stat *dstst) -{ - pathname_t sym; - const char *p; - - if (dstst->st_ino != 0 && (iflag || fflag)) { - if (srcst->st_ino == dstst->st_ino) { - if (fflag) return; - fprintf(stderr, "%s: Can't link %s onto itself\n", - prog_name, src); - ex_code= 1; - return; - } - if (iflag) { - fprintf(stderr, "Remove %s? ", dst); - if (!affirmative()) return; - } - errno= EISDIR; - if (S_ISDIR(dstst->st_mode) || unlink(dst) < 0) { - report(dst); - return; - } - } - - if (!sflag && !(rflag && S_ISLNK(srcst->st_mode)) && !(Sflag && xdev)) { - /* A normal link. */ - if (link(src, dst) < 0) { - if (!Sflag || errno != EXDEV) { - report2(src, dst); - return; - } - /* Can't do a cross-device link, we have to symlink. */ - xdev= 1; - } else { - if (vflag) printf("ln %s..\n", src); - return; - } - } - - /* Do a symlink. */ - if (!rflag && !Sflag) { - /* We can get away with a "don't care if it works" symlink. */ - if (symlink(src, dst) < 0) { - report(dst); - return; - } - if (vflag) printf("ln -s %s %s\n", src, dst); - return; - } - - /* If the source is a symlink then it is simply copied. */ - if (S_ISLNK(srcst->st_mode)) { - int r; - char buf[1024+1]; - - if ((r= readlink(src, buf, sizeof(buf)-1)) < 0) { - report(src); - return; - } - buf[r]= 0; - if (symlink(buf, dst) < 0) { - report(dst); - return; - } - if (vflag) printf("ln -s %s %s\n", buf, dst); - return; - } - - /* Make a symlink that has to work, i.e. we must be able to access the - * source now, and the link must work. - */ - if (dst[0] == '/' && src[0] != '/') { - /* ln -[rsS] relative/path /full/path. */ - fprintf(stderr, - "%s: Symlinking %s to %s is too difficult for me to figure out\n", - prog_name, src, dst); - exit(1); - } - - /* Count the number of subdirectories in the destination file and - * add one '..' for each. - */ - path_init(&sym); - if (src[0] != '/') { - p= dst; - while (*p != 0) { - if (p[0] == '.') { - if (p[1] == '/' || p[1] == 0) { - /* A "." component; skip. */ - do p++; while (*p == '/'); - continue; - } else - if (p[1] == '.' && (p[2] == '/' || p[2] == 0)) { - /* A ".." component; oops. */ - switch (path_length(&sym)) { - case 0: - fprintf(stderr, - "%s: Symlinking %s to %s is too difficult for me to figure out\n", - prog_name, src, dst); - exit(1); - case 2: - path_trunc(&sym, 0); - break; - default: - path_trunc(&sym, path_length(&sym) - 3); - } - p++; - do p++; while (*p == '/'); - continue; - } - } - while (*p != 0 && *p != '/') p++; - while (*p == '/') p++; - if (*p == 0) break; - path_add(&sym, ".."); - } - } - path_add(&sym, src); - - if (symlink(path_name(&sym), dst) < 0) { - report(dst); - } else { - if (vflag) printf("ln -s %s %s\n", path_name(&sym), dst); - } - path_drop(&sym); -} - -typedef struct entrylist { - struct entrylist *next; - char *name; -} entrylist_t; - -int eat_dir(const char *dir, entrylist_t **dlist) -/* Make a linked list of all the names in a directory. */ -{ - DIR *dp; - struct dirent *entry; - - if ((dp= opendir(dir)) == nil) return 0; - - while ((entry= readdir(dp)) != nil) { - if (strcmp(entry->d_name, ".") == 0) continue; - if (strcmp(entry->d_name, "..") == 0) continue; - - *dlist= allocate(nil, sizeof(**dlist)); - (*dlist)->name= allocate(nil, strlen(entry->d_name)+1); - strcpy((*dlist)->name, entry->d_name); - dlist= &(*dlist)->next; - } - closedir(dp); - *dlist= nil; - return 1; -} - -void chop_dlist(entrylist_t **dlist) -/* Chop an entry of a name list. */ -{ - entrylist_t *junk= *dlist; - - *dlist= junk->next; - deallocate(junk->name); - deallocate(junk); -} - -void drop_dlist(entrylist_t *dlist) -/* Get rid of a whole list. */ -{ - while (dlist != nil) chop_dlist(&dlist); -} - -void do1(pathname_t *src, pathname_t *dst, int depth) -/* Perform the appropriate action on a source and destination file. */ -{ - size_t slashsrc, slashdst; - struct stat srcst, dstst; - entrylist_t *dlist; - static ino_t topdst_ino; - static dev_t topdst_dev; - static dev_t topsrc_dev; - -#if DEBUG - if (vflag && depth == 0) { - char flags[100], *pf= flags; - - if (pflag) *pf++= 'p'; - if (iflag) *pf++= 'i'; - if (fflag) *pf++= 'f'; - if (sflag) *pf++= 's'; - if (Sflag) *pf++= 'S'; - if (mflag) *pf++= 'm'; - if (rflag) *pf++= 'r'; - if (vflag) *pf++= 'v'; - if (xflag) *pf++= 'x'; - if (expand) *pf++= 'L'; - if (conforming) *pf++= 'C'; - *pf= 0; - printf(": %s -%s %s %s\n", prog_name, flags, - path_name(src), path_name(dst)); - } -#endif - - /* st_ino == 0 if not stat()'ed yet, or nonexistent. */ - srcst.st_ino= 0; - dstst.st_ino= 0; - - if (action != LINK || !sflag || rflag) { - /* Source must exist unless symlinking. */ - if ((expand ? stat : lstat)(path_name(src), &srcst) < 0) { - report(path_name(src)); - return; - } - } - - if (depth == 0) { - /* First call: Not cross-device yet, first dst not seen yet, - * remember top device number. - */ - xdev= 0; - topdst_ino= 0; - topsrc_dev= srcst.st_dev; - } - - /* Inspect the intended destination unless removing. */ - if (action != REMOVE) { - if ((expand ? stat : lstat)(path_name(dst), &dstst) < 0) { - if (errno != ENOENT) { - report(path_name(dst)); - return; - } - } - } - - if (action == MOVE && !xdev) { - if (dstst.st_ino != 0 && srcst.st_dev != dstst.st_dev) { - /* It's a cross-device rename, i.e. copy and remove. */ - xdev= 1; - } else - if (!mflag || dstst.st_ino == 0 || !S_ISDIR(dstst.st_mode)) { - /* Try to simply rename the file (not merging trees). */ - - if (srcst.st_ino == dstst.st_ino) { - fprintf(stderr, - "%s: Can't move %s onto itself\n", - prog_name, path_name(src)); - ex_code= 1; - return; - } - - if (dstst.st_ino != 0) { - if (iflag || (!fflag && !writable(&dstst))) { - fprintf(stderr, - "Replace %s? (mode = %03o) ", - path_name(dst), - dstst.st_mode & 07777); - if (!affirmative()) return; - } - if (!S_ISDIR(dstst.st_mode)) - (void) unlink(path_name(dst)); - } - - if (rename(path_name(src), path_name(dst)) == 0) { - /* Success. */ - if (vflag) { - printf("mv %s %s\n", path_name(src), - path_name(dst)); - } - return; - } - if (errno == EXDEV) { - xdev= 1; - } else { - report2(path_name(src), path_name(dst)); - return; - } - } - } - - if (srcst.st_ino == 0 || !S_ISDIR(srcst.st_mode)) { - /* Copy/move/remove/link a single file. */ - switch (action) { - case COPY: - case MOVE: - copy1(path_name(src), path_name(dst), &srcst, &dstst); - break; - case REMOVE: - remove1(path_name(src), &srcst); - break; - case LINK: - link1(path_name(src), path_name(dst), &srcst, &dstst); - break; - } - return; - } - - /* Recursively copy/move/remove/link a directory if -r or -R. */ - if (!rflag) { - errno= EISDIR; - report(path_name(src)); - return; - } - - /* Ok to remove contents of dir? */ - if (action == REMOVE) { - if (xflag && topsrc_dev != srcst.st_dev) { - /* Don't recurse past a mount point. */ - return; - } - if (iflag) { - fprintf(stderr, "Remove contents of %s? ", path_name(src)); - if (!affirmative()) return; - } - } - - /* Gather the names in the source directory. */ - if (!eat_dir(path_name(src), &dlist)) { - report(path_name(src)); - return; - } - - /* Check/create the target directory. */ - if (action != REMOVE && dstst.st_ino != 0 && !S_ISDIR(dstst.st_mode)) { - if (action != MOVE && !fflag) { - errno= ENOTDIR; - report(path_name(dst)); - return; - } - if (iflag) { - fprintf(stderr, "Replace %s? ", path_name(dst)); - if (!affirmative()) { - drop_dlist(dlist); - return; - } - } - if (unlink(path_name(dst)) < 0) { - report(path_name(dst)); - drop_dlist(dlist); - return; - } - dstst.st_ino= 0; - } - - if (action != REMOVE) { - if (dstst.st_ino == 0) { - /* Create a new target directory. */ - if (!pflag && conforming) srcst.st_mode&= fc_mask; - - if (mkdir(path_name(dst), srcst.st_mode | S_IRWXU) < 0 - || stat(path_name(dst), &dstst) < 0) { - report(path_name(dst)); - drop_dlist(dlist); - return; - } - if (vflag) printf("mkdir %s\n", path_name(dst)); - } else { - /* Target directory already exists. */ - if (action == MOVE && !mflag) { - errno= EEXIST; - report(path_name(dst)); - drop_dlist(dlist); - return; - } - if (!pflag) { - /* Keep the existing attributes. */ - srcst.st_mode= dstst.st_mode; - srcst.st_uid= dstst.st_uid; - srcst.st_gid= dstst.st_gid; - srcst.st_mtime= dstst.st_mtime; - } - } - - if (topdst_ino == 0) { - /* Remember the top destination. */ - topdst_dev= dstst.st_dev; - topdst_ino= dstst.st_ino; - } - - if (srcst.st_ino == topdst_ino && srcst.st_dev == topdst_dev) { - /* E.g. cp -r /shallow /shallow/deep. */ - fprintf(stderr, - "%s%s %s/ %s/: infinite recursion avoided\n", - prog_name, action != MOVE ? " -r" : "", - path_name(src), path_name(dst)); - drop_dlist(dlist); - return; - } - - if (xflag && topsrc_dev != srcst.st_dev) { - /* Don't recurse past a mount point. */ - drop_dlist(dlist); - return; - } - } - - /* Go down. */ - slashsrc= path_length(src); - slashdst= path_length(dst); - - while (dlist != nil) { - path_add(src, dlist->name); - if (action != REMOVE) path_add(dst, dlist->name); - - do1(src, dst, depth+1); - - path_trunc(src, slashsrc); - path_trunc(dst, slashdst); - chop_dlist(&dlist); - } - - if (action == MOVE || action == REMOVE) { - /* The contents of the source directory should have - * been (re)moved above. Get rid of the empty dir. - */ - if (action == REMOVE && iflag) { - fprintf(stderr, "Remove directory %s? ", - path_name(src)); - if (!affirmative()) return; - } - if (rmdir(path_name(src)) < 0) { - if (errno != ENOTEMPTY) report(path_name(src)); - return; - } - if (vflag) printf("rmdir %s\n", path_name(src)); - } - - if (action != REMOVE) { - /* Set the attributes of a new directory. */ - struct utimbuf ut; - - /* Copy the ownership. */ - if ((pflag || !conforming) - && (dstst.st_uid != srcst.st_uid - || dstst.st_gid != srcst.st_gid) - ) { - if (chown(path_name(dst), srcst.st_uid, - srcst.st_gid) < 0) { - if (errno != EPERM) { - report(path_name(dst)); - return; - } - } - } - - /* Copy the mode. */ - if (dstst.st_mode != srcst.st_mode) { - if (chmod(path_name(dst), srcst.st_mode) < 0) { - report(path_name(dst)); - return; - } - } - - /* Copy the file modification time. */ - if (dstst.st_mtime != srcst.st_mtime) { - ut.actime= action == MOVE ? srcst.st_atime : time(nil); - ut.modtime= srcst.st_mtime; - if (utime(path_name(dst), &ut) < 0) { - if (errno != EPERM) { - report(path_name(dst)); - return; - } - fprintf(stderr, - "%s: Can't set the time of %s\n", - prog_name, path_name(dst)); - } - } - } -} - -void usage(void) -{ - char *flags1, *flags2; - - switch (identity) { - case CP: - flags1= "pifsmrRvx"; - flags2= "pifsrRvx"; - break; - case MV: - flags1= "ifsmvx"; - flags2= "ifsvx"; - break; - case RM: - fprintf(stderr, "Usage: rm [-ifrRvx] file ...\n"); - exit(1); - case LN: - flags1= "ifsSmrRvx"; - flags2= "ifsSrRvx"; - break; - case CPDIR: - flags1= "ifvx"; - flags2= nil; - break; - case CLONE: - flags1= "ifsSvx"; - flags2= nil; - break; - } - fprintf(stderr, "Usage: %s [-%s] file1 file2\n", prog_name, flags1); - if (flags2 != nil) - fprintf(stderr, " %s [-%s] file ... dir\n", prog_name, flags2); - exit(1); -} - -int main(int argc, char **argv) -{ - int i; - char *flags; - struct stat st; - pathname_t src, dst; - size_t slash; - -#if DEBUG >= 3 - /* The first argument is the call name while debugging. */ - if (argc < 2) exit(-1); - argv++; - argc--; -#endif -#if DEBUG - vflag= isatty(1); -#endif - - /* Call name of this program. */ - prog_name= basename(argv[0]); - - /* Required action. */ - if (strcmp(prog_name, "cp") == 0) { - identity= CP; - action= COPY; - flags= "pifsmrRvx"; - expand= 1; - } else - if (strcmp(prog_name, "mv") == 0) { - identity= MV; - action= MOVE; - flags= "ifsmvx"; - rflag= pflag= 1; - } else - if (strcmp(prog_name, "rm") == 0) { - identity= RM; - action= REMOVE; - flags= "ifrRvx"; - } else - if (strcmp(prog_name, "ln") == 0) { - identity= LN; - action= LINK; - flags= "ifsSmrRvx"; - } else - if (strcmp(prog_name, "cpdir") == 0) { - identity= CPDIR; - action= COPY; - flags= "pifsmrRvx"; - rflag= mflag= pflag= 1; - conforming= 0; - } else - if (strcmp(prog_name, "clone") == 0) { - identity= CLONE; - action= LINK; - flags= "ifsSmrRvx"; - rflag= mflag= fflag= 1; - } else { - fprintf(stderr, - "%s: Identity crisis, not called cp, mv, rm, ln, cpdir, or clone\n", - prog_name); - exit(1); - } - - /* Who am I?, where am I?, how protective am I? */ - uid= geteuid(); - gid= getegid(); - istty= isatty(0); - fc_mask= ~umask(0); - - /* Gather flags. */ - i= 1; - while (i < argc && argv[i][0] == '-') { - char *opt= argv[i++] + 1; - - if (opt[0] == '-' && opt[1] == 0) break; /* -- */ - - while (*opt != 0) { - /* Flag supported? */ - if (strchr(flags, *opt) == nil) usage(); - - switch (*opt++) { - case 'p': - pflag= 1; - break; - case 'i': - iflag= 1; - if (action == MOVE) fflag= 0; - break; - case 'f': - fflag= 1; - if (action == MOVE) iflag= 0; - break; - case 's': - if (action == LINK) { - sflag= 1; - } else { - /* Forget about POSIX, do it right. */ - conforming= 0; - } - break; - case 'S': - Sflag= 1; - break; - case 'm': - mflag= 1; - break; - case 'r': - expand= 0; - /*FALL THROUGH*/ - case 'R': - rflag= 1; - break; - case 'v': - vflag= 1; - break; - case 'x': - xflag= 1; - break; - default: - assert(0); - } - } - } - - switch (action) { - case REMOVE: - if (i == argc) { - if (fflag) - exit(0); - usage(); - } - break; - case LINK: - /* 'ln dir/file' is to be read as 'ln dir/file .'. */ - if ((argc - i) == 1 && action == LINK) argv[argc++]= "."; - /*FALL THROUGH*/ - default: - if ((argc - i) < 2) usage(); - } - - path_init(&src); - path_init(&dst); - - if (action != REMOVE && !mflag - && stat(argv[argc-1], &st) >= 0 && S_ISDIR(st.st_mode) - ) { - /* The last argument is a directory, this means we have to - * throw the whole lot into this directory. This is the - * Right Thing unless you use -r. - */ - path_add(&dst, argv[argc-1]); - slash= path_length(&dst); - - do { - path_add(&src, argv[i]); - path_add(&dst, basename(argv[i])); - - do1(&src, &dst, 0); - - path_trunc(&src, 0); - path_trunc(&dst, slash); - } while (++i < argc-1); - } else - if (action == REMOVE || (argc - i) == 2) { - /* Just two files (or many files for rm). */ - do { - path_add(&src, argv[i]); - if (action != REMOVE) path_add(&dst, argv[i+1]); - - do1(&src, &dst, 0); - path_trunc(&src, 0); - } while (action == REMOVE && ++i < argc); - } else { - usage(); - } - path_drop(&src); - path_drop(&dst); - -#if DEBUG - if (nchunks != 0) { - fprintf(stderr, "(%ld chunks of memory not freed)\n", - (long) nchunks); - } -#endif - exit(ex_code); - return ex_code; -} diff --git a/commands/setup/setup.sh b/commands/setup/setup.sh index 9b9201598..192d04abf 100644 --- a/commands/setup/setup.sh +++ b/commands/setup/setup.sh @@ -695,17 +695,14 @@ mount /dev/$usr /mnt >/dev/null || exit # Mount the intended /usr. (cd /usr || exit 1 list="`ls | fgrep -v install`" - for d in $list - do - cp -psmr -v $d /mnt/$d - done + pax -rw -pe -v $list /mnt 2>&1 ) | progressbar "$USRFILES" || exit # Copy the usr floppy. umount /dev/$usr >/dev/null || exit # Unmount the intended /usr. mount /dev/$root /mnt >/dev/null || exit # Running from the installation CD. -cp -psmr -vx / /mnt | progressbar "$ROOTFILES" || exit +pax -rw -pe -vX / /mnt 2>&1 | progressbar "$ROOTFILES" || exit chmod o-w /mnt/usr cp /mnt/etc/motd.install /mnt/etc/motd @@ -753,7 +750,7 @@ then if mount /dev/$home /home 2>/dev/null do h=`eval echo "~$u"` if mkdir $h then echo " * Creating home directory for $u in $h" - cp -psmr /usr/ast $h + pax -rw -pe /usr/ast $h chown -R $u:operator $h else echo " * Couldn't create $h" fi diff --git a/man/man1/Makefile b/man/man1/Makefile index 412dcec27..b39017f58 100644 --- a/man/man1/Makefile +++ b/man/man1/Makefile @@ -1,7 +1,7 @@ MAN= ash.1 at.1 \ bsfilt.1 cawf.1 chgrp.1 \ cmp.1 compress.1 \ - cp.1 crc.1 crontab.1 dd.1 \ + crc.1 crontab.1 dd.1 \ dhrystone.1 dosdir.1 dosread.1 doswrite.1 \ eject.1 \ flexdoc.1 format.1 \ diff --git a/man/man1/cp.1 b/man/man1/cp.1 deleted file mode 100644 index 511628b99..000000000 --- a/man/man1/cp.1 +++ /dev/null @@ -1,223 +0,0 @@ -.TH CP 1 -.SH NAME -cp, mv, rm, ln, cpdir, clone \- copy, move, remove, link -.SH SYNOPSIS -.B cp -.RB [ \-pifsmrRvx ] -.I file1 file2 -.br -.B cp -.RB [ \-pifsrRvx ] -.IR file " ... " dir -.PP -.B mv -.RB [ \-ifsmvx ] -.I file1 file2 -.br -.B mv -.RB [ \-ifsvx ] -.IR file " ... " dir -.PP -.B rm -.RB [ \-ifrRvx ] -.IR file " ..." -.PP -.B ln -.RB [ \-ifsSmrRvx ] -.I file1 file2 -.br -.B ln -.RB [ \-ifsSrRvx ] -.IR file " ... " dir -.PP -.B cpdir -.RB [ \-ifvx ] -.I file1 file2 -.PP -.B clone -.RB [ \-ifsSvx ] -.I file1 file2 -.SH DESCRIPTION -.de SP -.if t .sp 0.4 -.if n .sp -.. -The utilities -.BR cp , -.BR mv , -.BR rm , -and -.B ln -do basic file management: copying, renaming or moving, deletion, and -creating links. (The -.B cpdir -and -.B clone -utilities are easy to use aliases for copying or linking whole trees. -They are the same as -.B cp \-psmr -and -.BR "ln \-fmr" ) -.PP -The first synopsis form of the utilities -.BR cp , -.BR mv , -and -.B ln -is used if only two arguments are given, and the second argument is not a -directory. The source and target file are then the two files given. -.PP -If the second synopsis form is used then the last argument must be a -directory. Each of the files is copied, moved or linked into this directory. -.PP -A file is by default copied by -.B cp -without looking at its type, so symlinks are followed and devices are opened -and read from or written to. Links between files are ignored. This -behavior can be changed by using the proper options. -.PP -The -.B mv -utility uses the -.BR rename (2) -call to rename or move files. If source and target are on different devices -however, then -.B mv -will use -.B cp \-pr -to copy the files or directory trees. -.PP -Each utility continues with the next file on errors, except on I/O errors. -.SH OPTIONS -.TP -.B \-p -Copy the file attributes like mode, owner, group and time of last -modification. Normally only the mode is copied to a new file with the file -creation mask applied. Setuid bits are cleared if setting the ownership -fails. -.TP -.B \-i -Ask if ok to overwrite, replace or remove. -.B Mv -and -.B rm -will ask this automatically if interactive and the target file is writable. -.B Cp -will fail if the target cannot be written, -.B ln -will always fail if the target exists. -.TP -.B \-f -Makes -.B cp -remove a target file before copying if it is not writable, -.B mv -removes an existing target without asking, -.B rm -does not report any errors, and -.B ln -removes an existing target file before linking. The last of -.B \-i -and -.B \-f -wins for -.B mv -if both flags are set, the other utilities do something sensible, like asking -before forcefully removing. -.TP -.B \-s -Make a symlink instead of a normal link. For utilities other than -.B ln -this flag means "copy similar". The modified time is always copied for -.B cp \-s -and the other attributes are copied if a new file is created. The normal -\s-2POSIX\s+2 required patronizing like applying the file creation mask or -clearing setuid bits is not done. -.TP -.B \-S -Make a symlink if a normal link cannot be made because source and target are -on different devices. The symlink is required to really refer back to the -source, meaning that a/b must exist in the call -.BR "ln \-S a/b c/d" , -and that the symlink from c/d must lead back to a/b. So the symlink will be -created as if -.B "ln \-s ../a/b c/d" -was called. If the target is a full path, but the source is not then an -error will be given saying that this is "too difficult." -.TP -.B \-m -Merge trees. The first synopsis form is assumed, and the files from one -tree are merged into the other. There is no "if it's a directory the put -it into that directory" trickery here. -.TP -.BR \-r ", " \-R -Recursively copy, remove, or link. If the source is a directory then the -files in this directory are copied to similarly named files in the target -directory. Special files are copied as new special files, they are not read -or written. Symlinks are still expanded and the link structure ignored with -.BR \-R . -The -.B \-r -flag does copy symlinks as symlinks and keeps the link structure intact. -(Note that -.B \-R -is invented by \s-2POSIX\s+2 as a replacement for the classic -.B \-r -option of older copy commands that did read special files. The standard -says that -.B \-r -is implementation defined, so that's why this flag is better than -.B \-R -in this implementation of -.BR cp .) -For -.B rm -and -.B ln -both flags mean the same. -.B Ln -will recursively link the files in the trees, except symlinks, they are -copied. If symlinks are created with -.B ln \-rs -or -.B ln \-rS -then they are required "to work" as described with the -.B \-S -flag. -.TP -.B \-v -Verbose. Show what is done on standard output. -.TP -.B \-x -Do not cross mount points. Empty directories will be created if the source -directory is a mount point on a copy, move or link. A mount point will not -be removed or traversed recursively. This flag allows one to copy the root -device, e.g. -.BR "cpdir \-x / /mnt" . -.SH "SEE ALSO" -.BR cat (1), -.BR mkdir (1), -.BR rmdir (1), -.BR mkdir (2), -.BR rmdir (2), -.BR link (2), -.BR unlink (2), -.BR rename (2), -.BR open (2), -.BR read (2), -.BR write (2), -.BR opendir (3). -.SH NOTES -All the utilities described are links to the same program. -.SH BUGS -.B Mv -should first copy a tree across devices and then remove the source tree if -there was no error. Instead, each file in the tree is copied and -immediately removed. On error you may be left with two half-filled trees, -together containing all of the files. You may have to restart the move with -.BR "mv \-m" . -.PP -.B Rm -should be able to remove arbitrarily deep trees. -.SH AUTHOR -Kees J. Bot (kjb@cs.vu.nl) diff --git a/man/man8/usage.8 b/man/man8/usage.8 index 73ffb8af1..e0ee5520b 100644 --- a/man/man8/usage.8 +++ b/man/man8/usage.8 @@ -311,7 +311,7 @@ Next make a file system for on-disk /usr and copy the floppy /usr on to it. .PP .XB "mkfs\0/dev/c0d0p1s2" .XB "mount\0/dev/c0d0p1s2\0/mnt" -.XB "cp -psmr\0\-v\0/usr\0/mnt" +.XB "pax -rw -pe\0\-v\0/usr\0/mnt" .PP This will create a file system on /dev/c0d0p1s2, mount it on /mnt, and copy the contents of the \s-2USR\s+2 floppy onto it. @@ -330,7 +330,7 @@ fill it from the floppy: .XB "mkfs\0\-i\0512\0/dev/c0d0p1s0" .XB "mount\0/dev/fd0\0/fd0" .XB "mount\0/dev/c0d0p1s0\0/mnt" -.XB "cp -psmr\0\-v\0/fd0\0/mnt" +.XB "pax -rw -pe\0\-v\0/fd0\0/mnt" .XB "umount\0/dev/fd0" .PP Remove @@ -646,7 +646,7 @@ This is slower then a RAM disk, but saves a lot of memory. The automatic installation script knows how to handle this new situation. If you install manually then you have to use .PP -.XB "cp -psmr\0\-vx\0/\0/mnt" +.XB "pax -rw -pe\0\-vx\0/\0/mnt" .PP to copy the root device to disk. When it is time to fill /usr and you only have one floppy drive then hit DEL to get out of the installation script and