1# 2# SPDX-License-Identifier: BSD-2-Clause 3# 4# Copyright (c) 2020 Kyle Evans <kevans@FreeBSD.org> 5# 6# Redistribution and use in source and binary forms, with or without 7# modification, are permitted provided that the following conditions 8# are met: 9# 1. Redistributions of source code must retain the above copyright 10# notice, this list of conditions and the following disclaimer. 11# 2. Redistributions in binary form must reproduce the above copyright 12# notice, this list of conditions and the following disclaimer in the 13# documentation and/or other materials provided with the distribution. 14# 15# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 16# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 19# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 21# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 25# SUCH DAMAGE. 26# 27 28check_size() 29{ 30 file=$1 31 sz=$2 32 33 atf_check -o inline:"$sz\n" stat -f '%z' $file 34} 35 36atf_test_case basic 37basic_body() 38{ 39 echo "foo" > bar 40 41 atf_check cp bar baz 42 check_size baz 4 43} 44 45atf_test_case basic_symlink 46basic_symlink_body() 47{ 48 echo "foo" > bar 49 ln -s bar baz 50 51 atf_check cp baz foo 52 atf_check test '!' -L foo 53 54 atf_check cmp foo bar 55} 56 57atf_test_case chrdev 58chrdev_body() 59{ 60 echo "foo" > bar 61 62 check_size bar 4 63 atf_check cp /dev/null trunc 64 check_size trunc 0 65 atf_check cp bar trunc 66 check_size trunc 4 67 atf_check cp /dev/null trunc 68 check_size trunc 0 69} 70 71atf_test_case hardlink 72hardlink_body() 73{ 74 echo "foo" >foo 75 atf_check cp -l foo bar 76 atf_check -o inline:"foo\n" cat bar 77 atf_check_equal "$(stat -f%d,%i foo)" "$(stat -f%d,%i bar)" 78} 79 80atf_test_case hardlink_exists 81hardlink_exists_body() 82{ 83 echo "foo" >foo 84 echo "bar" >bar 85 atf_check -s not-exit:0 -e match:exists cp -l foo bar 86 atf_check -o inline:"bar\n" cat bar 87 atf_check_not_equal "$(stat -f%d,%i foo)" "$(stat -f%d,%i bar)" 88} 89 90atf_test_case hardlink_exists_force 91hardlink_exists_force_body() 92{ 93 echo "foo" >foo 94 echo "bar" >bar 95 atf_check cp -fl foo bar 96 atf_check -o inline:"foo\n" cat bar 97 atf_check_equal "$(stat -f%d,%i foo)" "$(stat -f%d,%i bar)" 98} 99 100atf_test_case matching_srctgt 101matching_srctgt_body() 102{ 103 104 # PR235438: `cp -R foo foo` would previously infinitely recurse and 105 # eventually error out. 106 mkdir foo 107 echo "qux" > foo/bar 108 cp foo/bar foo/zoo 109 110 atf_check cp -R foo foo 111 atf_check -o inline:"qux\n" cat foo/foo/bar 112 atf_check -o inline:"qux\n" cat foo/foo/zoo 113 atf_check -e not-empty -s not-exit:0 stat foo/foo/foo 114} 115 116atf_test_case matching_srctgt_contained 117matching_srctgt_contained_body() 118{ 119 120 # Let's do the same thing, except we'll try to recursively copy foo into 121 # one of its subdirectories. 122 mkdir foo 123 ln -s foo coo 124 echo "qux" > foo/bar 125 mkdir foo/moo 126 touch foo/moo/roo 127 cp foo/bar foo/zoo 128 129 atf_check cp -R foo foo/moo 130 atf_check cp -RH coo foo/moo 131 atf_check -o inline:"qux\n" cat foo/moo/foo/bar 132 atf_check -o inline:"qux\n" cat foo/moo/coo/bar 133 atf_check -o inline:"qux\n" cat foo/moo/foo/zoo 134 atf_check -o inline:"qux\n" cat foo/moo/coo/zoo 135 136 # We should have copied the contents of foo/moo before foo, coo started 137 # getting copied in. 138 atf_check -o not-empty stat foo/moo/foo/moo/roo 139 atf_check -o not-empty stat foo/moo/coo/moo/roo 140 atf_check -e not-empty -s not-exit:0 stat foo/moo/foo/moo/foo 141 atf_check -e not-empty -s not-exit:0 stat foo/moo/coo/moo/coo 142} 143 144atf_test_case matching_srctgt_link 145matching_srctgt_link_body() 146{ 147 148 mkdir foo 149 echo "qux" > foo/bar 150 cp foo/bar foo/zoo 151 152 atf_check ln -s foo roo 153 atf_check cp -RH roo foo 154 atf_check -o inline:"qux\n" cat foo/roo/bar 155 atf_check -o inline:"qux\n" cat foo/roo/zoo 156} 157 158atf_test_case matching_srctgt_nonexistent 159matching_srctgt_nonexistent_body() 160{ 161 162 # We'll copy foo to a nonexistent subdirectory; ideally, we would 163 # skip just the directory and end up with a layout like; 164 # 165 # foo/ 166 # bar 167 # dne/ 168 # bar 169 # zoo 170 # zoo 171 # 172 mkdir foo 173 echo "qux" > foo/bar 174 cp foo/bar foo/zoo 175 176 atf_check cp -R foo foo/dne 177 atf_check -o inline:"qux\n" cat foo/dne/bar 178 atf_check -o inline:"qux\n" cat foo/dne/zoo 179 atf_check -e not-empty -s not-exit:0 stat foo/dne/foo 180} 181 182atf_test_case pflag_acls 183pflag_acls_body() 184{ 185 mkdir dir 186 ln -s dir lnk 187 echo "hello" >dir/file 188 if ! setfacl -m g:staff:D::allow dir || 189 ! setfacl -m g:staff:d::allow dir/file ; then 190 atf_skip "file system does not support ACLs" 191 fi 192 atf_check -o match:"group:staff:-+D-+" getfacl dir 193 atf_check -o match:"group:staff:-+d-+" getfacl dir/file 194 # file-to-file copy without -p 195 atf_check cp dir/file dst1 196 atf_check -o not-match:"group:staff:-+d-+" getfacl dst1 197 # file-to-file copy with -p 198 atf_check cp -p dir/file dst2 199 atf_check -o match:"group:staff:-+d-+" getfacl dst2 200 # recursive copy without -p 201 atf_check cp -r dir dst3 202 atf_check -o not-match:"group:staff:-+D-+" getfacl dst3 203 atf_check -o not-match:"group:staff:-+d-+" getfacl dst3/file 204 # recursive copy with -p 205 atf_check cp -rp dir dst4 206 atf_check -o match:"group:staff:-+D-+" getfacl dst4 207 atf_check -o match:"group:staff:-+d-+" getfacl dst4/file 208 # source is a link without -p 209 atf_check cp -r lnk dst5 210 atf_check -o not-match:"group:staff:-+D-+" getfacl dst5 211 atf_check -o not-match:"group:staff:-+d-+" getfacl dst5/file 212 # source is a link with -p 213 atf_check cp -rp lnk dst6 214 atf_check -o match:"group:staff:-+D-+" getfacl dst6 215 atf_check -o match:"group:staff:-+d-+" getfacl dst6/file 216} 217 218atf_test_case pflag_flags 219pflag_flags_body() 220{ 221 mkdir dir 222 ln -s dir lnk 223 echo "hello" >dir/file 224 if ! chflags nodump dir || 225 ! chflags nodump dir/file ; then 226 atf_skip "file system does not support flags" 227 fi 228 atf_check -o match:"nodump" stat -f%Sf dir 229 atf_check -o match:"nodump" stat -f%Sf dir/file 230 # file-to-file copy without -p 231 atf_check cp dir/file dst1 232 atf_check -o not-match:"nodump" stat -f%Sf dst1 233 # file-to-file copy with -p 234 atf_check cp -p dir/file dst2 235 atf_check -o match:"nodump" stat -f%Sf dst2 236 # recursive copy without -p 237 atf_check cp -r dir dst3 238 atf_check -o not-match:"nodump" stat -f%Sf dst3 239 atf_check -o not-match:"nodump" stat -f%Sf dst3/file 240 # recursive copy with -p 241 atf_check cp -rp dir dst4 242 atf_check -o match:"nodump" stat -f%Sf dst4 243 atf_check -o match:"nodump" stat -f%Sf dst4/file 244 # source is a link without -p 245 atf_check cp -r lnk dst5 246 atf_check -o not-match:"nodump" stat -f%Sf dst5 247 atf_check -o not-match:"nodump" stat -f%Sf dst5/file 248 # source is a link with -p 249 atf_check cp -rp lnk dst6 250 atf_check -o match:"nodump" stat -f%Sf dst6 251 atf_check -o match:"nodump" stat -f%Sf dst6/file 252} 253 254recursive_link_setup() 255{ 256 extra_cpflag=$1 257 258 mkdir -p foo/bar 259 ln -s bar foo/baz 260 261 mkdir foo-mirror 262 eval "cp -R $extra_cpflag foo foo-mirror" 263} 264 265atf_test_case recursive_link_dflt 266recursive_link_dflt_body() 267{ 268 recursive_link_setup 269 270 # -P is the default, so this should work and preserve the link. 271 atf_check cp -R foo foo-mirror 272 atf_check test -L foo-mirror/foo/baz 273} 274 275atf_test_case recursive_link_Hflag 276recursive_link_Hflag_body() 277{ 278 recursive_link_setup 279 280 # -H will not follow either, so this should also work and preserve the 281 # link. 282 atf_check cp -RH foo foo-mirror 283 atf_check test -L foo-mirror/foo/baz 284} 285 286atf_test_case recursive_link_Lflag 287recursive_link_Lflag_body() 288{ 289 recursive_link_setup -L 290 291 # -L will work, but foo/baz ends up expanded to a directory. 292 atf_check test -d foo-mirror/foo/baz -a \ 293 '(' ! -L foo-mirror/foo/baz ')' 294 atf_check cp -RL foo foo-mirror 295 atf_check test -d foo-mirror/foo/baz -a \ 296 '(' ! -L foo-mirror/foo/baz ')' 297} 298 299atf_test_case samefile 300samefile_body() 301{ 302 echo "foo" >foo 303 ln foo bar 304 ln -s bar baz 305 atf_check -e match:"baz and baz are identical" \ 306 -s exit:1 cp baz baz 307 atf_check -e match:"bar and baz are identical" \ 308 -s exit:1 cp baz bar 309 atf_check -e match:"foo and baz are identical" \ 310 -s exit:1 cp baz foo 311 atf_check -e match:"bar and foo are identical" \ 312 -s exit:1 cp foo bar 313} 314 315file_is_sparse() 316{ 317 atf_check ${0%/*}/sparse "$1" 318} 319 320files_are_equal() 321{ 322 atf_check_not_equal "$(stat -f%d,%i "$1")" "$(stat -f%d,%i "$2")" 323 atf_check cmp "$1" "$2" 324} 325 326atf_test_case sparse_leading_hole 327sparse_leading_hole_body() 328{ 329 # A 16-megabyte hole followed by one megabyte of data 330 truncate -s 16M foo 331 seq -f%015g 65536 >>foo 332 file_is_sparse foo 333 334 atf_check cp foo bar 335 files_are_equal foo bar 336 file_is_sparse bar 337} 338 339atf_test_case sparse_multiple_holes 340sparse_multiple_holes_body() 341{ 342 # Three one-megabyte blocks of data preceded, separated, and 343 # followed by 16-megabyte holes 344 truncate -s 16M foo 345 seq -f%015g 65536 >>foo 346 truncate -s 33M foo 347 seq -f%015g 65536 >>foo 348 truncate -s 50M foo 349 seq -f%015g 65536 >>foo 350 truncate -s 67M foo 351 file_is_sparse foo 352 353 atf_check cp foo bar 354 files_are_equal foo bar 355 file_is_sparse bar 356} 357 358atf_test_case sparse_only_hole 359sparse_only_hole_body() 360{ 361 # A 16-megabyte hole 362 truncate -s 16M foo 363 file_is_sparse foo 364 365 atf_check cp foo bar 366 files_are_equal foo bar 367 file_is_sparse bar 368} 369 370atf_test_case sparse_to_dev 371sparse_to_dev_body() 372{ 373 # Three one-megabyte blocks of data preceded, separated, and 374 # followed by 16-megabyte holes 375 truncate -s 16M foo 376 seq -f%015g 65536 >>foo 377 truncate -s 33M foo 378 seq -f%015g 65536 >>foo 379 truncate -s 50M foo 380 seq -f%015g 65536 >>foo 381 truncate -s 67M foo 382 file_is_sparse foo 383 384 atf_check -o file:foo cp foo /dev/stdout 385} 386 387atf_test_case sparse_trailing_hole 388sparse_trailing_hole_body() 389{ 390 # One megabyte of data followed by a 16-megabyte hole 391 seq -f%015g 65536 >foo 392 truncate -s 17M foo 393 file_is_sparse foo 394 395 atf_check cp foo bar 396 files_are_equal foo bar 397 file_is_sparse bar 398} 399 400atf_test_case standalone_Pflag 401standalone_Pflag_body() 402{ 403 echo "foo" > bar 404 ln -s bar foo 405 406 atf_check cp -P foo baz 407 atf_check -o inline:'Symbolic Link\n' stat -f %SHT baz 408} 409 410atf_test_case symlink 411symlink_body() 412{ 413 echo "foo" >foo 414 atf_check cp -s foo bar 415 atf_check -o inline:"foo\n" cat bar 416 atf_check -o inline:"foo\n" readlink bar 417} 418 419atf_test_case symlink_exists 420symlink_exists_body() 421{ 422 echo "foo" >foo 423 echo "bar" >bar 424 atf_check -s not-exit:0 -e match:exists cp -s foo bar 425 atf_check -o inline:"bar\n" cat bar 426} 427 428atf_test_case symlink_exists_force 429symlink_exists_force_body() 430{ 431 echo "foo" >foo 432 echo "bar" >bar 433 atf_check cp -fs foo bar 434 atf_check -o inline:"foo\n" cat bar 435 atf_check -o inline:"foo\n" readlink bar 436} 437 438atf_test_case directory_to_symlink 439directory_to_symlink_body() 440{ 441 mkdir -p foo 442 ln -s .. foo/bar 443 mkdir bar 444 touch bar/baz 445 atf_check -s not-exit:0 -e match:"Not a directory" \ 446 cp -R bar foo 447 atf_check -s not-exit:0 -e match:"Not a directory" \ 448 cp -r bar foo 449} 450 451atf_test_case overwrite_directory 452overwrite_directory_body() 453{ 454 mkdir -p foo/bar/baz 455 touch bar 456 atf_check -s not-exit:0 -e match:"Is a directory" \ 457 cp bar foo 458 rm bar 459 mkdir bar 460 touch bar/baz 461 atf_check -s not-exit:0 -e match:"Is a directory" \ 462 cp -R bar foo 463 atf_check -s not-exit:0 -e match:"Is a directory" \ 464 cp -r bar foo 465} 466 467atf_test_case to_dir_dne 468to_dir_dne_body() 469{ 470 mkdir dir 471 echo "foo" >dir/foo 472 atf_check cp -r dir dne 473 atf_check test -d dne 474 atf_check test -f dne/foo 475 atf_check cmp dir/foo dne/foo 476} 477 478atf_test_case to_nondir 479to_nondir_body() 480{ 481 echo "foo" >foo 482 echo "bar" >bar 483 echo "baz" >baz 484 # This is described as “case 1” in source code comments 485 atf_check cp foo bar 486 atf_check cmp -s foo bar 487 # This is “case 2”, the target must be a directory 488 atf_check -s not-exit:0 -e match:"Not a directory" \ 489 cp foo bar baz 490} 491 492atf_test_case to_deadlink 493to_deadlink_body() 494{ 495 echo "foo" >foo 496 ln -s bar baz 497 atf_check cp foo baz 498 atf_check cmp -s foo bar 499} 500 501atf_test_case to_deadlink_append 502to_deadlink_append_body() 503{ 504 echo "foo" >foo 505 mkdir bar 506 ln -s baz bar/foo 507 atf_check cp foo bar 508 atf_check cmp -s foo bar/baz 509 rm -f bar/foo bar/baz 510 ln -s baz bar/foo 511 atf_check cp foo bar/ 512 atf_check cmp -s foo bar/baz 513 rm -f bar/foo bar/baz 514 ln -s $PWD/baz bar/foo 515 atf_check cp foo bar/ 516 atf_check cmp -s foo baz 517} 518 519atf_test_case to_dirlink 520to_dirlink_body() 521{ 522 mkdir src dir 523 echo "foo" >src/file 524 ln -s dir dst 525 atf_check cp -r src dst 526 atf_check cmp -s src/file dir/src/file 527 rm -r dir/* 528 atf_check cp -r src dst/ 529 atf_check cmp -s src/file dir/src/file 530 rm -r dir/* 531 # If the source is a directory and ends in a slash, our cp has 532 # traditionally copied the contents of the source rather than 533 # the source itself. It is unclear whether this is intended 534 # or simply a consequence of how FTS handles the situation. 535 # Notably, GNU cp does not behave in this manner. 536 atf_check cp -r src/ dst 537 atf_check cmp -s src/file dir/file 538 rm -r dir/* 539 atf_check cp -r src/ dst/ 540 atf_check cmp -s src/file dir/file 541 rm -r dir/* 542} 543 544atf_test_case to_deaddirlink 545to_deaddirlink_body() 546{ 547 mkdir src 548 echo "foo" >src/file 549 ln -s dir dst 550 # It is unclear which error we should expect in these cases. 551 # Our current implementation always reports ENOTDIR, but one 552 # might be equally justified in expecting EEXIST or ENOENT. 553 # GNU cp reports EEXIST when the destination is given with a 554 # trailing slash and “cannot overwrite non-directory with 555 # directory” otherwise. 556 atf_check -s not-exit:0 -e ignore \ 557 cp -r src dst 558 atf_check -s not-exit:0 -e ignore \ 559 cp -r src dst/ 560 atf_check -s not-exit:0 -e ignore \ 561 cp -r src/ dst 562 atf_check -s not-exit:0 -e ignore \ 563 cp -r src/ dst/ 564 atf_check -s not-exit:0 -e ignore \ 565 cp -R src dst 566 atf_check -s not-exit:0 -e ignore \ 567 cp -R src dst/ 568 atf_check -s not-exit:0 -e ignore \ 569 cp -R src/ dst 570 atf_check -s not-exit:0 -e ignore \ 571 cp -R src/ dst/ 572} 573 574atf_test_case to_link_outside 575to_link_outside_body() 576{ 577 mkdir dir dst dst/dir 578 echo "foo" >dir/file 579 ln -s ../../file dst/dir/file 580 atf_check \ 581 -s exit:1 \ 582 -e match:"dst/dir/file: Permission denied" \ 583 cp -r dir dst 584} 585 586atf_test_case dstmode 587dstmode_body() 588{ 589 mkdir -m 0755 dir 590 echo "foo" >dir/file 591 umask 0177 592 atf_check cp -R dir dst 593 umask 022 594 atf_check -o inline:"40600\n" stat -f%p dst 595 atf_check chmod 0750 dst 596 atf_check cmp dir/file dst/file 597} 598 599atf_test_case to_root cleanup 600to_root_head() 601{ 602 atf_set "require.user" "unprivileged" 603} 604to_root_body() 605{ 606 dst="test.$(atf_get ident).$$" 607 echo "$dst" >dst 608 echo "foo" >"$dst" 609 atf_check -s not-exit:0 \ 610 -e match:"^cp: /$dst: (Permission|Read-only)" \ 611 cp "$dst" / 612 atf_check -s not-exit:0 \ 613 -e match:"^cp: /$dst: (Permission|Read-only)" \ 614 cp "$dst" // 615} 616to_root_cleanup() 617{ 618 (dst=$(cat dst) && rm "/$dst") 2>/dev/null || true 619} 620 621atf_test_case dirloop 622dirloop_head() 623{ 624 atf_set "descr" "Test cycle detection when recursing" 625} 626dirloop_body() 627{ 628 mkdir -p src/a src/b 629 ln -s ../b src/a 630 ln -s ../a src/b 631 atf_check \ 632 -s exit:1 \ 633 -e match:"src/a/b/a: directory causes a cycle" \ 634 -e match:"src/b/a/b: directory causes a cycle" \ 635 cp -r src dst 636 atf_check test -d dst 637 atf_check test -d dst/a 638 atf_check test -d dst/b 639 atf_check test -d dst/a/b 640 atf_check test ! -e dst/a/b/a 641 atf_check test -d dst/b/a 642 atf_check test ! -e dst/b/a/b 643} 644 645atf_test_case unrdir 646unrdir_head() 647{ 648 atf_set "descr" "Test handling of unreadable directories" 649} 650unrdir_body() 651{ 652 for d in a b c ; do 653 mkdir -p src/$d 654 echo "$d" >src/$d/f 655 done 656 chmod 0 src/b 657 atf_check \ 658 -s exit:1 \ 659 -e match:"^cp: src/b: Permission denied" \ 660 cp -R src dst 661 atf_check test -d dst/a 662 atf_check cmp src/a/f dst/a/f 663 atf_check test -d dst/b 664 atf_check test ! -e dst/b/f 665 atf_check test -d dst/c 666 atf_check cmp src/c/f dst/c/f 667} 668 669atf_test_case unrfile 670unrfile_head() 671{ 672 atf_set "descr" "Test handling of unreadable files" 673} 674unrfile_body() 675{ 676 mkdir src 677 for d in a b c ; do 678 echo "$d" >src/$d 679 done 680 chmod 0 src/b 681 atf_check \ 682 -s exit:1 \ 683 -e match:"^cp: src/b: Permission denied" \ 684 cp -R src dst 685 atf_check test -d dst 686 atf_check cmp src/a dst/a 687 atf_check test ! -e dst/b 688 atf_check cmp src/c dst/c 689} 690 691atf_init_test_cases() 692{ 693 atf_add_test_case basic 694 atf_add_test_case basic_symlink 695 atf_add_test_case chrdev 696 atf_add_test_case hardlink 697 atf_add_test_case hardlink_exists 698 atf_add_test_case hardlink_exists_force 699 atf_add_test_case matching_srctgt 700 atf_add_test_case matching_srctgt_contained 701 atf_add_test_case matching_srctgt_link 702 atf_add_test_case matching_srctgt_nonexistent 703 atf_add_test_case pflag_acls 704 atf_add_test_case pflag_flags 705 atf_add_test_case recursive_link_dflt 706 atf_add_test_case recursive_link_Hflag 707 atf_add_test_case recursive_link_Lflag 708 atf_add_test_case samefile 709 atf_add_test_case sparse_leading_hole 710 atf_add_test_case sparse_multiple_holes 711 atf_add_test_case sparse_only_hole 712 atf_add_test_case sparse_to_dev 713 atf_add_test_case sparse_trailing_hole 714 atf_add_test_case standalone_Pflag 715 atf_add_test_case symlink 716 atf_add_test_case symlink_exists 717 atf_add_test_case symlink_exists_force 718 atf_add_test_case directory_to_symlink 719 atf_add_test_case overwrite_directory 720 atf_add_test_case to_dir_dne 721 atf_add_test_case to_nondir 722 atf_add_test_case to_deadlink 723 atf_add_test_case to_deadlink_append 724 atf_add_test_case to_dirlink 725 atf_add_test_case to_deaddirlink 726 atf_add_test_case to_link_outside 727 atf_add_test_case dstmode 728 atf_add_test_case to_root 729 atf_add_test_case dirloop 730 atf_add_test_case unrdir 731 atf_add_test_case unrfile 732} 733