diff options
Diffstat (limited to 'tests/cp')
62 files changed, 3773 insertions, 0 deletions
diff --git a/tests/cp/abuse.sh b/tests/cp/abuse.sh new file mode 100755 index 0000000..4e65717 --- /dev/null +++ b/tests/cp/abuse.sh @@ -0,0 +1,50 @@ +#!/bin/sh +# ensure that cp does not write through a just-copied symlink + +# Copyright (C) 2007-2022 Free Software Foundation, Inc. + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src +print_ver_ cp + +mkdir a b c || framework_failure_ +ln -s ../t a/1 || framework_failure_ +echo payload > b/1 || framework_failure_ + +echo "cp: will not copy 'b/1' through just-created symlink 'c/1'" \ + > exp || framework_failure_ + +# Check both cases: a dangling symlink, and one pointing to a writable file. + +for i in dangling-dest existing-dest; do + test $i = existing-dest && echo i > t + test $i = dangling-dest && rm -f t + + cp -dR a/1 b/1 c 2> out && fail=1 + + compare exp out || fail=1 + + # When the destination is a dangling symlink, + # ensure that cp does not create it. + test $i = dangling-dest \ + && test -f t && fail=1 + + # When the destination symlink points to a writable file, + # ensure that cp does not change it. + test $i = existing-dest \ + && case $(cat t) in i);; *) fail=1;; esac +done + +Exit $fail diff --git a/tests/cp/acl.sh b/tests/cp/acl.sh new file mode 100755 index 0000000..152b8d6 --- /dev/null +++ b/tests/cp/acl.sh @@ -0,0 +1,60 @@ +#!/bin/sh +# copy files/directories across file system boundaries +# and make sure acls are preserved appropriately + +# Copyright (C) 2005-2022 Free Software Foundation, Inc. + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src +print_ver_ cp + +require_acl_ + +# Skip this test if cp was built without ACL support: +grep '^#define USE_ACL 1' $CONFIG_HEADER > /dev/null || + skip_ "insufficient ACL support" + +mkdir -p a b || framework_failure_ +touch a/file || framework_failure_ + +# Ensure that setfacl and getfacl work on this file system. +skip=no +acl1=$(cd a && getfacl file) || skip=yes +setfacl -m user:bin:rw- a/file 2> /dev/null || skip=yes +test $skip = yes && + skip_ "'.' is not on a suitable file system for this test" + +# copy a file without preserving permissions +cp a/file b/ || fail=1 +acl2=$(cd b && getfacl file) || framework_failure_ +test "$acl1" = "$acl2" || fail=1 + +# Update with acl set above +acl1=$(cd a && getfacl file) || framework_failure_ + +# copy a file, preserving permissions +cp -p a/file b/ || fail=1 +acl2=$(cd b && getfacl file) || framework_failure_ +test "$acl1" = "$acl2" || fail=1 + +# copy a file, preserving permissions, with --attributes-only +echo > a/file || framework_failure_ # add some data +test -s a/file || framework_failure_ +cp -p --attributes-only a/file b/ || fail=1 +compare /dev/null b/file || fail=1 +acl2=$(cd b && getfacl file) || framework_failure_ +test "$acl1" = "$acl2" || fail=1 + +Exit $fail diff --git a/tests/cp/attr-existing.sh b/tests/cp/attr-existing.sh new file mode 100755 index 0000000..a6ff58b --- /dev/null +++ b/tests/cp/attr-existing.sh @@ -0,0 +1,44 @@ +#!/bin/sh +# Make sure cp --attributes-only doesn't truncate existing data + +# Copyright 2012-2022 Free Software Foundation, Inc. + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src +print_ver_ cp + +printf '1' > file1 || framework_failure_ +printf '2' > file2 || framework_failure_ +printf '2' > file2.exp || framework_failure_ + +cp --attributes-only file1 file2 || fail=1 +cmp file2 file2.exp || fail=1 + +# coreutils v8.32 and before would remove destination files +# if hardlinked or the source was not a regular file. +ln file2 link2 || framework_failure_ +cp -a --attributes-only file1 file2 || fail=1 +cmp file2 file2.exp || fail=1 + +ln -s file1 sym1 || framework_failure_ +returns_ 1 cp -a --attributes-only sym1 file2 || fail=1 +cmp file2 file2.exp || fail=1 + +# One can still force removal though +cp -a --remove-destination --attributes-only sym1 file2 || fail=1 +test -L file2 || fail=1 +cmp file1 file2 || fail=1 + +Exit $fail diff --git a/tests/cp/backup-1.sh b/tests/cp/backup-1.sh new file mode 100755 index 0000000..9c1750b --- /dev/null +++ b/tests/cp/backup-1.sh @@ -0,0 +1,37 @@ +#!/bin/sh +# Test cp backup. + +# Copyright (C) 1997-2022 Free Software Foundation, Inc. + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src +print_ver_ cp + +suffix=.b +file=F +file_backup="$file$suffix" + +echo test > $file || framework_failure_ + +# Specify both version control and suffix so the environment variables +# (possibly set by the user running these tests) aren't used. +cp --force --backup=simple --suffix=$suffix $file $file || fail=1 +cp -T --force --backup=simple --suffix=$suffix $file $file || fail=1 + +test -f $file || fail=1 +test -f $file_backup || fail=1 +compare $file $file_backup > /dev/null || fail=1 + +Exit $fail diff --git a/tests/cp/backup-dir.sh b/tests/cp/backup-dir.sh new file mode 100755 index 0000000..531367d --- /dev/null +++ b/tests/cp/backup-dir.sh @@ -0,0 +1,32 @@ +#!/bin/sh +# Ensure that cp -b doesn't back up directories. + +# Copyright (C) 2006-2022 Free Software Foundation, Inc. + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src +print_ver_ cp + +mkdir x y || framework_failure_ + + +cp -a x y || fail=1 + +# This would mistakenly create a backup of y/x (y/x~) in coreutils-6.3. +cp -ab x y || fail=1 +test -d y/x || fail=1 +test -d y/x~ && fail=1 + +Exit $fail diff --git a/tests/cp/backup-is-src.sh b/tests/cp/backup-is-src.sh new file mode 100755 index 0000000..50497aa --- /dev/null +++ b/tests/cp/backup-is-src.sh @@ -0,0 +1,36 @@ +#!/bin/sh +# Test cp backup to source file. + +# Copyright (C) 1998-2022 Free Software Foundation, Inc. + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src +print_ver_ cp + +echo a > a || framework_failure_ +echo a-tilde > a~ || framework_failure_ + +# This cp command should exit nonzero. +cp --b=simple a~ a > out 2>&1 && fail=1 + +sed "s,cp:,XXX:," out > out2 + +cat > exp <<\EOF +XXX: backing up 'a' might destroy source; 'a~' not copied +EOF + +compare exp out2 || fail=1 + +Exit $fail diff --git a/tests/cp/capability.sh b/tests/cp/capability.sh new file mode 100755 index 0000000..f67b2cb --- /dev/null +++ b/tests/cp/capability.sh @@ -0,0 +1,52 @@ +#!/bin/sh +# Ensure cp --preserves copies capabilities + +# Copyright (C) 2010-2022 Free Software Foundation, Inc. + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src +print_ver_ cp +require_root_ +working_umask_or_skip_ + + +grep '^#define HAVE_CAP 1' $CONFIG_HEADER > /dev/null \ + || skip_ "configured without libcap support" + +(setcap --help) 2>&1 |grep 'usage: setcap' > /dev/null \ + || skip_ "setcap utility not found" +(getcap --help) 2>&1 |grep 'usage: getcap' > /dev/null \ + || skip_ "getcap utility not found" + + +touch file || framework_failure_ +chown $NON_ROOT_USERNAME file || framework_failure_ + +setcap 'cap_net_bind_service=ep' file || + skip_ "setcap doesn't work" +getcap file | grep cap_net_bind_service >/dev/null || + skip_ "getcap doesn't work" + +cp --preserve=xattr file copy1 || fail=1 + +# Before coreutils 8.5 the capabilities would not be preserved, +# as the owner was set _after_ copying xattrs, thus clearing any capabilities. +cp --preserve=all file copy2 || fail=1 + +for file in copy1 copy2; do + getcap $file | grep cap_net_bind_service >/dev/null || fail=1 +done + +Exit $fail diff --git a/tests/cp/copy-FMR.sh b/tests/cp/copy-FMR.sh new file mode 100755 index 0000000..e6dcce7 --- /dev/null +++ b/tests/cp/copy-FMR.sh @@ -0,0 +1,31 @@ +#!/bin/sh +# Trigger a free-memory read bug in cp from coreutils-[8.11..8.19] + +# Copyright (C) 2012-2022 Free Software Foundation, Inc. + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src +print_ver_ cp + +require_valgrind_ +require_perl_ + +# Trigger FMR in fiemap logic from v8.11..v8.19 +$PERL -e 'for (1..600) { sysseek (*STDOUT, 4096, 1)' \ + -e '&& syswrite (*STDOUT, "a" x 1024) or die "$!"}' > j || fail=1 +valgrind --quiet --error-exitcode=3 cp --reflink=never j j2 || fail=1 +cmp j j2 || fail=1 + +Exit $fail diff --git a/tests/cp/cp-HL.sh b/tests/cp/cp-HL.sh new file mode 100755 index 0000000..54f8a79 --- /dev/null +++ b/tests/cp/cp-HL.sh @@ -0,0 +1,40 @@ +#!/bin/sh +# test cp's -H and -L options + +# Copyright (C) 2000-2022 Free Software Foundation, Inc. + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src +print_ver_ cp + +mkdir src-dir dest-dir || framework_failure_ +echo f > f || framework_failure_ +ln -s f slink || framework_failure_ +ln -s no-such-file src-dir/slink || framework_failure_ + + +cp -H -R slink src-dir dest-dir || fail=1 +test -d src-dir || fail=1 +test -d dest-dir/src-dir || fail=1 + +# Expect this to succeed since this slink is not a symlink +cat dest-dir/slink > /dev/null 2>&1 || fail=1 + +# Expect this to fail since *this* slink is a dangling symlink. +returns_ 1 cat dest-dir/src-dir/slink >/dev/null 2>&1 || fail=1 + +# FIXME: test -L, too. + +Exit $fail diff --git a/tests/cp/cp-a-selinux.sh b/tests/cp/cp-a-selinux.sh new file mode 100755 index 0000000..f0872bc --- /dev/null +++ b/tests/cp/cp-a-selinux.sh @@ -0,0 +1,228 @@ +#!/bin/sh +# Ensure that cp -Z, -a and cp --preserve=context work properly. +# In particular, test on a writable NFS partition. +# Check also locally if --preserve=context, -a and --preserve=all +# does work + +# Copyright (C) 2007-2022 Free Software Foundation, Inc. + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src +print_ver_ cp +require_root_ +require_selinux_ + +cwd=$(pwd) +cleanup_() { cd /; umount "$cwd/mnt"; } + +# This context is special: it works even when mcstransd isn't running. +ctx='root:object_r:tmp_t' +mls_enabled_ && ctx="$ctx:s0" + +# Check basic functionality - before check on fixed context mount +touch c || framework_failure_ +chcon $ctx c || skip_ "Failed to set context: $ctx" +cp -a c d 2>err || framework_failure_ +cp --preserve=context c e || framework_failure_ +cp --preserve=all c f || framework_failure_ +ls -Z d | grep $ctx || fail=1 +# there must be no stderr output for -a +compare /dev/null err || fail=1 +ls -Z e | grep $ctx || fail=1 +ls -Z f | grep $ctx || fail=1 +rm -f f + +# Check handling of existing dirs which requires specific handling +# due to recursion, and was handled incorrectly in coreutils-8.22 +# Note standard permissions are updated for existing directories +# in the destination, so SELinux contexts should be updated too. +mkdir -p backup/existing_dir/ || framework_failure_ +ls -Zd backup/existing_dir > ed_ctx || fail=1 +grep $ctx ed_ctx && framework_failure_ +touch backup/existing_dir/file || framework_failure_ +chcon $ctx backup/existing_dir/file || framework_failure_ +# Set the dir context to ensure it is reset +mkdir -p --context="$ctx" restore/existing_dir || framework_failure_ +# Copy and ensure existing directories updated +cp -a backup/. restore/ || fail=1 +ls -Zd restore/existing_dir > ed_ctx || fail=1 +grep $ctx ed_ctx && + { ls -lZd restore/existing_dir; fail=1; } + +# Check context preserved with directories created with --parents, +# which was not handled before coreutils-8.27 +mkdir -p parents/a/b || framework_failure_ +ls -Zd parents/a/b > ed_ctx || fail=1 +grep $ctx ed_ctx && framework_failure_ +touch parents/a/b/file || framework_failure_ +chcon $ctx parents/a/b || framework_failure_ +# Set the dir context to ensure it is reset +mkdir -p --context="$ctx" parents_dest/parents/a || framework_failure_ +# Copy and ensure existing directories updated +cp -r --parents --preserve=context parents/a/b/file parents_dest || fail=1 +# Check new context +ls -Zd parents_dest/parents/a/b > ed_ctx || fail=1 +grep $ctx ed_ctx || + { ls -lZd parents_dest/parents/a/b; fail=1; } +# Check updated context +ls -Zd parents_dest/parents/a > ed_ctx || fail=1 +grep $ctx ed_ctx && + { ls -lZd parents_dest/parents/a; fail=1; } + +# Check restorecon (-Z) functionality for file and directory +# Also make a dir with our known context +mkdir c_d || framework_failure_ +chcon $ctx c_d || framework_failure_ +# Get the type of this known context for file and dir for tracing +old_type_f=$(get_selinux_type c) +old_type_d=$(get_selinux_type c_d) +# Setup copies for manipulation with restorecon +# and get the adjusted type for comparison +cp -a c Z1 || fail=1 +cp -a c_d Z1_d || fail=1 +if restorecon Z1 Z1_d 2>restorecon.err \ + && compare /dev/null restorecon.err; then + new_type_f=$(get_selinux_type Z1) + new_type_d=$(get_selinux_type Z1_d) + + # Ensure -Z sets the type like restorecon does + cp -Z c Z2 || fail=1 + cpZ_type_f=$(get_selinux_type Z2) + test "$cpZ_type_f" = "$new_type_f" || fail=1 + + # Ensure -Z overrides -a and that dirs are handled too + cp -aZ c Z3 || fail=1 + cp -aZ c_d Z3_d || fail=1 + cpaZ_type_f=$(get_selinux_type Z3) + cpaZ_type_d=$(get_selinux_type Z3_d) + test "$cpaZ_type_f" = "$new_type_f" || fail=1 + test "$cpaZ_type_d" = "$new_type_d" || fail=1 + + # Ensure -Z sets the type for existing files + mkdir -p existing/c_d || framework_failure_ + touch existing/c || framework_failure_ + cp -aZ c c_d existing || fail=1 + cpaZ_type_f=$(get_selinux_type existing/c) + cpaZ_type_d=$(get_selinux_type existing/c_d) + test "$cpaZ_type_f" = "$new_type_f" || fail=1 + test "$cpaZ_type_d" = "$new_type_d" || fail=1 +fi + +skip=0 +# Create a file system, then mount it with the context=... option. +dd if=/dev/zero of=blob bs=8192 count=200 || skip=1 +mkdir mnt || skip=1 +mkfs -t ext2 -F blob || + skip_ "failed to create an ext2 file system" + +mount -oloop,context=$ctx blob mnt || skip=1 +test $skip = 1 \ + && skip_ "insufficient mount/ext2 support" + +cd mnt || framework_failure_ + +# Create files with hopefully different contexts +echo > ../f || framework_failure_ +echo > g || framework_failure_ +test "$(stat -c%C ../f)" = "$(stat -c%C g)" && + skip_ "files on separate file systems have the same security context" + +# /bin/cp from coreutils-6.7-3.fc7 would fail this test by letting cp +# succeed (giving no diagnostics), yet leaving the destination file empty. +cp -a ../f g 2>err || fail=1 +test -s g || fail=1 # The destination file must not be empty. +compare /dev/null err || fail=1 + +# ===================================================== +# Here, we expect cp to succeed and not warn with "Operation not supported" +rm -f g +echo > g +cp --preserve=all ../f g 2>err || fail=1 +test -s g || fail=1 +grep "Operation not supported" err && fail=1 + +# ===================================================== +# The same as above except destination does not exist +rm -f g +cp --preserve=all ../f g 2>err || fail=1 +test -s g || fail=1 +grep "Operation not supported" err && fail=1 + +# An alternative to the following approach would be to run in a confined +# domain (maybe creating/loading it) that lacks the required permissions +# to the file type. +# Note: this test could also be run by a regular (non-root) user in an +# NFS mounted directory. When doing that, I get this diagnostic: +# cp: failed to set the security context of 'g' to 'system_u:object_r:nfs_t': \ +# Operation not supported +cat <<\EOF > exp || framework_failure_ +cp: failed to set the security context of +EOF + +rm -f g +echo > g +# ===================================================== +# Here, we expect cp to fail, because it cannot set the SELinux +# security context through NFS or a mount with fixed context. +cp --preserve=context ../f g 2> out && fail=1 +# Here, we *do* expect the destination to be empty. +compare /dev/null g || fail=1 +sed "s/ .g'.*//" out > k +mv k out +compare exp out || fail=1 + +rm -f g +echo > g +# Check if -a option doesn't silence --preserve=context option diagnostics +cp -a --preserve=context ../f g 2> out2 && fail=1 +# Here, we *do* expect the destination to be empty. +compare /dev/null g || fail=1 +sed "s/ .g'.*//" out2 > k +mv k out2 +compare exp out2 || fail=1 + +for no_g_cmd in '' 'rm -f g'; do + # restorecon equivalent. Note even though the context + # returned from matchpathcon() will not match $ctx + # the resulting ENOTSUP warning will be suppressed. + + # With absolute path + $no_g_cmd + cp -Z ../f $(realpath g) || fail=1 + # With relative path + $no_g_cmd + cp -Z ../f g || fail=1 + # -Z overrides -a + $no_g_cmd + cp -Z -a ../f g || fail=1 + # -Z doesn't take an arg + $no_g_cmd + returns_ 1 cp -Z "$ctx" ../f g || fail=1 + + # Explicit context + $no_g_cmd + # Explicitly defaulting to the global $ctx should work + cp --context="$ctx" ../f g || fail=1 + # --context overrides -a + $no_g_cmd + cp -a --context="$ctx" ../f g || fail=1 +done + +# Mutually exclusive options +returns_ 1 cp -Z --preserve=context ../f g || fail=1 +returns_ 1 cp --preserve=context -Z ../f g || fail=1 +returns_ 1 cp --preserve=context --context="$ctx" ../f g || fail=1 + +Exit $fail diff --git a/tests/cp/cp-deref.sh b/tests/cp/cp-deref.sh new file mode 100755 index 0000000..b5eed20 --- /dev/null +++ b/tests/cp/cp-deref.sh @@ -0,0 +1,34 @@ +#!/bin/sh +# cp -RL dir1 dir2' must handle the case in which each of dir1 and dir2 +# contain a symlink pointing to some third directory. + +# Copyright (C) 2006-2022 Free Software Foundation, Inc. + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src +print_ver_ cp + +mkdir a b c d || framework_failure_ +ln -s ../c a || framework_failure_ +ln -s ../c b || framework_failure_ + + +# Before coreutils-5.94, the following would fail with this message: +# cp: will not create hard link 'd/b/c' to directory 'd/a/c' +cp -RL a b d || fail=1 +test -d a/c || fail=1 +test -d b/c || fail=1 + +Exit $fail diff --git a/tests/cp/cp-i.sh b/tests/cp/cp-i.sh new file mode 100755 index 0000000..f2cafa7 --- /dev/null +++ b/tests/cp/cp-i.sh @@ -0,0 +1,65 @@ +#!/bin/sh +# Test whether cp -i prompts in the right place. + +# Copyright (C) 2006-2022 Free Software Foundation, Inc. + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src +print_ver_ cp + +mkdir -p a b/a/c || framework_failure_ +touch a/c || framework_failure_ + + +# coreutils 6.2 cp would neglect to prompt in this case. +echo n | cp -iR a b 2>/dev/null || fail=1 + +# test miscellaneous combinations of -f -i -n parameters +touch c d || framework_failure_ +echo "'c' -> 'd'" > out_copy +> out_empty + +# ask for overwrite, answer no +echo n | cp -vi c d 2>/dev/null > out1 || fail=1 +compare out1 out_empty || fail=1 + +# ask for overwrite, answer yes +echo y | cp -vi c d 2>/dev/null > out2 || fail=1 +compare out2 out_copy || fail=1 + +# -i wins over -n +echo y | cp -vni c d 2>/dev/null > out3 || fail=1 +compare out3 out_copy || fail=1 + +# -n wins over -i +echo y | cp -vin c d 2>/dev/null > out4 || fail=1 +compare out4 out_empty || fail=1 + +# ask for overwrite, answer yes +echo y | cp -vfi c d 2>/dev/null > out5 || fail=1 +compare out5 out_copy || fail=1 + +# do not ask, prevent from overwrite +echo n | cp -vfn c d 2>/dev/null > out6 || fail=1 +compare out6 out_empty || fail=1 + +# do not ask, prevent from overwrite +echo n | cp -vnf c d 2>/dev/null > out7 || fail=1 +compare out7 out_empty || fail=1 + +# options --backup and --no-clobber are mutually exclusive +returns_ 1 cp -bn c d 2>/dev/null || fail=1 + +Exit $fail diff --git a/tests/cp/cp-mv-backup.sh b/tests/cp/cp-mv-backup.sh new file mode 100755 index 0000000..36f6501 --- /dev/null +++ b/tests/cp/cp-mv-backup.sh @@ -0,0 +1,92 @@ +#!/bin/sh +# Test basic --backup functionality for both cp and mv. + +# Copyright (C) 1999-2022 Free Software Foundation, Inc. + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src +print_ver_ cp + +umask 022 + +# Be careful to close $actual before removing the containing directory. +# Use '1>&2' rather than '1<&-' since the latter appears not to work +# with /bin/sh from powerpc-ibm-aix4.2.0.0. + +actual=actual +expected=expected + +exec 3>&1 1> $actual + +for prog in cp mv; do + for initial_files in 'x' 'x y' 'x y y~' 'x y y.~1~' 'x y y~ y.~1~'; do + for opt in none off numbered t existing nil simple never; do + touch $initial_files + $prog --backup=$opt x y || fail=1 + echo $initial_files $opt: $(ls [xy]*); rm -f x y y~ y.~?~ + done + done +done + +cat <<\EOF > $expected-tmp +x none: x y +x off: x y +x numbered: x y +x t: x y +x existing: x y +x nil: x y +x simple: x y +x never: x y +x y none: x y +x y off: x y +x y numbered: x y y.~1~ +x y t: x y y.~1~ +x y existing: x y y~ +x y nil: x y y~ +x y simple: x y y~ +x y never: x y y~ +x y y~ none: x y y~ +x y y~ off: x y y~ +x y y~ numbered: x y y.~1~ y~ +x y y~ t: x y y.~1~ y~ +x y y~ existing: x y y~ +x y y~ nil: x y y~ +x y y~ simple: x y y~ +x y y~ never: x y y~ +x y y.~1~ none: x y y.~1~ +x y y.~1~ off: x y y.~1~ +x y y.~1~ numbered: x y y.~1~ y.~2~ +x y y.~1~ t: x y y.~1~ y.~2~ +x y y.~1~ existing: x y y.~1~ y.~2~ +x y y.~1~ nil: x y y.~1~ y.~2~ +x y y.~1~ simple: x y y.~1~ y~ +x y y.~1~ never: x y y.~1~ y~ +x y y~ y.~1~ none: x y y.~1~ y~ +x y y~ y.~1~ off: x y y.~1~ y~ +x y y~ y.~1~ numbered: x y y.~1~ y.~2~ y~ +x y y~ y.~1~ t: x y y.~1~ y.~2~ y~ +x y y~ y.~1~ existing: x y y.~1~ y.~2~ y~ +x y y~ y.~1~ nil: x y y.~1~ y.~2~ y~ +x y y~ y.~1~ simple: x y y.~1~ y~ +x y y~ y.~1~ never: x y y.~1~ y~ +EOF + +sed 's/: x/:/' $expected-tmp |cat $expected-tmp - > $expected + +exec 1>&3 3>&- + +compare $expected $actual || fail=1 + +Exit $fail diff --git a/tests/cp/cp-mv-enotsup-xattr.sh b/tests/cp/cp-mv-enotsup-xattr.sh new file mode 100755 index 0000000..9164c6e --- /dev/null +++ b/tests/cp/cp-mv-enotsup-xattr.sh @@ -0,0 +1,132 @@ +#!/bin/sh +# Ensure that mv, cp -a and cp --preserve=xattr(all) options do work +# as expected on file systems without their support and do show correct +# diagnostics when required + +# Copyright (C) 2009-2022 Free Software Foundation, Inc. + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src +print_ver_ cp mv + +require_root_ + +cwd=$(pwd) +cleanup_() { cd /; umount "$cwd/noxattr"; umount "$cwd/xattr"; } + +skip=0 + +# Mount an ext2 loopback file system at $WHERE with $OPTS +make_fs() { + where="$1" + opts="$2" + + fs="$where.bin" + + dd if=/dev/zero of="$fs" bs=8192 count=200 > /dev/null 2>&1 \ + || skip=1 + mkdir "$where" || skip=1 + mkfs -t ext2 -F "$fs" || + skip_ "failed to create ext2 file system" + mount -oloop,$opts "$fs" "$where" || skip=1 + echo test > "$where"/f || skip=1 + test -s "$where"/f || skip=1 + + test $skip = 1 && + skip_ "insufficient mount/ext2 support" +} + +make_fs noxattr nouser_xattr +make_fs xattr user_xattr + +# testing xattr name-value pair +xattr_name="user.foo" +xattr_value="bar" +xattr_pair="$xattr_name=\"$xattr_value\"" + +echo test > xattr/a || framework_failure_ +getfattr -d xattr/a >out_a || skip_ "failed to get xattr of file" +grep -F "$xattr_pair" out_a >/dev/null && framework_failure_ +setfattr -n "$xattr_name" -v "$xattr_value" xattr/a >out_a \ + || skip_ "failed to set xattr of file" +getfattr -d xattr/a >out_a || skip_ "failed to get xattr of file" +grep -F "$xattr_pair" out_a >/dev/null \ + || skip_ "failed to set xattr of file" + + +# This should pass without diagnostics +cp -a xattr/a noxattr/ 2>err || fail=1 +test -s noxattr/a || fail=1 # destination file must not be empty +compare /dev/null err || fail=1 + +rm -f err noxattr/a + +# This should pass without diagnostics (new file) +cp --preserve=all xattr/a noxattr/ 2>err || fail=1 +test -s noxattr/a || fail=1 # destination file must not be empty +compare /dev/null err || fail=1 + +# This should pass without diagnostics (existing file) +cp --preserve=all xattr/a noxattr/ 2>err || fail=1 +test -s noxattr/a || fail=1 # destination file must not be empty +compare /dev/null err || fail=1 + +rm -f err noxattr/a + +# This should fail with corresponding diagnostics +cp -a --preserve=xattr xattr/a noxattr/ 2>err && fail=1 +if grep '^#define USE_XATTR 1' $CONFIG_HEADER > /dev/null; then +cat <<\EOF > exp +cp: setting attributes for 'noxattr/a': Operation not supported +EOF +else +cat <<\EOF > exp +cp: cannot preserve extended attributes, cp is built without xattr support +EOF +fi + +compare exp err || fail=1 + +rm -f err noxattr/a + +# This should pass without diagnostics +mv xattr/a noxattr/ 2>err || fail=1 +test -s noxattr/a || fail=1 # destination file must not be empty +compare /dev/null err || fail=1 + +# This should pass and copy xattrs of the symlink +# since the xattrs used here are not in the 'user.' namespace. +# Up to and including coreutils-8.22 xattrs of symlinks +# were not copied across file systems. +ln -s 'foo' xattr/symlink || framework_failure_ +# Note 'user.' namespace is only supported on regular files/dirs +# so use the 'trusted.' namespace here +txattr='trusted.overlay.whiteout' +if setfattr -hn "$txattr" -v y xattr/symlink; then + # Note only root can read the 'trusted.' namespace + if getfattr -h -m- -d xattr/symlink | grep -F "$txattr"; then + mv xattr/symlink noxattr/ 2>err || fail=1 + if grep '^#define USE_XATTR 1' $CONFIG_HEADER > /dev/null; then + getfattr -h -m- -d noxattr/symlink | grep -F "$txattr" || fail=1 + fi + compare /dev/null err || fail=1 + else + echo "failed to get '$txattr' xattr. skipping symlink check" >&2 + fi +else + echo "failed to set '$txattr' xattr. skipping symlink check" >&2 +fi + +Exit $fail diff --git a/tests/cp/cp-parents.sh b/tests/cp/cp-parents.sh new file mode 100755 index 0000000..9644469 --- /dev/null +++ b/tests/cp/cp-parents.sh @@ -0,0 +1,69 @@ +#!/bin/sh + +# Copyright (C) 2000-2022 Free Software Foundation, Inc. + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src +print_ver_ cp + +working_umask_or_skip_ + +# Run the setgid check from the just-created directory. +skip_if_setgid_ + +{ + mkdir foo bar + mkdir -p a/b/c d e g + ln -s d/a sym + touch f +} || framework_failure_ + +# With 4.0.37 and earlier (back to when?), this would fail +# with the failed assertion from dirname.c due to the trailing slash. +cp -R --parents foo/ bar || fail=1 + +# Exercise the make_path and re_protect code in cp.c. +# FIXME: compare verbose output with expected output. +cp --verbose -a --parents a/b/c d > /dev/null 2>&1 || fail=1 +test -d d/a/b/c || fail=1 + +# With 6.7 and earlier, cp --parents f/g d would mistakenly create a +# directory d/f, even though f is a regular file. +returns_ 1 cp --parents f/g d 2>/dev/null || fail=1 +test -d d/f && fail=1 + +# Check that re_protect works. +chmod go=w d/a || framework_failure_ +cp -a --parents d/a/b/c e || fail=1 +cp -a --parents sym/b/c g || fail=1 +p=$(ls -ld e/d|cut -b-10); case $p in drwxr-xr-x);; *) fail=1;; esac +p=$(ls -ld e/d/a|cut -b-10); case $p in drwx-w--w-);; *) fail=1;; esac +p=$(ls -ld g/sym|cut -b-10); case $p in drwx-w--w-);; *) fail=1;; esac +p=$(ls -ld e/d/a/b/c|cut -b-10); case $p in drwxr-xr-x);; *) fail=1;; esac +p=$(ls -ld g/sym/b/c|cut -b-10); case $p in drwxr-xr-x);; *) fail=1;; esac + +# Before 8.25 cp --parents --no-preserve=mode would copy +# the mode bits from the source directories +{ + mkdir -p np/b && + chmod 0700 np && + touch np/b/file && + chmod 775 np/b/file && + mkdir np_dest +} || framework_failure_ +cp --parents --no-preserve=mode np/b/file np_dest/ || fail=1 +p=$(ls -ld np_dest/np|cut -b-10); case $p in drwxr-xr-x);; *) fail=1;; esac + +Exit $fail diff --git a/tests/cp/cross-dev-symlink.sh b/tests/cp/cross-dev-symlink.sh new file mode 100755 index 0000000..f7df222 --- /dev/null +++ b/tests/cp/cross-dev-symlink.sh @@ -0,0 +1,38 @@ +#!/bin/sh +# Ensure symlinks can be replaced across devices + +# Copyright (C) 2018-2022 Free Software Foundation, Inc. + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src +print_ver_ cp +require_root_ + +cwd=$(pwd) +cleanup_() { cd /; umount "$cwd/mnt"; } + +truncate -s100M fs.img || framework_failure_ +mkfs -t ext4 fs.img || skip_ 'failed to create ext4 file system' +mkdir mnt || framework_failure_ +mount fs.img mnt || skip_ 'failed to mount ext4 file system' + +mkdir mnt/path1 || framework_failure_ +touch mnt/path1/file || framework_failure_ +mkdir path2 || framework_failure_ +cd path2 && ln -s ../mnt/path1/file || framework_failure_ + +cp -dsf ../mnt/path1/file . || fail=1 + +Exit $fail diff --git a/tests/cp/deref-slink.sh b/tests/cp/deref-slink.sh new file mode 100755 index 0000000..c7561c0 --- /dev/null +++ b/tests/cp/deref-slink.sh @@ -0,0 +1,28 @@ +#!/bin/sh +# Demonstrate bug when using -d with an existing destination file +# that is a symlink. + +# Copyright (C) 2000-2022 Free Software Foundation, Inc. + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src +print_ver_ cp + +touch f slink-target || framework_failure_ +ln -s slink-target slink || framework_failure_ + +cp -d f slink || fail=1 + +Exit $fail diff --git a/tests/cp/dir-rm-dest.sh b/tests/cp/dir-rm-dest.sh new file mode 100755 index 0000000..c784abb --- /dev/null +++ b/tests/cp/dir-rm-dest.sh @@ -0,0 +1,35 @@ +#!/bin/sh +# verify cp's --remove-destination option + +# Copyright (C) 2000-2022 Free Software Foundation, Inc. + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src +print_ver_ cp + +mkdir d e || framework_failure_ + +# Do it once with no destination... +cp -R --remove-destination d e || fail=1 + +# ...and again, with an existing destination. +cp -R --remove-destination d e || fail=1 + +# verify no ELOOP which was the case in <= 8.29 +ln -s loop loop || framework_failure_ +touch file || framework_failure_ +cp --remove-destination file loop || fail=1 + +Exit $fail diff --git a/tests/cp/dir-slash.sh b/tests/cp/dir-slash.sh new file mode 100755 index 0000000..e3e5a1f --- /dev/null +++ b/tests/cp/dir-slash.sh @@ -0,0 +1,35 @@ +#!/bin/sh +# Make sure that cp -R DIR1 DIR2 does the right thing +# when DIR1 is written with a trailing slash. + +# Copyright (C) 2000-2022 Free Software Foundation, Inc. + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src +print_ver_ cp + +mkdir dir1 dir2 || framework_failure_ +touch dir1/file || framework_failure_ + +cp -R dir1/ dir2 || fail=1 + +# This file should not exist, but it did with fileutils-4.0w. +test -r dir2/file && fail=1 + +# These two should. +test -r dir2/dir1/file || fail=1 +test -r dir1/file || fail=1 + +Exit $fail diff --git a/tests/cp/dir-vs-file.sh b/tests/cp/dir-vs-file.sh new file mode 100755 index 0000000..7f33501 --- /dev/null +++ b/tests/cp/dir-vs-file.sh @@ -0,0 +1,33 @@ +#!/bin/sh +# A directory may not replace an existing file. + +# Copyright (C) 2001-2022 Free Software Foundation, Inc. + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src +print_ver_ cp + +mkdir dir || framework_failure_ +touch file || framework_failure_ + + +# In 4.0.35, this cp invocation silently succeeded. +returns_ 1 cp -R dir file 2>/dev/null || fail=1 + +# Make sure file is not replaced with a directory. +# In 4.0.35, it was. +test -f file || fail=1 + +Exit $fail diff --git a/tests/cp/existing-perm-dir.sh b/tests/cp/existing-perm-dir.sh new file mode 100755 index 0000000..46d73a7 --- /dev/null +++ b/tests/cp/existing-perm-dir.sh @@ -0,0 +1,31 @@ +#!/bin/sh +# Make sure cp -p doesn't "restore" permissions it shouldn't (Bug#9170). + +# Copyright 2011-2022 Free Software Foundation, Inc. + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src +print_ver_ cp + +umask 002 +mkdir -p -m ug-s,u=rwx,g=rwx,o=rx src/dir || fail=1 +mkdir -p -m ug-s,u=rwx,g=,o= dst/dir || fail=1 + +cp -r src/. dst/ || fail=1 + +mode=$(stat --p=%A dst/dir) +test "$mode" = drwx------ || fail=1 + +Exit $fail diff --git a/tests/cp/existing-perm-race.sh b/tests/cp/existing-perm-race.sh new file mode 100755 index 0000000..9c35426 --- /dev/null +++ b/tests/cp/existing-perm-race.sh @@ -0,0 +1,94 @@ +#!/bin/sh +# Make sure cp -p isn't too generous with existing file permissions. + +# Copyright (C) 2006-2022 Free Software Foundation, Inc. + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src +print_ver_ cp + +require_membership_in_two_groups_ + +# cp -p gives ENOTSUP on NFS on Linux 2.6.9 at least +require_local_dir_ + +require_no_default_acl_ . + +set _ $groups; shift +g1=$1 +g2=$2 + + +umask 077 +mkfifo_or_skip_ fifo + +touch fifo-copy && +chgrp $g1 fifo && +chgrp $g2 fifo-copy && +chmod g+r fifo-copy || framework-failure + +# Terminate any background cp process +cleanup_() { kill $pid 2>/dev/null && wait $pid; } + +# Copy a fifo's contents. That way, we can examine the +# destination permissions before they're finalized. +cp -p --copy-contents fifo fifo-copy & pid=$! + +( + # Now 'cp' is reading the fifo. Wait for the destination file to + # be written to, encouraging things along by echoing to the fifo. + while test ! -s fifo-copy; do + echo foo + done + + # Check the permissions of the destination. + ls -l -n fifo-copy >ls.out && + + # Close the fifo so that "cp" can continue. But output first, + # before exiting, otherwise some shells would optimize away the file + # descriptor that holds the fifo open. + echo foo +) >fifo || fail=1 + +# Check that the destination mode is safe while the file is being copied. +read mode links owner group etc <ls.out || fail=1 +case $mode in + -rw-------*) ;; + + # FIXME: Remove the following case; the file mode should always be + # 600 while the data are being copied. This will require changing + # cp so that it also does not put $g1's data in a file that is + # accessible to $g2. This fix will not close a security hole, since + # a $g2 process can maintain an open file descriptor to the + # destination, but it's safer anyway. + -rw-r-----*) + # If the file has group $g1 and is group-readable, that is definitely bogus, + # as neither the source nor the destination was readable to group $g1. + test "$group" = "$g1" && fail=1;; + + *) fail=1;; +esac + +wait $pid || fail=1 + +# Check that the final mode and group are right. +ls -l -n fifo-copy >ls.out && +read mode links owner group etc <ls.out || fail=1 +case $mode in + -rw-------*) test "$group" = "$g1" || fail=1;; + *) fail=1;; +esac + +Exit $fail diff --git a/tests/cp/fail-perm.sh b/tests/cp/fail-perm.sh new file mode 100755 index 0000000..b41c5ef --- /dev/null +++ b/tests/cp/fail-perm.sh @@ -0,0 +1,66 @@ +#!/bin/sh + +# Copyright (C) 2000-2022 Free Software Foundation, Inc. + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src +print_ver_ cp +skip_if_root_ + +chmod g-s . || framework_failure_ +mkdir D D/D || framework_failure_ +touch D/a || framework_failure_ +chmod 0 D/a || framework_failure_ +chmod u=rx,go=,-st D || framework_failure_ + + +# This is expected to exit non-zero, because it can't read D/a. +returns_ 1 cp -pR D DD > /dev/null 2>&1 || fail=1 + +# Permissions on DD must be 'dr-x------' + +mode=$(ls -ld DD|cut -b-10) +test "$mode" = dr-x------ || fail=1 + +chmod 0 D +ln -s D/D symlink +touch F +cat > exp <<\EOF +cp: cannot stat 'symlink': Permission denied +EOF + +cp F symlink 2> out && fail=1 +# HPUX appears to fail with EACCES rather than EPERM. +# Transform their diagnostic +# ...: The file access permissions do not allow the specified action. +# to the expected one: +sed 's/: The file access permissions.*/: Permission denied/'<out>o1;mv o1 out +compare exp out || fail=1 + +cp --no-target-directory F symlink 2> out && fail=1 +sed 's/: The file access permissions.*/: Permission denied/'<out>o1;mv o1 out +compare exp out || fail=1 + +cat > exp <<\EOF +cp: target directory 'symlink': Permission denied +EOF + +cp --target-directory=symlink F 2> out && fail=1 +sed 's/: The file access permissions.*/: Permission denied/'<out>o1;mv o1 out +compare exp out || fail=1 + +chmod 700 D + +Exit $fail diff --git a/tests/cp/file-perm-race.sh b/tests/cp/file-perm-race.sh new file mode 100755 index 0000000..14fa27e --- /dev/null +++ b/tests/cp/file-perm-race.sh @@ -0,0 +1,58 @@ +#!/bin/sh +# Make sure cp -p isn't too generous with file permissions. + +# Copyright (C) 2006-2022 Free Software Foundation, Inc. + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src +print_ver_ cp + +# cp -p gives ENOTSUP on NFS on Linux 2.6.9 at least +require_local_dir_ + +umask 022 +mkfifo_or_skip_ fifo + +# Terminate any background cp process +cleanup_() { kill $pid 2>/dev/null && wait $pid; } + +# Copy a fifo's contents. That way, we can examine the +# destination permissions before they're finalized. +cp -p --copy-contents fifo fifo-copy & pid=$! + +( + # Now 'cp' is reading the fifo. Wait for the destination file to + # be created, encouraging things along by echoing to the fifo. + while test ! -f fifo-copy; do + echo foo + done + + # Check the permissions of the destination. + ls -l fifo-copy >ls.out + + # Close the fifo so that "cp" can continue. But output first, + # before exiting, otherwise some shells would optimize away the file + # descriptor that holds the fifo open. + echo foo +) >fifo + +case $(cat ls.out) in +-???------*) ;; +*) fail=1;; +esac + +wait $pid || fail=1 + +Exit $fail diff --git a/tests/cp/into-self.sh b/tests/cp/into-self.sh new file mode 100755 index 0000000..cfd3640 --- /dev/null +++ b/tests/cp/into-self.sh @@ -0,0 +1,57 @@ +#!/bin/sh +# Confirm that copying a directory into itself gets a proper diagnostic. + +# Copyright (C) 2001-2022 Free Software Foundation, Inc. + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +# In 4.0.35 and earlier, 'mkdir dir && cp -R dir dir' would produce this: +# cp: won't create hard link 'dir/dir/dir' to directory '' +# Now it gives this: +# cp: can't copy a directory 'dir' into itself 'dir/dir' + +. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src +print_ver_ cp + +mkdir a dir || framework_failure_ + + +# This command should exit nonzero. +cp -R dir dir 2> out && fail=1 +echo 1 >> out + +# This should, too. However, with coreutils-7.1 it would infloop. +cp -rl dir dir 2>> out && fail=1 +echo 2 >> out + +cp -rl a dir dir 2>> out && fail=1 +echo 3 >> out +cp -rl a dir dir 2>> out && fail=1 +echo 4 >> out + +cat > exp <<\EOF +cp: cannot copy a directory, 'dir', into itself, 'dir/dir' +1 +cp: cannot copy a directory, 'dir', into itself, 'dir/dir' +2 +cp: cannot copy a directory, 'dir', into itself, 'dir/dir' +3 +cp: cannot copy a directory, 'dir', into itself, 'dir/dir' +4 +EOF +#' + +compare exp out || fail=1 + +Exit $fail diff --git a/tests/cp/link-deref.sh b/tests/cp/link-deref.sh new file mode 100755 index 0000000..e0eded5 --- /dev/null +++ b/tests/cp/link-deref.sh @@ -0,0 +1,126 @@ +#!/bin/sh +# Exercise cp --link's behavior regarding the dereferencing of symbolic links. + +# Copyright (C) 2013-2022 Free Software Foundation, Inc. + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src +print_ver_ cp + +if { grep '^#define HAVE_LINKAT 1' "$CONFIG_HEADER" > /dev/null \ + && grep '#undef LINKAT_SYMLINK_NOTSUP' "$CONFIG_HEADER" > /dev/null; } \ + || grep '^#define LINK_FOLLOWS_SYMLINKS 0' "$CONFIG_HEADER" > /dev/null; then + # With this config cp will attempt to linkat() to hardlink a symlink. + # So now double check the current file system supports this operation. + ln -s testtarget test_sl || framework_failure_ + ln -P test_sl test_hl_sl || framework_failure_ + ino_sl="$(stat -c '%i' test_sl)" || framework_failure_ + ino_hl="$(stat -c '%i' test_hl_sl)" || framework_failure_ + test "$ino_sl" = "$ino_hl" && can_hardlink_to_symlink=1 +fi + +mkdir dir || framework_failure_ +> file || framework_failure_ +ln -s dir dirlink || framework_failure_ +ln -s file filelink || framework_failure_ +ln -s nowhere danglink || framework_failure_ + +# printf format of the output line. +outformat='%s|result=%s|inode=%s|type=%s|error=%s\n' + +for src in dirlink filelink danglink; do + # Get symlink's target. + tgt=$(readlink $src) || framework_failure_ + # Get inodes and file type of the symlink (src) and its target (tgt). + # Note: this will fail for 'danglink'; catch it. + ino_src="$(stat -c '%i' $src)" || framework_failure_ + typ_src="$(stat -c '%F' $src)" || framework_failure_ + ino_tgt="$(stat -c '%i' $tgt 2>/dev/null)" || ino_tgt= + typ_tgt="$(stat -c '%F' $tgt 2>/dev/null)" || typ_tgt= + + for o in '' -L -H -P; do + + # Skip the -P case where we don't or can't hardlink symlinks + ! test "$can_hardlink_to_symlink" && test "$o" = '-P' && continue + + for r in '' -R; do + + command="cp --link $o $r $src dst" + $command 2> err + result=$? + + # Get inode and file type of the destination (which may fail, too). + ino_dst="$(stat -c '%i' dst 2>/dev/null)" || ini_dst= + typ_dst="$(stat -c '%F' dst 2>/dev/null)" || typ_dst= + + # Print the actual result in a certain format. + printf "$outformat" \ + "$command" \ + "$result" \ + "$ino_dst" \ + "$typ_dst" \ + "$(cat err)" \ + > out + + # What was expected? + if [ "$o" = "-P" ]; then + # cp --link should not dereference if -P is given. + exp_result=0 + exp_inode=$ino_src + exp_ftype=$typ_src + exp_error= + elif [ "$src" = 'danglink' ]; then + # Dereferencing should fail for the 'danglink'. + exp_result=1 + exp_inode= + exp_ftype= + exp_error="cp: cannot stat 'danglink': No such file or directory" + elif [ "$src" = 'dirlink' ] && [ "$r" != '-R' ]; then + # Dereferencing should fail for the 'dirlink' without -R. + exp_result=1 + exp_inode= + exp_ftype= + exp_error="cp: -r not specified; omitting directory 'dirlink'" + elif [ "$src" = 'dirlink' ]; then + # cp --link -R 'dirlink' should create a new directory. + exp_result=0 + exp_inode=$ino_dst + exp_ftype=$typ_dst + exp_error= + else + # cp --link 'filelink' should create a hard link to the target. + exp_result=0 + exp_inode=$ino_tgt + exp_ftype=$typ_tgt + exp_error= + fi + + # Print the expected result in a certain format. + printf "$outformat" \ + "$command" \ + "$exp_result" \ + "$exp_inode" \ + "$exp_ftype" \ + "$exp_error" \ + > exp + + compare exp out || { ls -lid $src $tgt dst; fail=1; } + + rm -rf dst err exp out || framework_failure_ + done + done +done + +Exit $fail diff --git a/tests/cp/link-heap.sh b/tests/cp/link-heap.sh new file mode 100755 index 0000000..52aaa85 --- /dev/null +++ b/tests/cp/link-heap.sh @@ -0,0 +1,42 @@ +#!/bin/sh +# ensure that cp --preserve=link --link doesn't waste heap + +# Copyright (C) 2008-2022 Free Software Foundation, Inc. + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src +print_ver_ cp +expensive_ + +# Determine basic amount of memory needed for 'cp -al'. +touch f || framework_failure_ +vm=$(get_min_ulimit_v_ cp -al f f2) \ + || skip_ "this shell lacks ulimit support" +rm f f2 || framework_failure_ + +a=$(printf %031d 0) +b=$(printf %031d 1) +(mkdir $a \ + && cd $a \ + && seq --format=%031g 10000 |xargs touch \ + && seq --format=d%030g 10000 |xargs mkdir ) || framework_failure_ +cp -al $a $b || framework_failure_ +mkdir e || framework_failure_ +mv $a $b e || framework_failure_ + +# Allow cp(1) to use 4MiB more virtual memory than for the above trivial case. +(ulimit -v $(($vm+4000)) && cp -al e f) || fail=1 + +Exit $fail diff --git a/tests/cp/link-no-deref.sh b/tests/cp/link-no-deref.sh new file mode 100755 index 0000000..97d52fb --- /dev/null +++ b/tests/cp/link-no-deref.sh @@ -0,0 +1,29 @@ +#!/bin/sh +# Ensure that cp --link --no-dereference works properly + +# Copyright (C) 2006-2022 Free Software Foundation, Inc. + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src +print_ver_ cp + +ln -s no-such-file dangling-slink || framework_failure_ + + +# Prior to coreutils-6.0, this would fail on non-Linux kernels, +# with link being applied to the dangling symlink. +cp --link --no-dereference dangling-slink d2 || fail=1 + +Exit $fail diff --git a/tests/cp/link-preserve.sh b/tests/cp/link-preserve.sh new file mode 100755 index 0000000..4747432 --- /dev/null +++ b/tests/cp/link-preserve.sh @@ -0,0 +1,91 @@ +#!/bin/sh +# ensure that 'cp -d' preserves hard-links between command line arguments +# ensure that --preserve=links works with -RH and -RL + +# Copyright (C) 2001-2022 Free Software Foundation, Inc. + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src +print_ver_ cp + +touch a || framework_failure_ +ln a b || framework_failure_ +mkdir c || framework_failure_ +cp -d a b c || framework_failure_ +test -f c/a || framework_failure_ +test -f c/b || framework_failure_ + + +a_inode=$(ls -i c/a|sed 's,c/.*,,') +b_inode=$(ls -i c/b|sed 's,c/.*,,') +test "$a_inode" = "$b_inode" || fail=1 +# -------------------------------------- + +rm -rf a b c +touch a +ln -s a b +mkdir c +cp --preserve=links -R -H a b c || fail=1 +a_inode=$(ls -i c/a|sed 's,c/.*,,') +b_inode=$(ls -i c/b|sed 's,c/.*,,') +test "$a_inode" = "$b_inode" || fail=1 +# -------------------------------------- + +# Ensure that -L makes cp follow the b->a symlink +# and translates to hard-linked a and b in the destination dir. +rm -rf a b c d; mkdir d; (cd d; touch a; ln -s a b) +cp --preserve=links -R -L d c || fail=1 +a_inode=$(ls -i c/a|sed 's,c/.*,,') +b_inode=$(ls -i c/b|sed 's,c/.*,,') +test "$a_inode" = "$b_inode" || fail=1 +# -------------------------------------- + +# Same as above, but starting with a/b hard linked. +rm -rf a b c d; mkdir d; (cd d; touch a; ln a b) +cp --preserve=links -R -L d c || fail=1 +a_inode=$(ls -i c/a|sed 's,c/.*,,') +b_inode=$(ls -i c/b|sed 's,c/.*,,') +test "$a_inode" = "$b_inode" || fail=1 +# -------------------------------------- + +# Ensure that --no-preserve=links works. +rm -rf a b c d; mkdir d; (cd d; touch a; ln a b) +cp -dR --no-preserve=links d c || fail=1 +a_inode=$(ls -i c/a|sed 's,c/.*,,') +b_inode=$(ls -i c/b|sed 's,c/.*,,') +test "$a_inode" = "$b_inode" && fail=1 +# -------------------------------------- + +# Ensure that -d still preserves hard links. +rm -rf a b c d +touch a; ln a b +mkdir c +cp -d a b c || fail=1 +a_inode=$(ls -i c/a|sed 's,c/.*,,') +b_inode=$(ls -i c/b|sed 's,c/.*,,') +test "$a_inode" = "$b_inode" || fail=1 +# -------------------------------------- + +# Ensure that --no-preserve=mode works +rm -rf a b c d +touch a; chmod 731 a +umask 077 +cp -a --no-preserve=mode a b || fail=1 +mode=$(ls -l b|cut -b-10) +test "$mode" = "-rw-------" || fail=1 +umask 022 +# -------------------------------------- + +Exit $fail diff --git a/tests/cp/link-symlink.sh b/tests/cp/link-symlink.sh new file mode 100755 index 0000000..63d166d --- /dev/null +++ b/tests/cp/link-symlink.sh @@ -0,0 +1,41 @@ +#!/bin/sh +# Ensure that cp -a --link maintains timestamps if possible + +# Copyright (C) 2011-2022 Free Software Foundation, Inc. + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src +print_ver_ cp + +# Check that the timestamps of the symlink are copied +# if we're using hardlink to symlink emulation. +touch file +ln -s file link || framework_failure_ +touch -m -h -d 2011-01-01 link || + skip_ "Your system doesn't support updating symlink timestamps" +case $(stat --format=%y link) in + 2011-01-01*) ;; + *) skip_ "Your system doesn't support updating symlink timestamps" ;; +esac + +# link.cp is probably a hardlink, but may also be a symlink +# In either case the timestamp should match the original. +cp -al link link.cp || fail=1 +case $(stat --format=%y link.cp) in + 2011-01-01*) ;; + *) fail=1 ;; +esac + +Exit $fail diff --git a/tests/cp/link.sh b/tests/cp/link.sh new file mode 100755 index 0000000..2a1cead --- /dev/null +++ b/tests/cp/link.sh @@ -0,0 +1,31 @@ +#!/bin/sh +# Make sure cp --link -f works when the target exists. +# This failed for 4.0z (due to a bug introduced in that test release). + +# Copyright (C) 2000-2022 Free Software Foundation, Inc. + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src +print_ver_ cp + +touch src || framework_failure_ +touch dest || framework_failure_ +touch dest2 || framework_failure_ + + +cp -f --link src dest || fail=1 +cp -f --symbolic-link src dest2 || fail=1 + +Exit $fail diff --git a/tests/cp/nfs-removal-race.sh b/tests/cp/nfs-removal-race.sh new file mode 100755 index 0000000..7603812 --- /dev/null +++ b/tests/cp/nfs-removal-race.sh @@ -0,0 +1,75 @@ +#!/bin/sh +# Running cp S D on an NFS client while another client has just removed D +# would lead (w/coreutils-8.16 and earlier) to cp's initial stat call +# seeing (via stale NFS cache) that D exists, so that cp would then call +# open without the O_CREAT flag. Yet, the open must actually consult +# the server, which confesses that D has been deleted, thus causing the +# open call to fail with ENOENT. +# +# This test simulates that situation by intercepting stat for a nonexistent +# destination, D, and making the stat fill in the result struct for another +# file and return 0. +# +# This test is skipped on systems that lack LD_PRELOAD support; that's fine. +# Similarly, on a system that lacks <dlfcn.h> or __xstat, skipping it is fine. + +# Copyright (C) 2012-2022 Free Software Foundation, Inc. + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src +print_ver_ cp +require_gcc_shared_ + +# Replace each stat call with a call to this wrapper. +cat > k.c <<'EOF' || framework_failure_ +#define _GNU_SOURCE +#include <stdio.h> +#include <sys/types.h> +#include <dlfcn.h> + +#define __xstat __xstat_orig + +#include <sys/stat.h> +#include <stddef.h> + +#undef __xstat + +int +__xstat (int ver, const char *path, struct stat *st) +{ + static int (*real_stat)(int ver, const char *path, struct stat *st) = NULL; + fclose(fopen("preloaded", "w")); + if (!real_stat) + real_stat = dlsym (RTLD_NEXT, "__xstat"); + /* When asked to stat nonexistent "d", + return results suggesting it exists. */ + return real_stat (ver, *path == 'd' && path[1] == 0 ? "d2" : path, st); +} +EOF + +# Then compile/link it: +gcc_shared_ k.c k.so \ + || framework_failure_ 'failed to build shared library' + +touch d2 || framework_failure_ +echo xyz > src || framework_failure_ + +# Finally, run the test: +LD_PRELOAD=$LD_PRELOAD:./k.so cp src d || fail=1 + +test -f preloaded || skip_ 'LD_PRELOAD was ineffective?' + +compare src d || fail=1 +Exit $fail diff --git a/tests/cp/no-ctx.sh b/tests/cp/no-ctx.sh new file mode 100755 index 0000000..78367a7 --- /dev/null +++ b/tests/cp/no-ctx.sh @@ -0,0 +1,65 @@ +#!/bin/sh +# Ensure we handle file systems returning no SELinux context, +# which triggered a segmentation fault in coreutils-8.22. +# This test is skipped on systems that lack LD_PRELOAD support; that's fine. +# Similarly, on a system that lacks lgetfilecon altogether, skipping it is fine. + +# Copyright (C) 2014-2022 Free Software Foundation, Inc. + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src +print_ver_ cp +require_gcc_shared_ +require_selinux_ + +# Replace each getfilecon and lgetfilecon call with a call to these stubs. +cat > k.c <<'EOF' || framework_failure_ +#include <stdio.h> +#include <selinux/selinux.h> +#include <errno.h> + +int getfilecon (const char *path, char **con) +{ + /* Leave a marker so we can identify if the function was intercepted. */ + fclose(fopen("preloaded", "w")); + + errno=ENODATA; + return -1; +} + +int lgetfilecon (const char *path, char **con) +{ return getfilecon (path, con); } +EOF + +# Then compile/link it: +gcc_shared_ k.c k.so \ + || skip_ 'failed to build SELinux shared library' + +touch file_src + +# New file with SELinux context optionally included +LD_PRELOAD=$LD_PRELOAD:./k.so cp -a file_src file_dst || fail=1 + +# Existing file with SELinux context optionally included +LD_PRELOAD=$LD_PRELOAD:./k.so cp -a file_src file_dst || fail=1 + +# ENODATA should give an immediate error when required to preserve ctx +# This is debatable, and maybe we should not fail when no context available? +( export LD_PRELOAD=$LD_PRELOAD:./k.so + returns_ 1 cp --preserve=context file_src file_dst ) || fail=1 + +test -e preloaded || skip_ 'LD_PRELOAD interception failed' + +Exit $fail diff --git a/tests/cp/no-deref-link1.sh b/tests/cp/no-deref-link1.sh new file mode 100755 index 0000000..678179f --- /dev/null +++ b/tests/cp/no-deref-link1.sh @@ -0,0 +1,37 @@ +#!/bin/sh +# cp from 3.16 fails this test + +# Copyright (C) 1997-2022 Free Software Foundation, Inc. + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src +print_ver_ cp + +mkdir a b +msg=bar +echo $msg > a/foo +cd b +ln -s ../a/foo . +cd .. + + +# It should fail with a message something like this: +# ./cp: 'a/foo' and 'b/foo' are the same file +# Fail this test if the exit status is not 1 +returns_ 1 cp -d a/foo b 2>/dev/null || fail=1 + +test "$(cat a/foo)" = $msg || fail=1 + +Exit $fail diff --git a/tests/cp/no-deref-link2.sh b/tests/cp/no-deref-link2.sh new file mode 100755 index 0000000..33e4268 --- /dev/null +++ b/tests/cp/no-deref-link2.sh @@ -0,0 +1,37 @@ +#!/bin/sh +# cp from 3.16 fails this test + +# Copyright (C) 1997-2022 Free Software Foundation, Inc. + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src +print_ver_ cp + +mkdir b +msg=bar +echo $msg > a +cd b +ln -s ../a . +cd .. + + +# It should fail with a message something like this: +# cp: 'a' and 'b/foo' are the same file +# Fail this test if the exit status is not 1 +returns_ 1 cp -d a b 2>/dev/null || fail=1 + +test "$(cat a)" = $msg || fail=1 + +Exit $fail diff --git a/tests/cp/no-deref-link3.sh b/tests/cp/no-deref-link3.sh new file mode 100755 index 0000000..c156214 --- /dev/null +++ b/tests/cp/no-deref-link3.sh @@ -0,0 +1,34 @@ +#!/bin/sh +# cp from 3.16 fails this test + +# Copyright (C) 1997-2022 Free Software Foundation, Inc. + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src +print_ver_ cp + +msg=bar +echo $msg > a +ln -s a b + + +# It should fail with a message something like this: +# cp: 'a' and 'b' are the same file +# Fail this test if the exit status is not 1 +returns_ 1 cp -d a b 2>/dev/null || fail=1 + +test "$(cat a)" = $msg || fail=1 + +Exit $fail diff --git a/tests/cp/parent-perm-race.sh b/tests/cp/parent-perm-race.sh new file mode 100755 index 0000000..5c142d1 --- /dev/null +++ b/tests/cp/parent-perm-race.sh @@ -0,0 +1,59 @@ +#!/bin/sh +# Make sure cp -pR --parents isn't too generous with parent permissions. + +# Copyright (C) 2006-2022 Free Software Foundation, Inc. + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src +print_ver_ cp + +# cp -p gives ENOTSUP on NFS on Linux 2.6.9 at least +require_local_dir_ + +umask 002 +mkdir mode ownership d || framework_failure_ +chmod g+s d 2>/dev/null # The cp test is valid either way. + +# Terminate any background cp process. +pid= +cleanup_() { kill $pid 2>/dev/null && wait $pid; } + +for attr in mode ownership +do + mkfifo_or_skip_ $attr/fifo + + # Copy a fifo's contents. That way, we can examine d/$attr's + # state while cp is running. + timeout 10 cp --preserve=$attr -R --copy-contents --parents $attr d & pid=$! + + # Check the permissions of the destination directory that 'cp' has made. + # 'ls' won't start until after 'cp' has made the destination directory + # $d/attr and has started to read the source file $attr/fifo. + timeout 10 sh -c "ls -ld d/$attr >$attr/fifo" || fail=1 + + wait $pid || fail=1 + + ls_output=$(cat d/$attr/fifo) || fail=1 + case $attr,$ls_output in + ownership,d???--[-S]--[-S]* | \ + mode,d????-??-?* | \ + mode,d??[-x]?w[-x]?-[-x]* ) + ;; + *) + fail=1;; + esac +done + +Exit $fail diff --git a/tests/cp/parent-perm.sh b/tests/cp/parent-perm.sh new file mode 100755 index 0000000..6cc79b8 --- /dev/null +++ b/tests/cp/parent-perm.sh @@ -0,0 +1,53 @@ +#!/bin/sh +# Ensure that cp --parents works properly with a preexisting dest. directory + +# Copyright (C) 2008-2022 Free Software Foundation, Inc. + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src +print_ver_ cp + +working_umask_or_skip_ +# cp -p gives ENOTSUP on NFS on Linux 2.6.9 at least +require_local_dir_ + +mkdir -p a/b/c a/b/d e || framework_failure_ +touch a/b/c/foo a/b/d/foo || framework_failure_ +cp -p --parent a/b/c/foo e || framework_failure_ + +# Make permissions of e/a different, so that we exercise the +# code in cp -p --parents that propagates permissions even +# to a destination directory that it doesn't create. +chmod g-rx e/a e/a/b || framework_failure_ + +cp -p --parent a/b/d/foo e || fail=1 + +# Ensure that permissions on just-created directory, e/a/, +# are the same as those on original, a/. + +# The sed filter maps any 's' from an inherited set-GID bit +# to the usual 'x'. Otherwise, under unusual circumstances, this +# test would fail with e.g., drwxr-sr-x != drwxr-xr-x . +# For reference, the unusual circumstances is: build dir is set-gid, +# so "a/" inherits that. However, when the user does not belong to +# the group of the build directory, chmod ("a/e", 02755) returns 0, +# yet fails to set the S_ISGID bit. +for dir in a a/b a/b/d; do + test $(stat --printf %A $dir|sed s/s/x/g) \ + = $(stat --printf %A e/$dir|sed s/s/x/g) || + fail=1 +done + +Exit $fail diff --git a/tests/cp/perm.sh b/tests/cp/perm.sh new file mode 100755 index 0000000..813b017 --- /dev/null +++ b/tests/cp/perm.sh @@ -0,0 +1,77 @@ +#!/bin/sh +# Make sure the permission-preserving code in copy.c (mv, cp, install) works. + +# Copyright (C) 2000-2022 Free Software Foundation, Inc. + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src +print_ver_ cp mv + +very_expensive_ + +umask 037 + + +# Now, try it with 'mv', with combinations of --force, no-f and +# existing-destination and not. +for u in 31 37 2; do + echo umask: $u + umask $u + for cmd in mv 'cp -p' cp; do + for force in '' -f; do + for existing_dest in yes no; do + for g_perm in r w x rw wx xr rwx; do + for o_perm in r w x rw wx xr rwx; do + touch src || exit 1 + chmod u=r,g=rx,o= src || exit 1 + expected_perms=$(stat --format=%A src) + rm -f dest + test $existing_dest = yes && { + touch dest || exit 1 + chmod u=rw,g=$g_perm,o=$o_perm dest || exit 1 + } + $cmd $force src dest || exit 1 + test "$cmd" = mv && test -f src && exit 1 + test "$cmd" = cp && { test -f src || exit 1; } + actual_perms=$(stat --format=%A dest) + + case "$cmd:$force:$existing_dest" in + cp:*:yes) + _g_perm=$(echo rwx|sed 's/[^'$g_perm']/-/g') + _o_perm=$(echo rwx|sed 's/[^'$o_perm']/-/g') + expected_perms=-rw-$_g_perm$_o_perm + ;; + cp:*:no) + test $u = 37 && + expected_perms=$( + echo $expected_perms | sed 's/.....$/-----/' + ) + test $u = 31 && + expected_perms=$( + echo $expected_perms | sed 's/..\(..\).$/--\1-/' + ) + ;; + esac + test _$actual_perms = _$expected_perms || exit 1 + # Perform only one iteration when there's no existing destination. + test $existing_dest = no && break 3 + done + done + done + done + done +done + +Exit $fail diff --git a/tests/cp/preserve-2.sh b/tests/cp/preserve-2.sh new file mode 100755 index 0000000..1f0fbbc --- /dev/null +++ b/tests/cp/preserve-2.sh @@ -0,0 +1,29 @@ +#!/bin/sh +# ensure that cp's --preserve=X,Y option is parsed properly + +# Copyright (C) 2002-2022 Free Software Foundation, Inc. + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src +print_ver_ cp + +# cp -p gives ENOTSUP on NFS on Linux 2.6.9 at least +require_local_dir_ + +touch f || framework_failure_ + +cp --preserve=mode,links f g || fail=1 + +Exit $fail diff --git a/tests/cp/preserve-gid.sh b/tests/cp/preserve-gid.sh new file mode 100755 index 0000000..8b36eab --- /dev/null +++ b/tests/cp/preserve-gid.sh @@ -0,0 +1,146 @@ +#!/bin/sh +# Verify that cp -p preserves GID when it is possible. + +# Copyright (C) 2007-2022 Free Software Foundation, Inc. + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src +print_ver_ cp + +require_perl_ +require_root_ + +# Some of the tests expect a umask that grants group and/or world read access. +working_umask_or_skip_ + +# Record primary group number, usually 0. +# This is the group ID used when cp (without -p) creates a new file. +primary_group_num=$(id -g) + +create() { + echo "$1" > "$1" || exit 1 + chown "+$2:+$3" "$1" || exit 1 +} + +t0() { + f=$1; shift + u=$1; shift + g=$1; shift + rm -f b || exit 1 + "$@" "$f" b || exit 1 + s=$(stat -c '%u %g' b) + if test "x$s" != "x$u $g"; then + # Allow the actual group to match that of the parent directory + # (it was set to 0 above). + if test "x$s" = "x$u $primary_group_num"; then + : + else + echo "$0: $* $f b: $u $g != $s" 1>&2 + Exit 1 + fi + fi +} + +nameless_uid=$($PERL -le ' + foreach my $i (1000..16*1024-1) + { + getpwuid $i or (print $i), exit + } +') +nameless_gid1=$($PERL -le ' + foreach my $i (1000..16*1024) + { + getgrgid $i or (print $i), exit + } +') +nameless_gid2=$($PERL -le ' + foreach my $i ('"$nameless_gid1"'+1..16*1024) + { + getgrgid $i or (print $i), exit + } +') + +if test -z "$nameless_uid" \ + || test -z "$nameless_gid1" \ + || test -z "$nameless_gid2"; then + skip_ "couldn't find a nameless UID or GID" +fi + +chown "+$nameless_uid:+0" . + +create a0 0 0 +create b0 "$nameless_uid" "$nameless_gid1" +create b1 "$nameless_uid" "$nameless_gid2" +create c0 0 "$nameless_gid1" +create c1 0 "$nameless_gid2" + +t0 a0 0 0 cp +t0 b0 0 0 cp +t0 b1 0 0 cp +t0 c0 0 0 cp +t0 c1 0 0 cp + +t0 a0 0 0 cp -p +t0 b0 "$nameless_uid" "$nameless_gid1" cp -p +t0 b1 "$nameless_uid" "$nameless_gid2" cp -p +t0 c0 0 "$nameless_gid1" cp -p +t0 c1 0 "$nameless_gid2" cp -p + +# For the remaining tests, we need a cp binary that is accessible to a user +# with UID of $nameless_uid. The build directory may not be accessible, +# so create a temporary directory and copy cp into it, ensure that +# $nameless_uid can access it and then make that directory the search path. +tmp_path= +cleanup_() { rm -rf "$tmp_path"; } + +# Cause mktemp to create a directory directly under /tmp. +# Setting TMPDIR explicitly is required here, in case $TMPDIR +# is not readable by our nameless IDs. +test -d /tmp && TMPDIR=/tmp +tmp_path=$(mktemp -d) || fail_ "failed to create temporary directory" +if test -x "$abs_path_dir_/coreutils" && + { test -L "$abs_path_dir_/cp" || + test $(wc -l < "$abs_path_dir_/cp") = 1; } then + # if configured with --enable-single-binary we need to use the single binary + cp "$abs_path_dir_/coreutils" "$tmp_path/cp" || framework_failure_ +else + cp "$abs_path_dir_/cp" "$tmp_path" +fi +chmod -R a+rx "$tmp_path" + +t1() { + f=$1; shift + u=$1; shift + g=$1; shift + t0 "$f" "$u" "$g" \ + chroot --skip-chdir \ + --user=+$nameless_uid:+$nameless_gid1 \ + --groups="+$nameless_gid1,+$nameless_gid2" \ + / env PATH="$tmp_path" "$@" +} + +t1 a0 "$nameless_uid" "$nameless_gid1" cp +t1 b0 "$nameless_uid" "$nameless_gid1" cp +t1 b1 "$nameless_uid" "$nameless_gid1" cp +t1 c0 "$nameless_uid" "$nameless_gid1" cp +t1 c1 "$nameless_uid" "$nameless_gid1" cp + +t1 a0 "$nameless_uid" "$nameless_gid1" cp -p +t1 b0 "$nameless_uid" "$nameless_gid1" cp -p +t1 b1 "$nameless_uid" "$nameless_gid2" cp -p +t1 c0 "$nameless_uid" "$nameless_gid1" cp -p +t1 c1 "$nameless_uid" "$nameless_gid2" cp -p + +Exit $fail diff --git a/tests/cp/preserve-link.sh b/tests/cp/preserve-link.sh new file mode 100755 index 0000000..e4a7e65 --- /dev/null +++ b/tests/cp/preserve-link.sh @@ -0,0 +1,92 @@ +#!/bin/sh +# Exercise the fix for https://bugs.gnu.org/8419 + +# Copyright (C) 2011-2022 Free Software Foundation, Inc. + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src +print_ver_ cp + +same_inode() +{ + local u v + u=$(stat --format %i "$1") && + v=$(stat --format %i "$2") && test "$u" = "$v" +} + +create_source_tree() +{ + rm -Rf s + mkdir s || framework_failure_ + + # a missing link in dest will be created + touch s/f || framework_failure_ + ln s/f s/linkm || framework_failure_ + + # an existing link in dest will be maintained + ln s/f s/linke || framework_failure_ + + # a separate older file in dest will be overwritten + ln s/f s/fileo || framework_failure_ + + # a separate newer file in dest will be overwritten! + ln s/f s/fileu || framework_failure_ +} + +create_target_tree() +{ + f=$1 # which of f or linkm to create in t/ + + rm -Rf t + mkdir -p t/s/ || framework_failure_ + + # a missing link in dest must be created + touch t/s/$f || framework_failure_ + + # an existing link must be maintained + ln t/s/$f t/s/linke || framework_failure_ + + # a separate older file in dest will be overwritten + touch -d '-1 hour' t/s/fileo || framework_failure_ + + # a separate newer file in dest will be overwritten! + touch -d '+1 hour' t/s/fileu || framework_failure_ +} + + +# Note we repeat this, creating either one of +# two hard linked files from source in the dest, so as to +# test both paths in $(cp) for creating the hard links. +# The path taken by cp is dependent on which cp encounters +# first in the source, which is non deterministic currently +# (I'm guessing that results are sorted by inode and +# beauses they're the same here, and due to the sort +# being unstable, either can be processed first). +create_source_tree + +for f in f linkm; do + create_target_tree $f + + # Copy all the hard links across. With cp from coreutils-8.12 + # and prior, it would sometimes mistakenly copy rather than link. + cp -au s t || fail=1 + + same_inode t/s/f t/s/linkm || fail=1 + same_inode t/s/f t/s/linke || fail=1 + same_inode t/s/f t/s/fileo || fail=1 + same_inode t/s/f t/s/fileu || fail=1 +done + +Exit $fail diff --git a/tests/cp/preserve-mode.sh b/tests/cp/preserve-mode.sh new file mode 100755 index 0000000..7527303 --- /dev/null +++ b/tests/cp/preserve-mode.sh @@ -0,0 +1,66 @@ +#!/bin/sh +# Check whether cp generates files with correct modes. + +# Copyright (C) 2002-2022 Free Software Foundation, Inc. + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src +print_ver_ cp + +get_mode() { stat -c%f "$1"; } + +umask 0022 || framework_failure_ + +#regular file test +touch a b || framework_failure_ +chmod 600 b || framework_failure_ +cp --no-preserve=mode b c || fail=1 +test "$(get_mode a)" = "$(get_mode c)" || fail=1 + +#existing destination test +chmod 600 c || framework_failure_ +cp --no-preserve=mode a b || fail=1 +test "$(get_mode b)" = "$(get_mode c)" || fail=1 + +#directory test +mkdir d1 d2 || framework_failure_ +chmod 705 d2 || framework_failure_ +cp --no-preserve=mode -r d2 d3 || fail=1 +test "$(get_mode d1)" = "$(get_mode d3)" || fail=1 + +#contradicting options test +rm -f a b || framework_failure_ +touch a || framework_failure_ +chmod 600 a || framework_failure_ +cp --no-preserve=mode --preserve=all a b || fail=1 +test "$(get_mode a)" = "$(get_mode b)" || fail=1 + +#fifo test +if mkfifo fifo; then + cp -a --no-preserve=mode fifo fifo_copy || fail=1 + #ensure default perms set appropriate for non regular files + #which wasn't done between v8.20 and 8.29 inclusive + test "$(get_mode fifo)" = "$(get_mode fifo_copy)" || fail=1 +fi + +# Test that plain --preserve=ownership does not affect destination mode. +rm -f a b c || framework_failure_ +touch a || framework_failure_ +chmod 660 a || framework_failure_ +cp a b || fail=1 +cp --preserve=ownership a c || fail=1 +test "$(get_mode b)" = "$(get_mode c)" || fail=1 + +Exit $fail diff --git a/tests/cp/preserve-slink-time.sh b/tests/cp/preserve-slink-time.sh new file mode 100755 index 0000000..e0484d0 --- /dev/null +++ b/tests/cp/preserve-slink-time.sh @@ -0,0 +1,51 @@ +#!/bin/sh +# Verify that cp -Pp preserves times even on symlinks. + +# Copyright (C) 2009-2022 Free Software Foundation, Inc. + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src +print_ver_ cp + +grep '^#define HAVE_UTIMENSAT 1' "$CONFIG_HEADER" > /dev/null || +grep '^#define HAVE_LUTIMES 1' "$CONFIG_HEADER" > /dev/null || + skip_ 'this system lacks the utimensat function' + +ln -s no-such dangle || framework_failure_ + +# If the current file system lacks sub-second resolution, sleep for 2s to +# ensure that the times on the copy are different from those of the original. +case $(stat --format=%y dangle) in + ??:??:??.000000000) sleep 2;; +esac + +copy_timestamp_() { + sleep $1 + rm -f d2 + cp -Pp dangle d2 || framework_failure_ + # Can't use --format=%x, as lstat() modifies atime on some platforms. + stat --format=%y dangle > t1 || framework_failure_ + stat --format=%y d2 > t2 || framework_failure_ + compare t1 t2 +} + +# We retry with a delay at least 1.5s because on GPFS +# it was seen that the timestamp wasn't updated unless there +# was sufficient delay between the ln and cp. +# I.e., if there wasn't sufficient difference in +# the timestamp being updated, the update was discarded. +retry_delay_ copy_timestamp_ .1 4 || fail=1 + +Exit $fail diff --git a/tests/cp/proc-short-read.sh b/tests/cp/proc-short-read.sh new file mode 100755 index 0000000..bedb08e --- /dev/null +++ b/tests/cp/proc-short-read.sh @@ -0,0 +1,36 @@ +#!/bin/sh +# exercise cp's short-read failure when operating on >4KB files in /proc + +# Copyright (C) 2009-2022 Free Software Foundation, Inc. + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src +print_ver_ cp + +proc_large=/proc/cpuinfo # usually > 4KiB + +test -r $proc_large || skip_ "your system lacks $proc_large" + +# Before coreutils-7.3, cp would copy less than 4KiB of this file. +cp $proc_large 1 || fail=1 +cat $proc_large > 2 || fail=1 + +# adjust varying parts +sed '/MHz/d; /bogomips/d;' 1 > proc.cp || framework_failure_ +sed '/MHz/d; /bogomips/d;' 2 > proc.cat || framework_failure_ + +compare proc.cp proc.cat || fail=1 + +Exit $fail diff --git a/tests/cp/proc-zero-len.sh b/tests/cp/proc-zero-len.sh new file mode 100755 index 0000000..1c11bd3 --- /dev/null +++ b/tests/cp/proc-zero-len.sh @@ -0,0 +1,47 @@ +#!/bin/sh +# Ensure that cp copies contents of non-empty "regular" file with st_size==0 + +# Copyright (C) 2007-2022 Free Software Foundation, Inc. + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src +print_ver_ cp + +touch empty || framework_failure_ + +f=/proc/cpuinfo +test -r $f || f=empty + +cat $f > out || fail=1 + +# With coreutils-6.9, this would create a zero-length "exp" file. +# Skip this test on architectures like aarch64 where the inode +# number of the file changed during the cp run. +cp $f exp 2>err \ + || { fail=1; + grep 'replaced while being copied' err \ + && skip_ "File $f is being replaced while being copied"; } + +# Don't simply compare contents; they might differ, +# e.g., if CPU freq changes between cat and cp invocations. +# Instead, simply compare whether they're both nonempty. +test -s out \ + && { rm -f out; echo nonempty > out; } +test -s exp \ + && { rm -f exp; echo nonempty > exp; } + +compare exp out || fail=1 + +Exit $fail diff --git a/tests/cp/r-vs-symlink.sh b/tests/cp/r-vs-symlink.sh new file mode 100755 index 0000000..0ad9b75 --- /dev/null +++ b/tests/cp/r-vs-symlink.sh @@ -0,0 +1,41 @@ +#!/bin/sh +# cp -r should not create symlinks. Fixed in fileutils-4.1.5. + +# Copyright (C) 2001-2022 Free Software Foundation, Inc. + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +# Restored old behavior (whereby cp -r preserves symlinks) in 4.1.6, +# though now such usage evokes a warning: +# cp: 'slink': WARNING: using -r to copy symbolic links is not portable + +. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src +print_ver_ cp + +echo abc > foo || framework_failure_ +ln -s foo slink || framework_failure_ +ln -s no-such-file no-file || framework_failure_ + + +# This would fail in 4.1.5, not in 4.1.6. +cp -r no-file junk 2>/dev/null || fail=1 + +cp -r slink bar 2>/dev/null || fail=1 +set x $(ls -l bar); shift; mode=$1 +case $mode in + l*) ;; + *) fail=1;; +esac + +Exit $fail diff --git a/tests/cp/reflink-auto.sh b/tests/cp/reflink-auto.sh new file mode 100755 index 0000000..16b4078 --- /dev/null +++ b/tests/cp/reflink-auto.sh @@ -0,0 +1,45 @@ +#!/bin/sh +# Test cp --reflink=auto + +# Copyright (C) 2009-2022 Free Software Foundation, Inc. + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src +print_ver_ cp + +cleanup_() { rm -rf "$other_partition_tmpdir"; } +. "$abs_srcdir/tests/other-fs-tmpdir" +a_other="$other_partition_tmpdir/a" +rm -f "$a_other" || framework_failure_ + +echo non_zero_size > "$a_other" || framework_failure_ + +# we shouldn't be able to reflink() files on separate partitions +returns_ 1 cp --reflink "$a_other" b || fail=1 + +# --reflink=auto should fall back to a normal copy +cp --reflink=auto "$a_other" b || fail=1 +test -s b || fail=1 + +# --reflink=auto should allow --sparse for fallback copies. +# This command can be used to create minimal sized copies. +cp --reflink=auto --sparse=always "$a_other" b || fail=1 +test -s b || fail=1 + +# --reflink=auto should be overridden by --reflink=never +cp --reflink=auto --reflink=never "$a_other" b || fail=1 +test -s b || fail=1 + +Exit $fail diff --git a/tests/cp/reflink-perm.sh b/tests/cp/reflink-perm.sh new file mode 100755 index 0000000..192fceb --- /dev/null +++ b/tests/cp/reflink-perm.sh @@ -0,0 +1,45 @@ +#!/bin/sh +# Test cp --reflink copies permissions + +# Copyright (C) 2009-2022 Free Software Foundation, Inc. + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src +print_ver_ cp + + +> time_check +> file +ts='2009-08-28 19:00' +touch -d "$ts" file || framework_failure_ +test time_check -nt file || skip_ "The system clock is wrong" + +chmod a=rwx file || framework_failure_ +umask 077 +cp --reflink=auto --preserve file copy || fail=1 + +mode=$(stat --printf "%A" copy) +test "$mode" = "-rwxrwxrwx" || fail=1 + +test copy -nt file && fail=1 + +# Ensure that --attributes-only overrides --reflink completely +echo > file2 # file with data +cp --reflink=auto --preserve --attributes-only file2 empty_copy || fail=1 +compare /dev/null empty_copy || fail=1 +cp --reflink=always --preserve --attributes-only file2 empty_copy || fail=1 +compare /dev/null empty_copy || fail=1 + +Exit $fail diff --git a/tests/cp/same-file.sh b/tests/cp/same-file.sh new file mode 100755 index 0000000..7a61565 --- /dev/null +++ b/tests/cp/same-file.sh @@ -0,0 +1,255 @@ +#!/bin/sh +# Test some of cp's options and how cp handles situations in +# which a naive implementation might overwrite the source file. + +# Copyright (C) 1998-2022 Free Software Foundation, Inc. + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src +print_ver_ cp + +# Unset CDPATH. Otherwise, output from the 'cd dir' command +# can make this test fail. +(unset CDPATH) >/dev/null 2>&1 && unset CDPATH + +VERSION_CONTROL=numbered; export VERSION_CONTROL + +# Determine whether a hard link to a symlink points to the symlink +# itself or to its referent. For example, the link from FreeBSD6.1 +# does dereference a symlink, but the one from Linux does not. +ln -s no-such dangling-slink +ln dangling-slink hard-link > /dev/null 2>&1 \ + && hard_link_to_symlink_does_the_deref=no \ + || hard_link_to_symlink_does_the_deref=yes +rm -f no-such dangling-slink hard-link + +test $hard_link_to_symlink_does_the_deref = yes \ + && remove_these_sed='/^0 -[bf]*l .*sl1 ->/d; /hlsl/d' \ + || remove_these_sed='/^ELIDE NO TEST OUTPUT/d' + +exec 3>&1 1> actual + +# FIXME: This should be bigger: like more than 8k +contents=XYZ + +for args in 'foo symlink' 'symlink foo' 'foo foo' 'sl1 sl2' \ + 'foo hardlink' 'hlsl sl2'; do + for options in '' -d -f -df --rem -b -bd -bf -bdf \ + -l -dl -fl -dfl -bl -bdl -bfl -bdfl -s -sf; do + case $args$options in + # These tests are not portable. + # They all involve making a hard link to a symbolic link. + # In the past, we've skipped the tests that are not portable, + # by doing "continue" here and eliminating the corresponding + # expected output lines below. Don't do that anymore. + 'symlink foo'-dfl) + continue;; + 'symlink foo'-bdl) + continue;; + 'symlink foo'-bdfl) + continue;; + 'sl1 sl2'-dfl) + continue;; + 'sl1 sl2'-bd*l) + continue;; + 'sl1 sl2'-dl) + continue;; + esac + + # cont'd Instead, skip them only on systems for which link does + # dereference a symlink. Detect and skip such tests here. + case $hard_link_to_symlink_does_the_deref:$args:$options in + 'yes:sl1 sl2:-fl') + continue ;; + 'yes:sl1 sl2:-bl') + continue ;; + 'yes:sl1 sl2:-bfl') + continue ;; + yes:hlsl*) + continue ;; + esac + + rm -rf dir + mkdir dir + cd dir + echo $contents > foo + case "$args" in *symlink*) ln -s foo symlink ;; esac + case "$args" in *hardlink*) ln foo hardlink ;; esac + case "$args" in *sl1*) ln -s foo sl1;; esac + case "$args" in *sl2*) ln -s foo sl2;; esac + case "$args" in *hlsl*) ln sl2 hlsl;; esac + ( + ( + # echo 1>&2 cp $options $args + cp $options $args 2>_err + echo $? $options + + # Normalize the program name and diagnostics in the error output, + # and put brackets around the output. + if test -s _err; then + sed ' + s/symbolic link/symlink/ + s/^[^:]*:\([^:]*\).*/cp:\1/ + 1s/^/[/ + $s/$/]/ + ' _err + fi + # Strip off all but the file names. + ls=$(ls -gG --ignore=_err . \ + | sed \ + -e '/^total /d' \ + -e 's/^[^ ]* *[^ ]* *[^ ]* *[^ ]* *[^ ]* *[^ ]* *//') + echo "($ls)" + # Make sure the original is unchanged and that + # the destination is a copy. + for f in $args; do + if test -f $f; then + case "$(cat $f)" in + "$contents") ;; + *) echo cp FAILED;; + esac + else + echo symlink-loop + fi + done + ) | tr '\n' ' ' + echo + ) | sed 's/ *$//' + cd .. + done + echo +done + +cat <<\EOF | sed "$remove_these_sed" > expected +1 [cp: 'foo' and 'symlink' are the same file] (foo symlink -> foo) +1 -d [cp: 'foo' and 'symlink' are the same file] (foo symlink -> foo) +1 -f [cp: 'foo' and 'symlink' are the same file] (foo symlink -> foo) +1 -df [cp: 'foo' and 'symlink' are the same file] (foo symlink -> foo) +0 --rem (foo symlink) +0 -b (foo symlink symlink.~1~ -> foo) +0 -bd (foo symlink symlink.~1~ -> foo) +0 -bf (foo symlink symlink.~1~ -> foo) +0 -bdf (foo symlink symlink.~1~ -> foo) +1 -l [cp: cannot create hard link 'symlink' to 'foo'] (foo symlink -> foo) +1 -dl [cp: cannot create hard link 'symlink' to 'foo'] (foo symlink -> foo) +0 -fl (foo symlink) +0 -dfl (foo symlink) +0 -bl (foo symlink symlink.~1~ -> foo) +0 -bdl (foo symlink symlink.~1~ -> foo) +0 -bfl (foo symlink symlink.~1~ -> foo) +0 -bdfl (foo symlink symlink.~1~ -> foo) +1 -s [cp: cannot create symlink 'symlink' to 'foo'] (foo symlink -> foo) +0 -sf (foo symlink -> foo) + +1 [cp: 'symlink' and 'foo' are the same file] (foo symlink -> foo) +1 -d [cp: 'symlink' and 'foo' are the same file] (foo symlink -> foo) +1 -f [cp: 'symlink' and 'foo' are the same file] (foo symlink -> foo) +1 -df [cp: 'symlink' and 'foo' are the same file] (foo symlink -> foo) +1 --rem [cp: 'symlink' and 'foo' are the same file] (foo symlink -> foo) +1 -b [cp: 'symlink' and 'foo' are the same file] (foo symlink -> foo) +0 -bd (foo -> foo foo.~1~ symlink -> foo) symlink-loop symlink-loop +1 -bf [cp: 'symlink' and 'foo' are the same file] (foo symlink -> foo) +0 -bdf (foo -> foo foo.~1~ symlink -> foo) symlink-loop symlink-loop +0 -l (foo symlink -> foo) +0 -dl (foo symlink -> foo) +0 -fl (foo symlink -> foo) +0 -bl (foo symlink -> foo) +0 -bfl (foo symlink -> foo) +1 -s [cp: 'symlink' and 'foo' are the same file] (foo symlink -> foo) +1 -sf [cp: 'symlink' and 'foo' are the same file] (foo symlink -> foo) + +1 [cp: 'foo' and 'foo' are the same file] (foo) +1 -d [cp: 'foo' and 'foo' are the same file] (foo) +1 -f [cp: 'foo' and 'foo' are the same file] (foo) +1 -df [cp: 'foo' and 'foo' are the same file] (foo) +1 --rem [cp: 'foo' and 'foo' are the same file] (foo) +1 -b [cp: 'foo' and 'foo' are the same file] (foo) +1 -bd [cp: 'foo' and 'foo' are the same file] (foo) +0 -bf (foo foo.~1~) +0 -bdf (foo foo.~1~) +0 -l (foo) +0 -dl (foo) +0 -fl (foo) +0 -dfl (foo) +0 -bl (foo) +0 -bdl (foo) +0 -bfl (foo foo.~1~) +0 -bdfl (foo foo.~1~) +1 -s [cp: 'foo' and 'foo' are the same file] (foo) +1 -sf [cp: 'foo' and 'foo' are the same file] (foo) + +1 [cp: 'sl1' and 'sl2' are the same file] (foo sl1 -> foo sl2 -> foo) +0 -d (foo sl1 -> foo sl2 -> foo) +1 -f [cp: 'sl1' and 'sl2' are the same file] (foo sl1 -> foo sl2 -> foo) +0 -df (foo sl1 -> foo sl2 -> foo) +0 --rem (foo sl1 -> foo sl2) +0 -b (foo sl1 -> foo sl2 sl2.~1~ -> foo) +0 -bd (foo sl1 -> foo sl2 -> foo sl2.~1~ -> foo) +0 -bf (foo sl1 -> foo sl2 sl2.~1~ -> foo) +0 -bdf (foo sl1 -> foo sl2 -> foo sl2.~1~ -> foo) +1 -l [cp: cannot create hard link 'sl2' to 'sl1'] (foo sl1 -> foo sl2 -> foo) +0 -fl (foo sl1 -> foo sl2) +0 -bl (foo sl1 -> foo sl2 sl2.~1~ -> foo) +0 -bfl (foo sl1 -> foo sl2 sl2.~1~ -> foo) +1 -s [cp: cannot create symlink 'sl2' to 'sl1'] (foo sl1 -> foo sl2 -> foo) +0 -sf (foo sl1 -> foo sl2 -> sl1) + +1 [cp: 'foo' and 'hardlink' are the same file] (foo hardlink) +1 -d [cp: 'foo' and 'hardlink' are the same file] (foo hardlink) +1 -f [cp: 'foo' and 'hardlink' are the same file] (foo hardlink) +1 -df [cp: 'foo' and 'hardlink' are the same file] (foo hardlink) +0 --rem (foo hardlink) +0 -b (foo hardlink hardlink.~1~) +0 -bd (foo hardlink hardlink.~1~) +0 -bf (foo hardlink hardlink.~1~) +0 -bdf (foo hardlink hardlink.~1~) +0 -l (foo hardlink) +0 -dl (foo hardlink) +0 -fl (foo hardlink) +0 -dfl (foo hardlink) +0 -bl (foo hardlink) +0 -bdl (foo hardlink) +0 -bfl (foo hardlink) +0 -bdfl (foo hardlink) +1 -s [cp: 'foo' and 'hardlink' are the same file] (foo hardlink) +1 -sf [cp: 'foo' and 'hardlink' are the same file] (foo hardlink) + +1 [cp: 'hlsl' and 'sl2' are the same file] (foo hlsl -> foo sl2 -> foo) +0 -d (foo hlsl -> foo sl2 -> foo) +1 -f [cp: 'hlsl' and 'sl2' are the same file] (foo hlsl -> foo sl2 -> foo) +0 -df (foo hlsl -> foo sl2 -> foo) +0 --rem (foo hlsl -> foo sl2) +0 -b (foo hlsl -> foo sl2 sl2.~1~ -> foo) +0 -bd (foo hlsl -> foo sl2 -> foo sl2.~1~ -> foo) +0 -bf (foo hlsl -> foo sl2 sl2.~1~ -> foo) +0 -bdf (foo hlsl -> foo sl2 -> foo sl2.~1~ -> foo) +1 -l [cp: cannot create hard link 'sl2' to 'hlsl'] (foo hlsl -> foo sl2 -> foo) +0 -dl (foo hlsl -> foo sl2 -> foo) +0 -fl (foo hlsl -> foo sl2) +0 -dfl (foo hlsl -> foo sl2 -> foo) +0 -bl (foo hlsl -> foo sl2 sl2.~1~ -> foo) +0 -bdl (foo hlsl -> foo sl2 -> foo) +0 -bfl (foo hlsl -> foo sl2 sl2.~1~ -> foo) +0 -bdfl (foo hlsl -> foo sl2 -> foo) +1 -s [cp: cannot create symlink 'sl2' to 'hlsl'] (foo hlsl -> foo sl2 -> foo) +0 -sf (foo hlsl -> foo sl2 -> hlsl) + +EOF + +exec 1>&3 3>&- + +compare expected actual 1>&2 || fail=1 + +Exit $fail diff --git a/tests/cp/slink-2-slink.sh b/tests/cp/slink-2-slink.sh new file mode 100755 index 0000000..4abf395 --- /dev/null +++ b/tests/cp/slink-2-slink.sh @@ -0,0 +1,32 @@ +#!/bin/sh +# 'test cp --update A B' where A and B are both symlinks that point +# to the same file + +# Copyright (C) 2000-2022 Free Software Foundation, Inc. + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src +print_ver_ cp + +touch file || framework_failure_ +ln -s file a || framework_failure_ +ln -s file b || framework_failure_ +ln -s no-such-file c || framework_failure_ +ln -s no-such-file d || framework_failure_ + +cp --update --no-dereference a b || fail=1 +cp --update --no-dereference c d || fail=1 + +Exit $fail diff --git a/tests/cp/sparse-2.sh b/tests/cp/sparse-2.sh new file mode 100755 index 0000000..2104071 --- /dev/null +++ b/tests/cp/sparse-2.sh @@ -0,0 +1,61 @@ +#!/bin/sh +# Exercise a few more corners of the copying code. + +# Copyright (C) 2011-2022 Free Software Foundation, Inc. + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src +print_ver_ cp stat dd + +touch sparse_chk +seek_data_capable_ sparse_chk \ + || skip_ "this file system lacks SEEK_DATA support" + +# Exercise the code that handles a file ending in a hole. +printf x > k || framework_failure_ +dd bs=1k seek=128 of=k < /dev/null || framework_failure_ + +# The first time through the outer loop, the input file, K, ends with a hole. +# The second time through, we append a byte so that it does not. +for append in no yes; do + test $append = yes && printf y >> k + for i in always never; do + cp --reflink=never --sparse=$i k k2 || fail=1 + cmp k k2 || fail=1 + done +done + +# Ensure that --sparse=always can restore holes. +rm -f k +# Create a file starting with an "x", followed by 256K-1 0 bytes. +printf x > k || framework_failure_ +dd bs=1k seek=1 of=k count=255 < /dev/zero || framework_failure_ + +# cp should detect the all-zero blocks and convert some of them to holes. +# How many it detects/converts currently depends on io_blksize. +# Currently, on my F14/ext4 desktop, this K file starts off with size 256KiB, +# (note that the K in the preceding test starts off with size 4KiB). +# cp from coreutils-8.9 with --sparse=always reduces the size to 32KiB. +cp --reflink=never --sparse=always k k2 || fail=1 +if test $(stat -c %b k2) -ge $(stat -c %b k); then + # If not sparse, then double check by creating with dd + # as we're not guaranteed that seek will create a hole. + # apfs on darwin 19.2.0 for example was seen to not to create holes < 16MiB. + hole_size=$(stat -c %o k2) || framework_failure_ + dd if=k of=k2.dd bs=$hole_size conv=sparse || framework_failure_ + test $(stat -c %b k2) -eq $(stat -c %b k2.dd) || fail=1 +fi + +Exit $fail diff --git a/tests/cp/sparse-extents-2.sh b/tests/cp/sparse-extents-2.sh new file mode 100755 index 0000000..37a4477 --- /dev/null +++ b/tests/cp/sparse-extents-2.sh @@ -0,0 +1,116 @@ +#!/bin/sh +# Test cp --sparse=always through SEEK_DATA copy + +# Copyright (C) 2010-2022 Free Software Foundation, Inc. + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src +print_ver_ cp +require_perl_ + +# The test was seen to fail on ext3 so exclude that type +# (or any file system where the type can't be determined) +touch sparse_chk +if seek_data_capable_ sparse_chk && ! df -t ext3 . >/dev/null; then + : # Current partition has working extents. Good! +else + skip_ "current file system has insufficient SEEK_DATA support" + + # It's not; we need to create one, hence we need root access. + require_root_ + + cwd=$PWD + cleanup_() { cd /; umount "$cwd/mnt"; } + + skip=0 + # Create an ext4 loopback file system + dd if=/dev/zero of=blob bs=32k count=1000 || skip=1 + mkdir mnt + mkfs -t ext4 -F blob || + skip_ "failed to create ext4 file system" + mount -oloop blob mnt || skip=1 + cd mnt || skip=1 + echo test > f || skip=1 + test -s f || skip=1 + + test $skip = 1 && + skip_ "insufficient mount/ext4 support" +fi + +# ================================================= +# The data below was set up to ensure that the original FIEMAP-copying code +# was exercised enough to provoke at least two iterations of the do...while loop +# in which it calls ioctl (fd, FS_IOC_FIEMAP,... +# This also verifies that non-trivial extents are preserved. + +# Extract logical block number and length pairs from filefrag -v output. +# The initial sed is to remove the "eof" from the normally-empty "flags" field. +# Similarly, remove flags values like "unknown,delalloc,eof". +# That is required when that final extent has no number in the "expected" field. +f() +{ + sed 's/ [a-z,][a-z,]*$//' $@ \ + | $AWK '/^ *[0-9]/ {printf "%d %d ", $2, (NF>=6 ? $6 : (NF<5 ? $NF : $5)) } + END {print ""}' +} + +for i in $(seq 1 2 21); do + for j in 1 2 31 100; do + $PERL -e '$n = '$i' * 1024; *F = *STDOUT;' \ + -e 'for (1..'$j') { sysseek (*F, $n, 1)' \ + -e '&& syswrite (*F, chr($_)x$n) or die "$!"}' > j1 || fail=1 + + # Note there is an implicit sync performed by cp on Linux kernels + # before 2.6.39 to work around bugs in EXT4 and BTRFS. + # (this was removed in the release after coreutils-8.32). + # Note also the -s parameter to the filefrag commands below + # for the same reasons. + cp --reflink=never --sparse=always j1 j2 || fail=1 + + cmp j1 j2 || fail_ "data loss i=$i j=$j" + if ! filefrag -vs j1 | grep -F extent >/dev/null; then + test $skip != 1 && warn_ 'skipping part; you lack filefrag' + skip=1 + else + # Here is sample filefrag output: + # $ perl -e 'BEGIN{$n=16*1024; *F=*STDOUT}' \ + # -e 'for (1..5) { sysseek(*F,$n,1)' \ + # -e '&& syswrite *F,"."x$n or die "$!"}' > j + # $ filefrag -v j + # File system type is: ef53 + # File size of j is 163840 (40 blocks, blocksize 4096) + # ext logical physical expected length flags + # 0 4 6258884 4 + # 1 12 6258892 6258887 4 + # 2 20 6258900 6258895 4 + # 3 28 6258908 6258903 4 + # 4 36 6258916 6258911 4 eof + # j: 6 extents found + + # exclude the physical block numbers; they always differ + filefrag -v j1 > ff1 || framework_failure_ + filefrag -vs j2 > ff2 || framework_failure_ + { f ff1; f ff2; } | $PERL $abs_srcdir/tests/filefrag-extent-compare \ + || { + warn_ ignoring filefrag-reported extent map differences + # Show the differing extent maps. + head -n99 ff1 ff2 + } + fi + test $fail = 1 && break 2 + done +done + +Exit $fail diff --git a/tests/cp/sparse-extents.sh b/tests/cp/sparse-extents.sh new file mode 100755 index 0000000..5216b95 --- /dev/null +++ b/tests/cp/sparse-extents.sh @@ -0,0 +1,83 @@ +#!/bin/sh +# Test cp handles extents correctly + +# Copyright (C) 2011-2022 Free Software Foundation, Inc. + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src +print_ver_ cp + +require_sparse_support_ + +touch sparse_chk || framework_failure_ +seek_data_capable_ sparse_chk || + skip_ 'this file system lacks SEEK_DATA support' + +fallocate --help >/dev/null || skip_ 'The fallocate utility is required' +touch falloc.test || framework_failure_ +fallocate -l 1 -o 1 -n falloc.test || + skip_ 'this file system lacks FALLOCATE support' +rm falloc.test + +# We don't currently handle unwritten extents specially +if false; then +# Require more space than we'll actually use, so that +# tests run in parallel do not run out of space. +# Otherwise, with inadequate space, simply running the following +# fallocate command would induce a temporary file-system-full condition, +# which would cause failure of unrelated tests run in parallel. +require_file_system_bytes_free_ 800000000 + +fallocate -l 1MiB num.test || + skip_ "this fallocate doesn't support numbers with IEX suffixes" + +fallocate -l 600MiB space.test || + skip_ 'this test needs at least 600MiB free space' + +# Disable this test on old BTRFS (e.g. Fedora 14) +# which reports ordinary extents for unwritten ones. +filefrag space.test || skip_ 'the 'filefrag' utility is missing' +filefrag -v space.test | grep -F 'unwritten' > /dev/null || + skip_ 'this file system does not report empty extents as "unwritten"' + +rm space.test + +# Ensure we read a large empty file quickly +fallocate -l 300MiB empty.big || framework_failure_ +timeout 3 cp --reflink=never --sparse=always empty.big cp.test || fail=1 +test $(stat -c %s empty.big) = $(stat -c %s cp.test) || fail=1 +rm empty.big cp.test +fi + +# Ensure we handle extents beyond file size correctly. +# Note until we support fallocate, we will not maintain +# the file allocation. FIXME: amend this test if fallocate is supported. +# Note currently this only uses SEEK_DATA logic when the allocation (-l) +# is smaller than the size, thus identifying the file as sparse. +# Note the '-l 1' case is an effective noop, and just checks +# a file with a trailing hole is copied correctly. +for sparse_arg in always auto never; do + for alloc in '-l 4194304' '-l 1048576 -o 4194304' '-l 1'; do + dd count=10 if=/dev/urandom iflag=fullblock of=unwritten.withdata + truncate -s 2MiB unwritten.withdata || framework_failure_ + fallocate $alloc -n unwritten.withdata || framework_failure_ + cp --reflink=never --sparse=$sparse_arg unwritten.withdata cp.test || fail=1 + test $(stat -c %s unwritten.withdata) = $(stat -c %s cp.test) || fail=1 + cmp unwritten.withdata cp.test || fail=1 + rm unwritten.withdata cp.test || framework_failure_ + done +done + +Exit $fail diff --git a/tests/cp/sparse-perf.sh b/tests/cp/sparse-perf.sh new file mode 100755 index 0000000..d529dd1 --- /dev/null +++ b/tests/cp/sparse-perf.sh @@ -0,0 +1,40 @@ +#!/bin/sh +# ensure that a sparse file is copied efficiently, by default + +# Copyright (C) 2021-2022 Free Software Foundation, Inc. + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src +print_ver_ cp + +# Create a large-but-sparse file. +timeout 10 truncate -s1T f || + skip_ "unable to create a 1 TiB sparse file" + +# Note zfs with zfs_dmu_offset_next_sync=0 (the default) +# will generally skip here, due to needing about 5 seconds +# between the creation of the file and the use of SEEK_DATA, +# for it to determine it's an empty file (return ENXIO). +seek_data_capable_ f || + skip_ "this file system lacks appropriate SEEK_DATA support" + +# Nothing can read that many bytes in so little time. +timeout 10 cp --reflink=never f f2 || fail=1 + +# Ensure that the sparse file copied through SEEK_DATA has the same size +# in bytes as the original. +test "$(stat --printf %s f)" = "$(stat --printf %s f2)" || fail=1 + +Exit $fail diff --git a/tests/cp/sparse-to-pipe.sh b/tests/cp/sparse-to-pipe.sh new file mode 100755 index 0000000..3c3f8f4 --- /dev/null +++ b/tests/cp/sparse-to-pipe.sh @@ -0,0 +1,38 @@ +#!/bin/sh +# copy a sparse file to a pipe, to exercise some seldom-used parts of copy.c + +# Copyright (C) 2011-2022 Free Software Foundation, Inc. + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src +print_ver_ cp + +require_sparse_support_ + +# Terminate any background cp process +cleanup_() { kill $pid 2>/dev/null && wait $pid; } + +mkfifo_or_skip_ pipe +timeout 10 cat pipe > copy & pid=$! + +truncate -s1M sparse || framework_failure_ +cp sparse pipe || fail=1 + +# Ensure that the cat has completed before comparing. +wait $pid + +cmp sparse copy || fail=1 + +Exit $fail diff --git a/tests/cp/sparse.sh b/tests/cp/sparse.sh new file mode 100755 index 0000000..ea5cba4 --- /dev/null +++ b/tests/cp/sparse.sh @@ -0,0 +1,77 @@ +#!/bin/sh +# Test cp --sparse=always + +# Copyright (C) 2006-2022 Free Software Foundation, Inc. + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src +print_ver_ cp +require_sparse_support_ + +# Create a sparse file. +# It has to be at least 128K in order to be sparse on some systems. +# Make its size one larger than 128K, in order to tickle the +# bug in coreutils-6.0. +size=$(expr 128 \* 1024 + 1) +dd bs=1 seek=$size of=sparse < /dev/null 2> /dev/null || framework_failure_ + +# Avoid reflinking. We want to test hole navigation here. +cp_no_reflink() { + cp --reflink=never "$@" +} + +cp_no_reflink --sparse=always sparse copy || fail=1 + +# Ensure that the copy has the same block count as the original. +test $(stat --printf %b copy) -le $(stat --printf %b sparse) || fail=1 + +# Ensure that --sparse={always,never} with --reflink fail. +returns_ 1 cp --sparse=always --reflink sparse copy || fail=1 +returns_ 1 cp --sparse=never --reflink sparse copy || fail=1 + + +# Ensure we handle sparse/non-sparse transitions correctly +maxn=128 # how many $hole_size chunks per file +hole_size=$(stat -c %o copy) +dd if=/dev/zero bs=$hole_size count=$maxn of=zeros || framework_failure_ +tr '\0' 'U' < zeros > nonzero || framework_failure_ + +for pattern in 1 0; do + test "$pattern" = 1 && pattern="$(printf '%s\n%s' nonzero zeros)" + test "$pattern" = 0 && pattern="$(printf '%s\n%s' zeros nonzero)" + + for n in 1 2 4 11 32 $maxn; do + parts=$(expr $maxn / $n) + + rm -f file.in + + # Generate non sparse file for copying with alternating + # hole/data patterns of size n * $hole_size + for i in $(yes "$pattern" | head -n$parts); do + dd iflag=fullblock if=$i of=file.in conv=notrunc oflag=append \ + bs=$hole_size count=$n status=none || framework_failure_ + done + + cp_no_reflink --sparse=always file.in sparse.out || fail=1 # non sparse in + cp_no_reflink --sparse=always sparse.out sparse.out2 || fail=1 # sparse in + + cmp file.in sparse.out || fail=1 + cmp file.in sparse.out2 || fail=1 + + ls -lsh file.in sparse.* + done +done + +Exit $fail diff --git a/tests/cp/special-bits.sh b/tests/cp/special-bits.sh new file mode 100755 index 0000000..1e4e5b2 --- /dev/null +++ b/tests/cp/special-bits.sh @@ -0,0 +1,51 @@ +#!/bin/sh +# make sure 'cp -p' preserves special bits +# This works only when run as root. + +# Copyright (C) 2000-2022 Free Software Foundation, Inc. + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +# This test would fail due to a bug introduced in 4.0y. +# The bug was fixed in 4.0z. + +. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src +print_ver_ cp +require_root_ + +touch a b c || framework_failure_ +chmod u+sx,go= a || framework_failure_ +chmod u=rwx,g=sx,o= b || framework_failure_ +chmod a=r,ug+sx c || framework_failure_ +chown $NON_ROOT_USERNAME . || framework_failure_ +chmod u=rwx,g=rx,o=rx . || framework_failure_ + + +cp -p a a2 || fail=1 +set _ $(ls -l a); shift; p1=$1 +set _ $(ls -l a2); shift; p2=$1 +test $p1 = $p2 || fail=1 + +cp -p b b2 || fail=1 +set _ $(ls -l b); shift; p1=$1 +set _ $(ls -l b2); shift; p2=$1 +test $p1 = $p2 || fail=1 + +chroot --skip-chdir --user=$NON_ROOT_USERNAME / env PATH="$PATH" cp -p c c2 \ + || fail=1 +set _ $(ls -l c); shift; p1=$1 +set _ $(ls -l c2); shift; p2=$1 +test $p1 = $p2 && fail=1 + +Exit $fail diff --git a/tests/cp/special-f.sh b/tests/cp/special-f.sh new file mode 100755 index 0000000..2c7bf5e --- /dev/null +++ b/tests/cp/special-f.sh @@ -0,0 +1,33 @@ +#!/bin/sh +# Ensure that "cp -Rf fifo E" unlinks E and retries. +# Up until coreutils-6.10.171, it would not. + +# Copyright (C) 2008-2022 Free Software Foundation, Inc. + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src +print_ver_ cp + +mkfifo_or_skip_ fifo + +touch e || framework-failure + +for force in '' '-f'; do + # Second time through will need to unlink fifo e + timeout 10 cp -R $force fifo e || fail=1 + test -p fifo || fail=1 +done + +Exit $fail diff --git a/tests/cp/src-base-dot.sh b/tests/cp/src-base-dot.sh new file mode 100755 index 0000000..2e5ac8d --- /dev/null +++ b/tests/cp/src-base-dot.sh @@ -0,0 +1,28 @@ +#!/bin/sh +# Ensure that "mkdir x y; cd y; cp -ab ../x/. ." is a successful, silent, no-op. + +# Copyright (C) 2006-2022 Free Software Foundation, Inc. + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src +print_ver_ cp + +mkdir x y || framework_failure_ + +cd y +cp --verbose -ab ../x/. . > out 2>&1 || fail=1 +compare /dev/null out || fail=1 + +Exit $fail diff --git a/tests/cp/symlink-slash.sh b/tests/cp/symlink-slash.sh new file mode 100755 index 0000000..db1b5fb --- /dev/null +++ b/tests/cp/symlink-slash.sh @@ -0,0 +1,36 @@ +#!/bin/sh +# Make sure that cp -dR dereferences a symlink arg if its name is +# written with a trailing slash. + +# Copyright (C) 2000-2022 Free Software Foundation, Inc. + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src +print_ver_ cp + +mkdir dir || framework_failure_ +ln -s dir symlink || framework_failure_ + +cp -dR symlink/ s || fail=1 +set $(ls -l s) + +# Prior to fileutils-4.0q, the following would have output ...'s -> dir' +# because the trailing slash was removed unconditionally (now you have to +# use the new --strip-trailing-slash option) causing cp to reproduce the +# symlink. Now, the trailing slash is interpreted by the stat library +# call and so cp ends up dereferencing the symlink and copying the directory. +test "$*" = 'total 0' && : || fail=1 + +Exit $fail diff --git a/tests/cp/thru-dangling.sh b/tests/cp/thru-dangling.sh new file mode 100755 index 0000000..44d0b15 --- /dev/null +++ b/tests/cp/thru-dangling.sh @@ -0,0 +1,51 @@ +#!/bin/sh +# Ensure that cp works as documented, when the destination is a dangling symlink + +# Copyright (C) 2007-2022 Free Software Foundation, Inc. + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src +print_ver_ cp + +ln -s no-such dangle || framework_failure_ +echo hi > f || framework_failure_ +echo hi > exp || framework_failure_ +echo "cp: not writing through dangling symlink 'dangle'" \ + > exp-err || framework_failure_ + + +# Starting with 6.9.90, this usage fails, by default: +for opt in '' '-f'; do + returns_ 1 cp $opt f dangle > err 2>&1 || fail=1 + compare exp-err err || fail=1 + test -f no-such && fail=1 +done + + +# But you can set POSIXLY_CORRECT to get the historical behavior. +env POSIXLY_CORRECT=1 cp f dangle > out 2>&1 || fail=1 +cat no-such >> out || fail=1 +compare exp out || fail=1 + + +# Starting with 8.30 we treat ELOOP as existing and so +# remove the symlink +ln -s loop loop || framework_failure_ +cp -f f loop > err 2>&1 || fail=1 +compare /dev/null err || fail=1 +compare exp loop || fail=1 +test -f loop || fail=1 + +Exit $fail |