diff --git a/bin/cp/tests/cp_test.sh b/bin/cp/tests/cp_test.sh index f06238c89367..d5268ed4c4c9 100755 --- a/bin/cp/tests/cp_test.sh +++ b/bin/cp/tests/cp_test.sh @@ -1,641 +1,659 @@ # # SPDX-License-Identifier: BSD-2-Clause # # 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. # 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 cmp foo 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 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() { # 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 ln -s foo coo echo "qux" > foo/bar mkdir foo/moo touch foo/moo/roo cp foo/bar foo/zoo atf_check cp -R foo foo/moo atf_check cp -RH coo foo/moo atf_check -o inline:"qux\n" cat foo/moo/foo/bar atf_check -o inline:"qux\n" cat foo/moo/coo/bar atf_check -o inline:"qux\n" cat foo/moo/foo/zoo atf_check -o inline:"qux\n" cat foo/moo/coo/zoo # We should have copied the contents of foo/moo before foo, coo started # getting copied in. atf_check -o not-empty stat foo/moo/foo/moo/roo atf_check -o not-empty stat foo/moo/coo/moo/roo atf_check -e not-empty -s not-exit:0 stat foo/moo/foo/moo/foo atf_check -e not-empty -s not-exit:0 stat foo/moo/coo/moo/coo } 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 } atf_test_case pflag_acls pflag_acls_body() { mkdir dir + ln -s dir lnk echo "hello" >dir/file if ! setfacl -m g:staff:D::allow dir || ! setfacl -m g:staff:d::allow dir/file ; then atf_skip "file system does not support ACLs" fi atf_check -o match:"group:staff:-+D-+" getfacl dir atf_check -o match:"group:staff:-+d-+" getfacl dir/file # file-to-file copy without -p atf_check cp dir/file dst1 atf_check -o not-match:"group:staff:-+d-+" getfacl dst1 # file-to-file copy with -p atf_check cp -p dir/file dst2 atf_check -o match:"group:staff:-+d-+" getfacl dst2 # recursive copy without -p atf_check cp -r dir dst3 atf_check -o not-match:"group:staff:-+D-+" getfacl dst3 atf_check -o not-match:"group:staff:-+d-+" getfacl dst3/file # recursive copy with -p atf_check cp -rp dir dst4 atf_check -o match:"group:staff:-+D-+" getfacl dst4 atf_check -o match:"group:staff:-+d-+" getfacl dst4/file + # source is a link without -p + atf_check cp -r lnk dst5 + atf_check -o not-match:"group:staff:-+D-+" getfacl dst5 + atf_check -o not-match:"group:staff:-+d-+" getfacl dst5/file + # source is a link with -p + atf_check cp -rp lnk dst6 + atf_check -o match:"group:staff:-+D-+" getfacl dst6 + atf_check -o match:"group:staff:-+d-+" getfacl dst6/file } atf_test_case pflag_flags pflag_flags_body() { mkdir dir + ln -s dir lnk echo "hello" >dir/file if ! chflags nodump dir || ! chflags nodump dir/file ; then atf_skip "file system does not support flags" fi atf_check -o match:"nodump" stat -f%Sf dir atf_check -o match:"nodump" stat -f%Sf dir/file # file-to-file copy without -p atf_check cp dir/file dst1 atf_check -o not-match:"nodump" stat -f%Sf dst1 # file-to-file copy with -p atf_check cp -p dir/file dst2 atf_check -o match:"nodump" stat -f%Sf dst2 # recursive copy without -p atf_check cp -r dir dst3 atf_check -o not-match:"nodump" stat -f%Sf dst3 atf_check -o not-match:"nodump" stat -f%Sf dst3/file # recursive copy with -p atf_check cp -rp dir dst4 atf_check -o match:"nodump" stat -f%Sf dst4 atf_check -o match:"nodump" stat -f%Sf dst4/file + # source is a link without -p + atf_check cp -r lnk dst5 + atf_check -o not-match:"nodump" stat -f%Sf dst5 + atf_check -o not-match:"nodump" stat -f%Sf dst5/file + # source is a link with -p + atf_check cp -rp lnk dst6 + atf_check -o match:"nodump" stat -f%Sf dst6 + atf_check -o match:"nodump" stat -f%Sf dst6/file } 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 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" } files_are_equal() { atf_check_not_equal "$(stat -f%d,%i "$1")" "$(stat -f%d,%i "$2")" atf_check cmp "$1" "$2" } atf_test_case sparse_leading_hole sparse_leading_hole_body() { # A 16-megabyte hole followed by one megabyte of data truncate -s 16M foo seq -f%015g 65536 >>foo file_is_sparse foo atf_check cp foo bar files_are_equal foo bar file_is_sparse bar } atf_test_case sparse_multiple_holes sparse_multiple_holes_body() { # Three one-megabyte blocks of data preceded, separated, and # followed by 16-megabyte holes truncate -s 16M foo seq -f%015g 65536 >>foo truncate -s 33M foo seq -f%015g 65536 >>foo truncate -s 50M foo seq -f%015g 65536 >>foo truncate -s 67M foo file_is_sparse foo atf_check cp foo bar files_are_equal foo bar file_is_sparse bar } atf_test_case sparse_only_hole sparse_only_hole_body() { # A 16-megabyte hole truncate -s 16M foo file_is_sparse foo atf_check cp foo bar files_are_equal foo bar file_is_sparse bar } atf_test_case sparse_to_dev sparse_to_dev_body() { # Three one-megabyte blocks of data preceded, separated, and # followed by 16-megabyte holes truncate -s 16M foo seq -f%015g 65536 >>foo truncate -s 33M foo seq -f%015g 65536 >>foo truncate -s 50M foo seq -f%015g 65536 >>foo truncate -s 67M foo file_is_sparse foo atf_check -o file:foo cp foo /dev/stdout } atf_test_case sparse_trailing_hole sparse_trailing_hole_body() { # One megabyte of data followed by a 16-megabyte hole seq -f%015g 65536 >foo truncate -s 17M foo file_is_sparse foo atf_check cp foo bar files_are_equal foo bar file_is_sparse bar } 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_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_test_case directory_to_symlink directory_to_symlink_body() { mkdir -p foo ln -s .. foo/bar mkdir bar touch bar/baz atf_check -s not-exit:0 -e match:"Not a directory" \ cp -R bar foo atf_check -s not-exit:0 -e match:"Not a directory" \ cp -r bar foo } atf_test_case overwrite_directory overwrite_directory_body() { mkdir -p foo/bar/baz touch bar atf_check -s not-exit:0 -e match:"Is a directory" \ cp bar foo rm bar mkdir bar touch bar/baz atf_check -s not-exit:0 -e match:"Is a directory" \ cp -R bar foo atf_check -s not-exit:0 -e match:"Is a directory" \ cp -r bar foo } atf_test_case to_dir_dne to_dir_dne_body() { mkdir dir echo "foo" >dir/foo atf_check cp -r dir dne atf_check test -d dne atf_check test -f dne/foo atf_check cmp dir/foo dne/foo } atf_test_case to_nondir to_nondir_body() { echo "foo" >foo echo "bar" >bar echo "baz" >baz # This is described as “case 1” in source code comments atf_check cp foo bar atf_check cmp -s foo bar # This is “case 2”, the target must be a directory atf_check -s not-exit:0 -e match:"Not a directory" \ cp foo bar baz } atf_test_case to_deadlink to_deadlink_body() { echo "foo" >foo ln -s bar baz atf_check cp foo baz atf_check cmp -s foo bar } atf_test_case to_deadlink_append to_deadlink_append_body() { echo "foo" >foo mkdir bar ln -s baz bar/foo atf_check cp foo bar atf_check cmp -s foo bar/baz rm -f bar/foo bar/baz ln -s baz bar/foo atf_check cp foo bar/ atf_check cmp -s foo bar/baz rm -f bar/foo bar/baz ln -s $PWD/baz bar/foo atf_check cp foo bar/ atf_check cmp -s foo baz } atf_test_case to_dirlink to_dirlink_body() { mkdir src dir echo "foo" >src/file ln -s dir dst atf_check cp -r src dst atf_check cmp -s src/file dir/src/file rm -r dir/* atf_check cp -r src dst/ atf_check cmp -s src/file dir/src/file rm -r dir/* # If the source is a directory and ends in a slash, our cp has # traditionally copied the contents of the source rather than # the source itself. It is unclear whether this is intended # or simply a consequence of how FTS handles the situation. # Notably, GNU cp does not behave in this manner. atf_check cp -r src/ dst atf_check cmp -s src/file dir/file rm -r dir/* atf_check cp -r src/ dst/ atf_check cmp -s src/file dir/file rm -r dir/* } atf_test_case to_deaddirlink to_deaddirlink_body() { mkdir src echo "foo" >src/file ln -s dir dst # It is unclear which error we should expect in these cases. # Our current implementation always reports ENOTDIR, but one # might be equally justified in expecting EEXIST or ENOENT. # GNU cp reports EEXIST when the destination is given with a # trailing slash and “cannot overwrite non-directory with # directory” otherwise. atf_check -s not-exit:0 -e ignore \ cp -r src dst atf_check -s not-exit:0 -e ignore \ cp -r src dst/ atf_check -s not-exit:0 -e ignore \ cp -r src/ dst atf_check -s not-exit:0 -e ignore \ cp -r src/ dst/ atf_check -s not-exit:0 -e ignore \ cp -R src dst atf_check -s not-exit:0 -e ignore \ cp -R src dst/ atf_check -s not-exit:0 -e ignore \ cp -R src/ dst atf_check -s not-exit:0 -e ignore \ cp -R src/ dst/ } atf_test_case to_link_outside to_link_outside_body() { mkdir dir dst dst/dir echo "foo" >dir/file ln -s ../../file dst/dir/file atf_check \ -s exit:1 \ -e match:"dst/dir/file: Permission denied" \ cp -r dir dst } atf_test_case dstmode dstmode_body() { mkdir -m 0755 dir echo "foo" >dir/file umask 0177 atf_check cp -R dir dst umask 022 atf_check -o inline:"40600\n" stat -f%p dst atf_check chmod 0750 dst atf_check cmp dir/file dst/file } atf_test_case to_root cleanup to_root_head() { atf_set "require.user" "unprivileged" } to_root_body() { dst="test.$(atf_get ident).$$" echo "$dst" >dst echo "foo" >"$dst" atf_check -s not-exit:0 \ -e match:"^cp: /$dst: (Permission|Read-only)" \ cp "$dst" / atf_check -s not-exit:0 \ -e match:"^cp: /$dst: (Permission|Read-only)" \ cp "$dst" // } to_root_cleanup() { (dst=$(cat dst) && rm "/$dst") 2>/dev/null || true } 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 atf_add_test_case matching_srctgt_nonexistent atf_add_test_case pflag_acls atf_add_test_case pflag_flags 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 atf_add_test_case directory_to_symlink atf_add_test_case overwrite_directory atf_add_test_case to_dir_dne atf_add_test_case to_nondir atf_add_test_case to_deadlink atf_add_test_case to_deadlink_append atf_add_test_case to_dirlink atf_add_test_case to_deaddirlink atf_add_test_case to_link_outside atf_add_test_case dstmode atf_add_test_case to_root } diff --git a/bin/cp/utils.c b/bin/cp/utils.c index 3e669b4b513d..cfc0f0f12603 100644 --- a/bin/cp/utils.c +++ b/bin/cp/utils.c @@ -1,493 +1,493 @@ /*- * SPDX-License-Identifier: BSD-3-Clause * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include "extern.h" #define cp_pct(x, y) ((y == 0) ? 0 : (int)(100.0 * (x) / (y))) /* * Memory strategy threshold, in pages: if physmem is larger then this, use a * large buffer. */ #define PHYSPAGES_THRESHOLD (32*1024) /* Maximum buffer size in bytes - do not allow it to grow larger than this. */ #define BUFSIZE_MAX (2*1024*1024) /* * Small (default) buffer size in bytes. It's inefficient for this to be * smaller than MAXPHYS. */ #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) { static char *buf = NULL; static size_t bufsize; ssize_t rcount, wresid, wcount = 0; char *bufp; if (buf == NULL) { if (sysconf(_SC_PHYS_PAGES) > PHYSPAGES_THRESHOLD) bufsize = MIN(BUFSIZE_MAX, MAXPHYS * 8); else bufsize = BUFSIZE_SMALL; buf = malloc(bufsize); if (buf == NULL) err(1, "Not enough memory"); } rcount = read(from_fd, buf, bufsize); if (rcount <= 0) return (rcount); for (bufp = buf, wresid = rcount; ; bufp += wcount, wresid -= wcount) { wcount = write(to_fd, bufp, wresid); if (wcount <= 0) break; if (wcount >= wresid) break; } return (wcount < 0 ? wcount : rcount); } int copy_file(const FTSENT *entp, bool dne, bool beneath) { struct stat sb, *fs; ssize_t wcount; off_t wtotal; int ch, checkch, from_fd, rval, to_fd; int use_copy_file_range = 1; fs = entp->fts_statp; from_fd = to_fd = -1; if (!lflag && !sflag) { if ((from_fd = open(entp->fts_path, O_RDONLY, 0)) < 0 || fstat(from_fd, &sb) != 0) { warn("%s", entp->fts_path); if (from_fd >= 0) (void)close(from_fd); return (1); } /* * Check that the file hasn't been replaced with one of a * different type. This can happen if we've been asked to * copy something which is actively being modified and * lost the race, or if we've been asked to copy something * like /proc/X/fd/Y which stat(2) reports as S_IFREG but * is actually something else once you open it. */ if ((sb.st_mode & S_IFMT) != (fs->st_mode & S_IFMT)) { warnx("%s: File changed", entp->fts_path); (void)close(from_fd); return (1); } } /* * 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) { if (nflag) { if (vflag) printf("%s%s not overwritten\n", to.base, to.path); rval = 1; goto done; } else if (iflag) { (void)fprintf(stderr, "overwrite %s%s? %s", to.base, to.path, YESNO); checkch = ch = getchar(); while (ch != '\n' && ch != EOF) ch = getchar(); if (checkch != 'y' && checkch != 'Y') { (void)fprintf(stderr, "not overwritten\n"); rval = 1; goto done; } } if (fflag) { /* remove existing destination file */ (void)unlinkat(to.dir, to.path, beneath ? AT_RESOLVE_BENEATH : 0); dne = 1; } } rval = 0; if (lflag) { if (linkat(AT_FDCWD, entp->fts_path, to.dir, to.path, 0) != 0) { warn("%s%s", to.base, to.path); rval = 1; } goto done; } if (sflag) { if (symlinkat(entp->fts_path, to.dir, to.path) != 0) { warn("%s%s", to.base, to.path); rval = 1; } goto done; } if (!dne) { /* overwrite existing destination file */ to_fd = openat(to.dir, to.path, O_WRONLY | O_TRUNC | (beneath ? O_RESOLVE_BENEATH : 0), 0); } else { /* create new destination file */ to_fd = openat(to.dir, to.path, O_WRONLY | O_TRUNC | O_CREAT | (beneath ? O_RESOLVE_BENEATH : 0), fs->st_mode & ~(S_ISUID | S_ISGID)); } if (to_fd == -1) { warn("%s%s", to.base, to.path); rval = 1; goto done; } 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%s %3d%%\n", entp->fts_path, to.base, to.path, cp_pct(wtotal, fs->st_size)); } } while (wcount > 0); if (wcount < 0) { warn("%s", entp->fts_path); rval = 1; } /* * Don't remove the target even after an error. The target might * not be a regular file, or its attributes might be important, * or its contents might be irreplaceable. It would only be safe * to remove it if we created it and its length is 0. */ if (pflag && setfile(fs, to_fd, beneath)) rval = 1; if (pflag && preserve_fd_acls(from_fd, to_fd) != 0) rval = 1; if (close(to_fd)) { warn("%s%s", to.base, to.path); rval = 1; } done: if (from_fd != -1) (void)close(from_fd); return (rval); } int copy_link(const FTSENT *p, bool dne, bool beneath) { ssize_t len; int atflags = beneath ? AT_RESOLVE_BENEATH : 0; char llink[PATH_MAX]; if (!dne && nflag) { if (vflag) printf("%s%s not overwritten\n", to.base, to.path); return (1); } if ((len = readlink(p->fts_path, llink, sizeof(llink) - 1)) == -1) { warn("readlink: %s", p->fts_path); return (1); } llink[len] = '\0'; if (!dne && unlinkat(to.dir, to.path, atflags) != 0) { warn("unlink: %s%s", to.base, to.path); return (1); } if (symlinkat(llink, to.dir, to.path) != 0) { warn("symlink: %s", llink); return (1); } return (pflag ? setfile(p->fts_statp, -1, beneath) : 0); } int copy_fifo(struct stat *from_stat, bool dne, bool beneath) { int atflags = beneath ? AT_RESOLVE_BENEATH : 0; if (!dne && nflag) { if (vflag) printf("%s%s not overwritten\n", to.base, to.path); return (1); } if (!dne && unlinkat(to.dir, to.path, atflags) != 0) { warn("unlink: %s%s", to.base, to.path); return (1); } if (mkfifoat(to.dir, to.path, from_stat->st_mode) != 0) { warn("mkfifo: %s%s", to.base, to.path); return (1); } return (pflag ? setfile(from_stat, -1, beneath) : 0); } int copy_special(struct stat *from_stat, bool dne, bool beneath) { int atflags = beneath ? AT_RESOLVE_BENEATH : 0; if (!dne && nflag) { if (vflag) printf("%s%s not overwritten\n", to.base, to.path); return (1); } if (!dne && unlinkat(to.dir, to.path, atflags) != 0) { warn("unlink: %s%s", to.base, to.path); return (1); } if (mknodat(to.dir, to.path, from_stat->st_mode, from_stat->st_rdev) != 0) { warn("mknod: %s%s", to.base, to.path); return (1); } return (pflag ? setfile(from_stat, -1, beneath) : 0); } int setfile(struct stat *fs, int fd, bool beneath) { static struct timespec tspec[2]; struct stat ts; int atflags = beneath ? AT_RESOLVE_BENEATH : 0; int rval, gotstat, islink, fdval; rval = 0; fdval = fd != -1; islink = !fdval && S_ISLNK(fs->st_mode); if (islink) atflags |= AT_SYMLINK_NOFOLLOW; fs->st_mode &= S_ISUID | S_ISGID | S_ISVTX | S_IRWXU | S_IRWXG | S_IRWXO; tspec[0] = fs->st_atim; tspec[1] = fs->st_mtim; if (fdval ? futimens(fd, tspec) : utimensat(to.dir, to.path, tspec, atflags)) { warn("utimensat: %s%s", to.base, to.path); rval = 1; } if (fdval ? fstat(fd, &ts) : fstatat(to.dir, to.path, &ts, atflags)) { gotstat = 0; } else { gotstat = 1; ts.st_mode &= S_ISUID | S_ISGID | S_ISVTX | 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 (!gotstat || fs->st_uid != ts.st_uid || fs->st_gid != ts.st_gid) { if (fdval ? fchown(fd, fs->st_uid, fs->st_gid) : fchownat(to.dir, to.path, fs->st_uid, fs->st_gid, atflags)) { if (errno != EPERM) { warn("chown: %s%s", to.base, to.path); rval = 1; } fs->st_mode &= ~(S_ISUID | S_ISGID); } } if (!gotstat || fs->st_mode != ts.st_mode) { if (fdval ? fchmod(fd, fs->st_mode) : fchmodat(to.dir, to.path, fs->st_mode, atflags)) { warn("chmod: %s%s", to.base, to.path); rval = 1; } } if (!Nflag && (!gotstat || fs->st_flags != ts.st_flags)) { if (fdval ? fchflags(fd, fs->st_flags) : chflagsat(to.dir, to.path, fs->st_flags, atflags)) { /* * 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.) */ if (errno != EOPNOTSUPP || fs->st_flags != 0) { warn("chflags: %s%s", to.base, to.path); rval = 1; } } } return (rval); } int preserve_fd_acls(int source_fd, int dest_fd) { acl_t acl; acl_type_t acl_type; int acl_supported = 0, ret, trivial; ret = fpathconf(source_fd, _PC_ACL_NFS4); if (ret > 0 ) { acl_supported = 1; acl_type = ACL_TYPE_NFS4; } else if (ret < 0 && errno != EINVAL) { warn("fpathconf(..., _PC_ACL_NFS4) failed for %s%s", to.base, to.path); return (-1); } if (acl_supported == 0) { ret = fpathconf(source_fd, _PC_ACL_EXTENDED); if (ret > 0 ) { acl_supported = 1; acl_type = ACL_TYPE_ACCESS; } else if (ret < 0 && errno != EINVAL) { warn("fpathconf(..., _PC_ACL_EXTENDED) failed for %s%s", to.base, to.path); return (-1); } } if (acl_supported == 0) return (0); acl = acl_get_fd_np(source_fd, acl_type); if (acl == NULL) { warn("failed to get acl entries while setting %s%s", to.base, to.path); return (-1); } if (acl_is_trivial_np(acl, &trivial)) { warn("acl_is_trivial() failed for %s%s", to.base, to.path); acl_free(acl); return (-1); } if (trivial) { acl_free(acl); return (0); } if (acl_set_fd_np(dest_fd, acl, acl_type) < 0) { warn("failed to set acl entries for %s%s", to.base, to.path); acl_free(acl); return (-1); } acl_free(acl); return (0); } int preserve_dir_acls(const char *source_dir, const char *dest_dir) { int source_fd = -1, dest_fd = -1, ret; - if ((source_fd = open(source_dir, O_PATH)) < 0) { + if ((source_fd = open(source_dir, O_DIRECTORY | O_RDONLY)) < 0) { warn("%s: failed to copy ACLs", source_dir); return (-1); } dest_fd = (*dest_dir == '\0') ? to.dir : openat(to.dir, dest_dir, O_DIRECTORY, AT_RESOLVE_BENEATH); if (dest_fd < 0) { warn("%s: failed to copy ACLs to %s%s", source_dir, to.base, dest_dir); close(source_fd); return (-1); } if ((ret = preserve_fd_acls(source_fd, dest_fd)) != 0) { /* preserve_fd_acls() already printed a message */ } if (dest_fd != to.dir) close(dest_fd); close(source_fd); return (ret); } void usage(void) { (void)fprintf(stderr, "%s\n%s\n", "usage: cp [-R [-H | -L | -P]] [-f | -i | -n] [-alpsvx] " "source_file target_file", " cp [-R [-H | -L | -P]] [-f | -i | -n] [-alpsvx] " "source_file ... " "target_directory"); exit(EX_USAGE); }