diff --git a/bin/cp/cp.1 b/bin/cp/cp.1 index 3862babafe7..d8d62ef076a 100644 --- a/bin/cp/cp.1 +++ b/bin/cp/cp.1 @@ -31,7 +31,7 @@ .\" .\" @(#)cp.1 8.3 (Berkeley) 4/18/94 .\" -.Dd December 7, 2023 +.Dd December 14, 2023 .Dt CP 1 .Os .Sh NAME @@ -90,10 +90,6 @@ option is specified, symbolic links on the command line are followed. If the .Fl R option is specified, all symbolic links are followed. -.It Fl N -When used with -.Fl p , -suppress copying file flags. .It Fl P No symbolic links are followed. This is the default if the @@ -161,6 +157,10 @@ or options.) .It Fl l Create hard links to regular files in a hierarchy instead of copying. +.It Fl N +When used with +.Fl p , +suppress copying file flags. .It Fl n Do not overwrite an existing file. (The diff --git a/bin/cp/cp.c b/bin/cp/cp.c index 8217a1e5d3c..852868e65dc 100644 --- a/bin/cp/cp.c +++ b/bin/cp/cp.c @@ -85,7 +85,7 @@ static char emptystring[] = ""; PATH_T to = { to.p_path, emptystring, "" }; int Nflag, fflag, iflag, lflag, nflag, pflag, sflag, vflag; -static int Hflag, Lflag, Rflag, rflag; +static int Hflag, Lflag, Pflag, Rflag, rflag; volatile sig_atomic_t info; enum op { FILE_TO_FILE, FILE_TO_DIR, DIR_TO_DNE }; @@ -98,12 +98,11 @@ main(int argc, char *argv[]) { struct stat to_stat, tmp_stat; enum op type; - int Pflag, ch, fts_options, r, have_trailing_slash; + int ch, fts_options, r, have_trailing_slash; char *target; fts_options = FTS_NOCHDIR | FTS_PHYSICAL; - Pflag = 0; - while ((ch = getopt(argc, argv, "HLNPRafilnprsvx")) != -1) + while ((ch = getopt(argc, argv, "HLPRafilNnprsvx")) != -1) switch (ch) { case 'H': Hflag = 1; @@ -113,9 +112,6 @@ main(int argc, char *argv[]) Lflag = 1; Hflag = Pflag = 0; break; - case 'N': - Nflag = 1; - break; case 'P': Pflag = 1; Hflag = Lflag = 0; @@ -140,6 +136,9 @@ main(int argc, char *argv[]) case 'l': lflag = 1; break; + case 'N': + Nflag = 1; + break; case 'n': nflag = 1; fflag = iflag = 0; diff --git a/bin/cp/tests/cp_test.sh b/bin/cp/tests/cp_test.sh index f995d709cc3..397c06d75bb 100755 --- a/bin/cp/tests/cp_test.sh +++ b/bin/cp/tests/cp_test.sh @@ -51,10 +51,7 @@ basic_symlink_body() 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_check cmp foo bar } atf_test_case chrdev @@ -71,6 +68,35 @@ chrdev_body() check_size trunc 0 } +atf_test_case hardlink +hardlink_body() +{ + echo "foo" >foo + atf_check cp -l foo bar + atf_check -o inline:"foo\n" cat bar + atf_check_equal "$(stat -f%d,%i foo)" "$(stat -f%d,%i bar)" +} + +atf_test_case hardlink_exists +hardlink_exists_body() +{ + echo "foo" >foo + echo "bar" >bar + atf_check -s not-exit:0 -e match:exists cp -l foo bar + atf_check -o inline:"bar\n" cat bar + atf_check_not_equal "$(stat -f%d,%i foo)" "$(stat -f%d,%i bar)" +} + +atf_test_case hardlink_exists_force +hardlink_exists_force_body() +{ + echo "foo" >foo + echo "bar" >bar + atf_check cp -fl foo bar + atf_check -o inline:"foo\n" cat bar + atf_check_equal "$(stat -f%d,%i foo)" "$(stat -f%d,%i bar)" +} + atf_test_case matching_srctgt matching_srctgt_body() { @@ -198,6 +224,22 @@ recursive_link_Lflag_body() '(' ! -L foo-mirror/foo/baz ')' } +atf_test_case samefile +samefile_body() +{ + echo "foo" >foo + ln foo bar + ln -s bar baz + atf_check -e match:"baz and baz are identical" \ + -s exit:1 cp baz baz + atf_check -e match:"bar and baz are identical" \ + -s exit:1 cp baz bar + atf_check -e match:"foo and baz are identical" \ + -s exit:1 cp baz foo + atf_check -e match:"bar and foo are identical" \ + -s exit:1 cp foo bar +} + file_is_sparse() { atf_check ${0%/*}/sparse "$1" @@ -205,7 +247,7 @@ file_is_sparse() files_are_equal() { - atf_check test "$(stat -f "%d %i" "$1")" != "$(stat -f "%d %i" "$2")" + atf_check_not_equal "$(stat -f%d,%i "$1")" "$(stat -f%d,%i "$2")" atf_check cmp "$1" "$2" } @@ -293,11 +335,42 @@ standalone_Pflag_body() atf_check -o inline:'Symbolic Link\n' stat -f %SHT baz } +atf_test_case symlink +symlink_body() +{ + echo "foo" >foo + atf_check cp -s foo bar + atf_check -o inline:"foo\n" cat bar + atf_check -o inline:"foo\n" readlink bar +} + +atf_test_case symlink_exists +symlink_exists_body() +{ + echo "foo" >foo + echo "bar" >bar + atf_check -s not-exit:0 -e match:exists cp -s foo bar + atf_check -o inline:"bar\n" cat bar +} + +atf_test_case symlink_exists_force +symlink_exists_force_body() +{ + echo "foo" >foo + echo "bar" >bar + atf_check cp -fs foo bar + atf_check -o inline:"foo\n" cat bar + atf_check -o inline:"foo\n" readlink bar +} + atf_init_test_cases() { atf_add_test_case basic atf_add_test_case basic_symlink atf_add_test_case chrdev + atf_add_test_case hardlink + atf_add_test_case hardlink_exists + atf_add_test_case hardlink_exists_force atf_add_test_case matching_srctgt atf_add_test_case matching_srctgt_contained atf_add_test_case matching_srctgt_link @@ -305,10 +378,14 @@ atf_init_test_cases() 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 samefile atf_add_test_case sparse_leading_hole atf_add_test_case sparse_multiple_holes atf_add_test_case sparse_only_hole atf_add_test_case sparse_to_dev atf_add_test_case sparse_trailing_hole atf_add_test_case standalone_Pflag + atf_add_test_case symlink + atf_add_test_case symlink_exists + atf_add_test_case symlink_exists_force } diff --git a/bin/cp/utils.c b/bin/cp/utils.c index f43902eab3e..0bb5157e3f5 100644 --- a/bin/cp/utils.c +++ b/bin/cp/utils.c @@ -68,6 +68,11 @@ static char sccsid[] = "@(#)utils.c 8.3 (Berkeley) 4/1/94"; */ #define BUFSIZE_SMALL (MAXPHYS) +/* + * Prompt used in -i case. + */ +#define YESNO "(y/n [n]) " + static ssize_t copy_fallback(int from_fd, int to_fd) { @@ -125,7 +130,6 @@ copy_file(const FTSENT *entp, int dne) * modified by the umask.) */ if (!dne) { -#define YESNO "(y/n [n]) " if (nflag) { if (vflag) printf("%s not overwritten\n", to.p_path); @@ -145,70 +149,69 @@ copy_file(const FTSENT *entp, int dne) } if (fflag) { - /* - * Remove existing destination file name create a new - * file. - */ + /* remove existing destination file */ (void)unlink(to.p_path); - if (!lflag && !sflag) { - to_fd = open(to.p_path, - O_WRONLY | O_TRUNC | O_CREAT, - fs->st_mode & ~(S_ISUID | S_ISGID)); - } - } else if (!lflag && !sflag) { - /* Overwrite existing destination file name. */ - to_fd = open(to.p_path, O_WRONLY | O_TRUNC, 0); + dne = 1; } - } else if (!lflag && !sflag) { + } + + rval = 0; + + if (lflag) { + if (link(entp->fts_path, to.p_path) != 0) { + warn("%s", to.p_path); + rval = 1; + } + goto done; + } + + if (sflag) { + if (symlink(entp->fts_path, to.p_path) != 0) { + warn("%s", to.p_path); + rval = 1; + } + goto done; + } + + if (!dne) { + /* overwrite existing destination file */ + to_fd = open(to.p_path, O_WRONLY | O_TRUNC, 0); + } else { + /* create new destination file */ to_fd = open(to.p_path, O_WRONLY | O_TRUNC | O_CREAT, fs->st_mode & ~(S_ISUID | S_ISGID)); } - - if (!lflag && !sflag && to_fd == -1) { + if (to_fd == -1) { warn("%s", to.p_path); rval = 1; goto done; } - rval = 0; - - if (!lflag && !sflag) { - wtotal = 0; - do { - if (use_copy_file_range) { - wcount = copy_file_range(from_fd, NULL, - to_fd, NULL, SSIZE_MAX, 0); - if (wcount < 0 && errno == EINVAL) { - /* Prob a non-seekable FD */ - use_copy_file_range = 0; - } + wtotal = 0; + do { + if (use_copy_file_range) { + wcount = copy_file_range(from_fd, NULL, + to_fd, NULL, SSIZE_MAX, 0); + if (wcount < 0 && errno == EINVAL) { + /* probably a non-seekable descriptor */ + use_copy_file_range = 0; } - if (!use_copy_file_range) { - wcount = copy_fallback(from_fd, to_fd); - } - wtotal += wcount; - if (info) { - info = 0; - (void)fprintf(stderr, - "%s -> %s %3d%%\n", - entp->fts_path, to.p_path, - cp_pct(wtotal, fs->st_size)); - } - } while (wcount > 0); - if (wcount < 0) { - warn("%s", entp->fts_path); - rval = 1; } - } else if (lflag) { - if (link(entp->fts_path, to.p_path)) { - warn("%s", to.p_path); - rval = 1; + if (!use_copy_file_range) { + wcount = copy_fallback(from_fd, to_fd); } - } else if (sflag) { - if (symlink(entp->fts_path, to.p_path)) { - warn("%s", to.p_path); - rval = 1; + wtotal += wcount; + if (info) { + info = 0; + (void)fprintf(stderr, + "%s -> %s %3d%%\n", + entp->fts_path, to.p_path, + cp_pct(wtotal, fs->st_size)); } + } while (wcount > 0); + if (wcount < 0) { + warn("%s", entp->fts_path); + rval = 1; } /* @@ -217,16 +220,13 @@ copy_file(const FTSENT *entp, int dne) * or its contents might be irreplaceable. It would only be safe * to remove it if we created it and its length is 0. */ - - if (!lflag && !sflag) { - if (pflag && setfile(fs, to_fd)) - rval = 1; - if (pflag && preserve_fd_acls(from_fd, to_fd) != 0) - rval = 1; - if (close(to_fd)) { - warn("%s", to.p_path); - rval = 1; - } + if (pflag && setfile(fs, to_fd)) + rval = 1; + if (pflag && preserve_fd_acls(from_fd, to_fd) != 0) + rval = 1; + if (close(to_fd)) { + warn("%s", to.p_path); + rval = 1; } done: