diff --git a/bin/cp/cp.1 b/bin/cp/cp.1 index f7e2d639def7..f6ff23a16f41 100644 --- a/bin/cp/cp.1 +++ b/bin/cp/cp.1 @@ -1,326 +1,334 @@ .\"- .\" 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 .\" $FreeBSD$ .\" -.Dd June 6, 2015 +.Dd February 23, 2022 .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 | n .Op Fl alpsvx .Ar source_file target_file .Nm .Oo .Fl R .Op Fl H | Fl L | Fl P .Oc .Op Fl f | i | n .Op Fl alpsvx .Ar source_file ... target_directory +.Nm +.Op Fl f | i | n +.Op Fl alPpsvx +.Ar source_file target_file +.Nm +.Op Fl f | i | n +.Op Fl alPpsvx +.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 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 L If the .Fl R option is specified, all symbolic links are followed. .It Fl P -If the +No symbolic links are followed. +This is the default if the .Fl R -option is specified, no symbolic links are followed. -This is the default. +option is specified. .It Fl R If .Ar source_file designates a directory, .Nm copies the directory and the entire subtree connected at that point. If the .Ar source_file ends in a .Pa / , the contents of the directory are copied rather than the directory itself. This option also causes symbolic links to be copied, rather than indirected through, 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' umask. .Pp Note that .Nm copies hard linked files as separate files. If you need to preserve hard links, consider using .Xr tar 1 , .Xr cpio 1 , or .Xr pax 1 instead. .It Fl a Archive mode. Same as .Fl RpP . .It Fl f For each existing destination pathname, remove it and create a new file, without prompting for confirmation regardless of its permissions. (The .Fl f option overrides any previous .Fl i or .Fl n options.) .It Fl i Cause .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 or .Sq Li Y , the file copy is attempted. (The .Fl i option overrides any previous .Fl f or .Fl n options.) .It Fl l Create hard links to regular files in a hierarchy instead of copying. .It Fl n Do not overwrite an existing file. (The .Fl n option overrides any previous .Fl f or .Fl i options.) .It Fl p Cause .Nm to preserve the following attributes of each source file in the copy: modification time, access time, file flags, file mode, ACL, user ID, and group ID, 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 nor set-group-ID bits are preserved in the copy's permissions. .It Fl s Create symbolic links to regular files in a hierarchy instead of copying. .It Fl v Cause .Nm to be verbose, showing files as they are copied. .It Fl x File system mount points are not traversed. .El .Pp For each destination file that already exists, its contents are overwritten if permissions allow. Its mode, user ID, and group ID are unchanged unless the .Fl p option was specified. .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 .Pf ( 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) cause symbolic links to be followed as described above. The .Fl H , .Fl L and .Fl P 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. .Pp If .Nm receives a .Dv SIGINFO (see the .Cm status argument for .Xr stty 1 ) signal, the current input and output file and the percentage complete will be written to the standard output. .Sh EXIT STATUS .Ex -std .Sh EXAMPLES Make a copy of file .Pa foo named .Pa bar : .Pp .Dl $ cp foo bar .Pp Copy a group of files to the .Pa /tmp directory: .Pp .Dl $ cp *.txt /tmp .Pp Copy the directory .Pa junk and all of its contents (including any subdirectories) to the .Pa /tmp directory: .Pp .Dl $ cp -R junk /tmp .Sh COMPATIBILITY Historic versions of the .Nm utility had a .Fl r option. This implementation supports that option, however, its behavior is different from historical .Fx behavior. Use of this option is strongly discouraged as the behavior is implementation-dependent. In .Fx , .Fl r is a synonym for .Fl RL and works the same unless modified by other flags. Historical implementations of .Fl r differ as they copy special files as normal files while recreating a hierarchy. .Pp The .Fl l , .Fl s , .Fl v , .Fl x and .Fl n options are non-standard and their use in scripts is not recommended. .Sh SEE ALSO .Xr mv 1 , .Xr rcp 1 , .Xr umask 2 , .Xr fts 3 , .Xr symlink 7 .Sh STANDARDS The .Nm command is expected to be .St -p1003.2 compatible. .Sh HISTORY A .Nm command appeared in .At v1 . diff --git a/bin/cp/cp.c b/bin/cp/cp.c index 7156c7f10742..f132bb940c09 100644 --- a/bin/cp/cp.c +++ b/bin/cp/cp.c @@ -1,584 +1,587 @@ /*- * SPDX-License-Identifier: BSD-3-Clause * * 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. */ #if 0 #ifndef lint static char const copyright[] = "@(#) Copyright (c) 1988, 1993, 1994\n\ The Regents of the University of California. All rights reserved.\n"; #endif /* not lint */ #ifndef lint static char sccsid[] = "@(#)cp.c 8.2 (Berkeley) 4/1/94"; #endif /* not lint */ #endif #include __FBSDID("$FreeBSD$"); /* * 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 emptystring[] = ""; PATH_T to = { to.p_path, emptystring, "" }; int fflag, iflag, lflag, nflag, pflag, sflag, vflag; static int Hflag, Lflag, Rflag, rflag; volatile sig_atomic_t info; enum op { FILE_TO_FILE, FILE_TO_DIR, DIR_TO_DNE }; static int copy(char *[], enum op, int, struct stat *); static void siginfo(int __unused); int main(int argc, char *argv[]) { struct stat to_stat, tmp_stat; enum op type; - int ch, fts_options, r, have_trailing_slash; + int Pflag, ch, fts_options, r, have_trailing_slash; char *target; fts_options = FTS_NOCHDIR | FTS_PHYSICAL; + Pflag = 0; while ((ch = getopt(argc, argv, "HLPRafilnprsvx")) != -1) switch (ch) { case 'H': Hflag = 1; - Lflag = 0; + Lflag = Pflag = 0; break; case 'L': Lflag = 1; - Hflag = 0; + Hflag = Pflag = 0; break; case 'P': + Pflag = 1; Hflag = Lflag = 0; break; case 'R': Rflag = 1; break; case 'a': pflag = 1; Rflag = 1; + Pflag = 1; Hflag = Lflag = 0; break; case 'f': fflag = 1; iflag = nflag = 0; break; case 'i': iflag = 1; fflag = nflag = 0; break; case 'l': lflag = 1; break; case 'n': nflag = 1; fflag = iflag = 0; break; case 'p': pflag = 1; break; case 'r': rflag = Lflag = 1; - Hflag = 0; + Hflag = Pflag = 0; break; case 's': sflag = 1; break; case 'v': vflag = 1; break; case 'x': fts_options |= FTS_XDEV; break; default: usage(); break; } argc -= optind; argv += optind; if (argc < 2) usage(); if (Rflag && rflag) errx(1, "the -R and -r options may not be specified together"); if (lflag && sflag) errx(1, "the -l and -s options may not be specified together"); if (rflag) Rflag = 1; if (Rflag) { if (Hflag) fts_options |= FTS_COMFOLLOW; if (Lflag) { fts_options &= ~FTS_PHYSICAL; fts_options |= FTS_LOGICAL; } - } else { + } else if (!Pflag) { fts_options &= ~FTS_PHYSICAL; fts_options |= FTS_LOGICAL | FTS_COMFOLLOW; } (void)signal(SIGINFO, siginfo); /* Save the target base in "to". */ target = argv[--argc]; if (strlcpy(to.p_path, target, sizeof(to.p_path)) >= sizeof(to.p_path)) errx(1, "%s: name too long", target); to.p_end = to.p_path + strlen(to.p_path); if (to.p_path == to.p_end) { *to.p_end++ = '.'; *to.p_end = 0; } 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; /* * 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". */ r = stat(to.p_path, &to_stat); if (r == -1 && errno != ENOENT) err(1, "%s", to.p_path); if (r == -1 || !S_ISDIR(to_stat.st_mode)) { /* * Case (1). Target is not a directory. */ if (argc > 1) errx(1, "%s is not a directory", to.p_path); /* * 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 && (Lflag || Hflag)) stat(*argv, &tmp_stat); else lstat(*argv, &tmp_stat); if (S_ISDIR(tmp_stat.st_mode) && 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; /* * For DIR_TO_DNE, we could provide copy() with the to_stat we've * already allocated on the stack here that isn't being used for * anything. Not doing so, though, simplifies later logic a little bit * as we need to skip checking root_stat on the first iteration and * ensure that we set it with the first mkdir(). */ exit (copy(argv, type, fts_options, (type == DIR_TO_DNE ? NULL : &to_stat))); } /* Does the right thing based on -R + -H/-L/-P */ static int copy_stat(const char *path, struct stat *sb) { /* * For -R -H/-P, we need to lstat() instead; copy() cares about the link * itself rather than the target if we're not following links during the * traversal. */ if (!Rflag || Lflag) return (stat(path, sb)); return (lstat(path, sb)); } static int copy(char *argv[], enum op type, int fts_options, struct stat *root_stat) { struct stat created_root_stat, to_stat; FTS *ftsp; FTSENT *curr; int base = 0, dne, badcp, rval; size_t nlen; char *p, *recurse_path, *target_mid; mode_t mask, mode; /* * Keep an inverted copy of the umask, for use in correcting * permissions on created directories when not using -p. */ mask = ~umask(0777); umask(~mask); recurse_path = NULL; if ((ftsp = fts_open(argv, fts_options, NULL)) == NULL) err(1, "fts_open"); for (badcp = rval = 0; errno = 0, (curr = fts_read(ftsp)) != NULL; badcp = 0) { switch (curr->fts_info) { case FTS_NS: case FTS_DNR: case FTS_ERR: warnx("%s: %s", curr->fts_path, strerror(curr->fts_errno)); badcp = rval = 1; continue; case FTS_DC: /* Warn, continue. */ warnx("%s: directory causes a cycle", curr->fts_path); badcp = rval = 1; continue; default: ; } if (curr->fts_info == FTS_D && type != FILE_TO_FILE && root_stat != NULL && root_stat->st_dev == curr->fts_statp->st_dev && root_stat->st_ino == curr->fts_statp->st_ino) { assert(recurse_path == NULL); if (curr->fts_level > FTS_ROOTLEVEL) { /* * If the recursion isn't at the immediate * level, we can just not traverse into this * directory. */ fts_set(ftsp, curr, FTS_SKIP); continue; } else { const char *slash; /* * Grab the last path component and double it, * to make life easier later and ensure that * we work even with fts_level == 0 is a couple * of components deep in fts_path. No path * separators are fine and expected in the * common case, though. */ slash = strrchr(curr->fts_path, '/'); if (slash != NULL) slash++; else slash = curr->fts_path; if (asprintf(&recurse_path, "%s/%s", curr->fts_path, slash) == -1) err(1, "asprintf"); } } if (recurse_path != NULL && strcmp(curr->fts_path, recurse_path) == 0) { fts_set(ftsp, curr, FTS_SKIP); 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) { /* * 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 * concatenation 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); badcp = rval = 1; continue; } (void)strncat(target_mid, p, nlen); to.p_end = target_mid + nlen; *to.p_end = 0; STRIP_TRAILING_SLASH(to); } if (curr->fts_info == FTS_DP) { /* * We are nearly finished with this directory. If we * didn't actually copy it, or otherwise don't need to * change its attributes, then we are done. */ if (!curr->fts_number) continue; /* * If -p is in effect, set all the attributes. * Otherwise, set the correct permissions, limited * by the umask. Optimise by avoiding a chmod() * if possible (which is usually the case if we * made the directory). Note that mkdir() does not * honour setuid, setgid and sticky bits, but we * normally want to preserve them on directories. */ if (pflag) { if (setfile(curr->fts_statp, -1)) rval = 1; if (preserve_dir_acls(curr->fts_statp, curr->fts_accpath, to.p_path) != 0) rval = 1; } else { mode = curr->fts_statp->st_mode; if ((mode & (S_ISUID | S_ISGID | S_ISTXT)) || ((mode | S_IRWXU) & mask) != (mode & mask)) if (chmod(to.p_path, mode & mask) != 0) { warn("chmod: %s", to.p_path); rval = 1; } } continue; } /* Not an error but need to remember it happened. */ if (copy_stat(to.p_path, &to_stat) == -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); badcp = rval = 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); badcp = rval = 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)) badcp = rval = 1; } else { if (copy_link(curr, !dne)) badcp = rval = 1; } break; case S_IFDIR: if (!Rflag) { warnx("%s is a directory (not copied).", curr->fts_path); (void)fts_set(ftsp, curr, FTS_SKIP); badcp = rval = 1; break; } /* * 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. */ if (dne) { if (mkdir(to.p_path, curr->fts_statp->st_mode | S_IRWXU) < 0) err(1, "%s", to.p_path); /* * First DNE with a NULL root_stat is the root * path, so set root_stat. We can't really * tell in all cases if the target path is * within the src path, so we just stat() the * first directory we created and use that. */ if (root_stat == NULL && stat(to.p_path, &created_root_stat) == -1) { err(1, "stat"); } else if (root_stat == NULL) { root_stat = &created_root_stat; } } else if (!S_ISDIR(to_stat.st_mode)) { errno = ENOTDIR; err(1, "%s", to.p_path); } /* * Arrange to correct directory attributes later * (in the post-order phase) if this is a new * directory, or if the -p flag is in effect. */ curr->fts_number = pflag || dne; break; case S_IFBLK: case S_IFCHR: if (Rflag && !sflag) { if (copy_special(curr->fts_statp, !dne)) badcp = rval = 1; } else { if (copy_file(curr, dne)) badcp = rval = 1; } break; case S_IFSOCK: warnx("%s is a socket (not copied).", curr->fts_path); break; case S_IFIFO: if (Rflag && !sflag) { if (copy_fifo(curr->fts_statp, !dne)) badcp = rval = 1; } else { if (copy_file(curr, dne)) badcp = rval = 1; } break; default: if (copy_file(curr, dne)) badcp = rval = 1; break; } if (vflag && !badcp) (void)printf("%s -> %s\n", curr->fts_path, to.p_path); } if (errno) err(1, "fts_read"); fts_close(ftsp); free(recurse_path); return (rval); } static void siginfo(int sig __unused) { info = 1; } diff --git a/bin/cp/tests/cp_test.sh b/bin/cp/tests/cp_test.sh index 36d64900f4bb..adb6ea24d9e7 100755 --- a/bin/cp/tests/cp_test.sh +++ b/bin/cp/tests/cp_test.sh @@ -1,205 +1,216 @@ # # SPDX-License-Identifier: BSD-2-Clause-FreeBSD # # Copyright (c) 2020 Kyle Evans # # 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. # # THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. # # $FreeBSD$ check_size() { file=$1 sz=$2 atf_check -o inline:"$sz\n" stat -f '%z' $file } atf_test_case basic basic_body() { echo "foo" > bar atf_check cp bar baz check_size baz 4 } atf_test_case basic_symlink basic_symlink_body() { echo "foo" > bar ln -s bar baz atf_check cp baz foo atf_check test '!' -L foo atf_check -e inline:"cp: baz and baz are identical (not copied).\n" \ -s exit:1 cp baz baz atf_check -e inline:"cp: bar and baz are identical (not copied).\n" \ -s exit:1 cp baz bar } atf_test_case chrdev chrdev_body() { echo "foo" > bar check_size bar 4 atf_check cp /dev/null trunc check_size trunc 0 atf_check cp bar trunc check_size trunc 4 atf_check cp /dev/null trunc check_size trunc 0 } atf_test_case matching_srctgt matching_srctgt_body() { # PR235438: `cp -R foo foo` would previously infinitely recurse and # eventually error out. mkdir foo echo "qux" > foo/bar cp foo/bar foo/zoo atf_check cp -R foo foo atf_check -o inline:"qux\n" cat foo/foo/bar atf_check -o inline:"qux\n" cat foo/foo/zoo atf_check -e not-empty -s not-exit:0 stat foo/foo/foo } atf_test_case matching_srctgt_contained matching_srctgt_contained_body() { # Let's do the same thing, except we'll try to recursively copy foo into # one of its subdirectories. mkdir foo echo "qux" > foo/bar mkdir foo/loo mkdir foo/moo mkdir foo/roo cp foo/bar foo/zoo atf_check cp -R foo foo/moo atf_check -o inline:"qux\n" cat foo/moo/foo/bar atf_check -o inline:"qux\n" cat foo/moo/foo/zoo atf_check -e not-empty -s not-exit:0 stat foo/moo/foo/moo } atf_test_case matching_srctgt_link matching_srctgt_link_body() { mkdir foo echo "qux" > foo/bar cp foo/bar foo/zoo atf_check ln -s foo roo atf_check cp -RH roo foo atf_check -o inline:"qux\n" cat foo/roo/bar atf_check -o inline:"qux\n" cat foo/roo/zoo } atf_test_case matching_srctgt_nonexistent matching_srctgt_nonexistent_body() { # We'll copy foo to a nonexistent subdirectory; ideally, we would # skip just the directory and end up with a layout like; # # foo/ # bar # dne/ # bar # zoo # zoo # mkdir foo echo "qux" > foo/bar cp foo/bar foo/zoo atf_check cp -R foo foo/dne atf_check -o inline:"qux\n" cat foo/dne/bar atf_check -o inline:"qux\n" cat foo/dne/zoo atf_check -e not-empty -s not-exit:0 stat foo/dne/foo } recursive_link_setup() { extra_cpflag=$1 mkdir -p foo/bar ln -s bar foo/baz mkdir foo-mirror eval "cp -R $extra_cpflag foo foo-mirror" } atf_test_case recursive_link_dflt recursive_link_dflt_body() { recursive_link_setup # -P is the default, so this should work and preserve the link. atf_check cp -R foo foo-mirror atf_check test -L foo-mirror/foo/baz } atf_test_case recursive_link_Hflag recursive_link_Hflag_body() { recursive_link_setup # -H will not follow either, so this should also work and preserve the # link. atf_check cp -RH foo foo-mirror atf_check test -L foo-mirror/foo/baz } atf_test_case recursive_link_Lflag recursive_link_Lflag_body() { recursive_link_setup -L # -L will work, but foo/baz ends up expanded to a directory. atf_check test -d foo-mirror/foo/baz -a \ '(' ! -L foo-mirror/foo/baz ')' atf_check cp -RL foo foo-mirror atf_check test -d foo-mirror/foo/baz -a \ '(' ! -L foo-mirror/foo/baz ')' } +atf_test_case standalone_Pflag +standalone_Pflag_body() +{ + echo "foo" > bar + ln -s bar foo + + atf_check cp -P foo baz + atf_check -o inline:'Symbolic Link\n' stat -f %SHT baz +} + atf_init_test_cases() { atf_add_test_case basic atf_add_test_case basic_symlink atf_add_test_case chrdev atf_add_test_case matching_srctgt atf_add_test_case matching_srctgt_contained atf_add_test_case matching_srctgt_link atf_add_test_case matching_srctgt_nonexistent atf_add_test_case recursive_link_dflt atf_add_test_case recursive_link_Hflag atf_add_test_case recursive_link_Lflag + atf_add_test_case standalone_Pflag }