xref: /freebsd/bin/cp/tests/cp_test.sh (revision 89990e28e6e1cae1b3d74d5a06159c1f9e12abc5)
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_head()
38{
39	atf_set "descr" "Copy a file"
40}
41basic_body()
42{
43	echo "foo" > bar
44
45	atf_check cp bar baz
46	check_size baz 4
47}
48
49atf_test_case basic_symlink
50basic_symlink_head()
51{
52	atf_set "descr" "Copy a symlink to a file"
53}
54basic_symlink_body()
55{
56	echo "foo" > bar
57	ln -s bar baz
58
59	atf_check cp baz foo
60	atf_check test ! -L foo
61
62	atf_check cmp foo bar
63}
64
65atf_test_case chrdev
66chrdev_head()
67{
68	atf_set "descr" "Copy a character device"
69}
70chrdev_body()
71{
72	echo "foo" > bar
73
74	check_size bar 4
75	atf_check cp /dev/null trunc
76	check_size trunc 0
77	atf_check cp bar trunc
78	check_size trunc 4
79	atf_check cp /dev/null trunc
80	check_size trunc 0
81}
82
83atf_test_case hardlink
84hardlink_head()
85{
86	atf_set "descr" "Create a hard link to a file"
87}
88hardlink_body()
89{
90	echo "foo" >foo
91	atf_check cp -l foo bar
92	atf_check -o inline:"foo\n" cat bar
93	atf_check_equal "$(stat -f%d,%i foo)" "$(stat -f%d,%i bar)"
94}
95
96atf_test_case hardlink_exists
97hardlink_exists_head()
98{
99	atf_set "descr" "Attempt to create a hard link to a file, " \
100	    "but the destination already exists"
101}
102hardlink_exists_body()
103{
104	echo "foo" >foo
105	echo "bar" >bar
106	atf_check -s not-exit:0 -e match:exists cp -l foo bar
107	atf_check -o inline:"bar\n" cat bar
108	atf_check_not_equal "$(stat -f%d,%i foo)" "$(stat -f%d,%i bar)"
109}
110
111atf_test_case hardlink_exists_force
112hardlink_exists_force_head()
113{
114	atf_set "descr" "Force creation of a hard link to a file " \
115	    "when the destination already exists"
116}
117hardlink_exists_force_body()
118{
119	echo "foo" >foo
120	echo "bar" >bar
121	atf_check cp -fl foo bar
122	atf_check -o inline:"foo\n" cat bar
123	atf_check_equal "$(stat -f%d,%i foo)" "$(stat -f%d,%i bar)"
124}
125
126atf_test_case matching_srctgt
127matching_srctgt_head()
128{
129	atf_set "descr" "Avoid infinite loop when copying a directory to itself"
130}
131matching_srctgt_body()
132{
133	# PR235438: `cp -R foo foo` would previously infinitely recurse and
134	# eventually error out.
135	mkdir foo
136	echo "qux" > foo/bar
137	cp foo/bar foo/zoo
138
139	atf_check cp -R foo foo
140	atf_check -o inline:"qux\n" cat foo/foo/bar
141	atf_check -o inline:"qux\n" cat foo/foo/zoo
142	atf_check test ! -e foo/foo/foo
143}
144
145atf_test_case matching_srctgt_contained
146matching_srctgt_contained_head()
147{
148	atf_set "descr" "Avoid infinite loop when copying a directory " \
149	    "into an existing subdirectory of itself"
150}
151matching_srctgt_contained_body()
152{
153	# Let's do the same thing, except we'll try to recursively copy foo into
154	# one of its subdirectories.
155	mkdir foo
156	ln -s foo coo
157	echo "qux" > foo/bar
158	mkdir foo/moo
159	touch foo/moo/roo
160	cp foo/bar foo/zoo
161
162	atf_check cp -R foo foo/moo
163	atf_check cp -RH coo foo/moo
164	atf_check -o inline:"qux\n" cat foo/moo/foo/bar
165	atf_check -o inline:"qux\n" cat foo/moo/coo/bar
166	atf_check -o inline:"qux\n" cat foo/moo/foo/zoo
167	atf_check -o inline:"qux\n" cat foo/moo/coo/zoo
168
169	# We should have copied the contents of foo/moo before foo, coo started
170	# getting copied in.
171	atf_check -o not-empty stat foo/moo/foo/moo/roo
172	atf_check -o not-empty stat foo/moo/coo/moo/roo
173	atf_check -e not-empty -s not-exit:0 stat foo/moo/foo/moo/foo
174	atf_check -e not-empty -s not-exit:0 stat foo/moo/coo/moo/coo
175}
176
177atf_test_case matching_srctgt_link
178matching_srctgt_link_head()
179{
180	atf_set "descr" "Avoid infinite loop when recursively copying a " \
181	    "symlink to a directory into the directory it links to"
182}
183matching_srctgt_link_body()
184{
185	mkdir foo
186	echo "qux" > foo/bar
187	cp foo/bar foo/zoo
188
189	atf_check ln -s foo roo
190	atf_check cp -RH roo foo
191	atf_check -o inline:"qux\n" cat foo/roo/bar
192	atf_check -o inline:"qux\n" cat foo/roo/zoo
193}
194
195atf_test_case matching_srctgt_nonexistent
196matching_srctgt_nonexistent_head()
197{
198	atf_set "descr" "Avoid infinite loop when recursively copying a " \
199	    "directory into a new subdirectory of itself"
200}
201matching_srctgt_nonexistent_body()
202{
203	# We'll copy foo to a nonexistent subdirectory; ideally, we would
204	# skip just the directory and end up with a layout like;
205	#
206	# foo/
207	#     bar
208	#     dne/
209	#         bar
210	#         zoo
211	#     zoo
212	#
213	mkdir foo
214	echo "qux" > foo/bar
215	cp foo/bar foo/zoo
216
217	atf_check cp -R foo foo/dne
218	atf_check -o inline:"qux\n" cat foo/dne/bar
219	atf_check -o inline:"qux\n" cat foo/dne/zoo
220	atf_check -e not-empty -s not-exit:0 stat foo/dne/foo
221}
222
223atf_test_case pflag_acls
224pflag_acls_head()
225{
226	atf_set "descr" "Verify that -p preserves access control lists"
227}
228pflag_acls_body()
229{
230	mkdir dir
231	ln -s dir lnk
232	echo "hello" >dir/file
233	if ! setfacl -m g:staff:D::allow dir ||
234	   ! setfacl -m g:staff:d::allow dir/file ; then
235		atf_skip "file system does not support ACLs"
236	fi
237	atf_check -o match:"group:staff:-+D-+" getfacl dir
238	atf_check -o match:"group:staff:-+d-+" getfacl dir/file
239	# file-to-file copy without -p
240	atf_check cp dir/file dst1
241	atf_check -o not-match:"group:staff:-+d-+" getfacl dst1
242	# file-to-file copy with -p
243	atf_check cp -p dir/file dst2
244	atf_check -o match:"group:staff:-+d-+" getfacl dst2
245	# recursive copy without -p
246	atf_check cp -r dir dst3
247	atf_check -o not-match:"group:staff:-+D-+" getfacl dst3
248	atf_check -o not-match:"group:staff:-+d-+" getfacl dst3/file
249	# recursive copy with -p
250	atf_check cp -rp dir dst4
251	atf_check -o match:"group:staff:-+D-+" getfacl dst4
252	atf_check -o match:"group:staff:-+d-+" getfacl dst4/file
253	# source is a link without -p
254	atf_check cp -r lnk dst5
255	atf_check -o not-match:"group:staff:-+D-+" getfacl dst5
256	atf_check -o not-match:"group:staff:-+d-+" getfacl dst5/file
257	# source is a link with -p
258	atf_check cp -rp lnk dst6
259	atf_check -o match:"group:staff:-+D-+" getfacl dst6
260	atf_check -o match:"group:staff:-+d-+" getfacl dst6/file
261}
262
263atf_test_case pflag_flags
264pflag_flags_head()
265{
266	atf_set "descr" "Verify that -p preserves file flags"
267}
268pflag_flags_body()
269{
270	mkdir dir
271	ln -s dir lnk
272	echo "hello" >dir/file
273	if ! chflags nodump dir ||
274	   ! chflags nodump dir/file ; then
275		atf_skip "file system does not support flags"
276	fi
277	atf_check -o match:"nodump" stat -f%Sf dir
278	atf_check -o match:"nodump" stat -f%Sf dir/file
279	# file-to-file copy without -p
280	atf_check cp dir/file dst1
281	atf_check -o not-match:"nodump" stat -f%Sf dst1
282	# file-to-file copy with -p
283	atf_check cp -p dir/file dst2
284	atf_check -o match:"nodump" stat -f%Sf dst2
285	# recursive copy without -p
286	atf_check cp -r dir dst3
287	atf_check -o not-match:"nodump" stat -f%Sf dst3
288	atf_check -o not-match:"nodump" stat -f%Sf dst3/file
289	# recursive copy with -p
290	atf_check cp -rp dir dst4
291	atf_check -o match:"nodump" stat -f%Sf dst4
292	atf_check -o match:"nodump" stat -f%Sf dst4/file
293	# source is a link without -p
294	atf_check cp -r lnk dst5
295	atf_check -o not-match:"nodump" stat -f%Sf dst5
296	atf_check -o not-match:"nodump" stat -f%Sf dst5/file
297	# source is a link with -p
298	atf_check cp -rp lnk dst6
299	atf_check -o match:"nodump" stat -f%Sf dst6
300	atf_check -o match:"nodump" stat -f%Sf dst6/file
301}
302
303recursive_link_setup()
304{
305	extra_cpflag=$1
306
307	mkdir -p foo/bar
308	ln -s bar foo/baz
309
310	mkdir foo-mirror
311	eval "cp -R $extra_cpflag foo foo-mirror"
312}
313
314atf_test_case recursive_link_dflt
315recursive_link_dflt_head()
316{
317	atf_set "descr" "Copy a directory containing a subdirectory and a " \
318	    "symlink to that subdirectory"
319}
320recursive_link_dflt_body()
321{
322	recursive_link_setup
323
324	# -P is the default, so this should work and preserve the link.
325	atf_check cp -R foo foo-mirror
326	atf_check test -L foo-mirror/foo/baz
327	atf_check test -d foo-mirror/foo/baz
328}
329
330atf_test_case recursive_link_Hflag
331recursive_link_Hflag_head()
332{
333	atf_set "descr" "Copy a directory containing a subdirectory and a " \
334	    "symlink to that subdirectory"
335}
336recursive_link_Hflag_body()
337{
338	recursive_link_setup
339
340	# -H will not follow either, so this should also work and preserve the
341	# link.
342	atf_check cp -RH foo foo-mirror
343	atf_check test -L foo-mirror/foo/baz
344	atf_check test -d foo-mirror/foo/baz
345}
346
347atf_test_case recursive_link_Lflag
348recursive_link_Lflag_head()
349{
350	atf_set "descr" "Copy a directory containing a subdirectory and a " \
351	    "symlink to that subdirectory"
352}
353recursive_link_Lflag_body()
354{
355	recursive_link_setup -L
356
357	# -L will work, but foo/baz ends up expanded to a directory.
358	atf_check test ! -L foo-mirror/foo/baz
359	atf_check test -d foo-mirror/foo/baz
360	atf_check cp -RL foo foo-mirror
361	atf_check test ! -L foo-mirror/foo/baz
362	atf_check test -d foo-mirror/foo/baz
363}
364
365atf_test_case samefile
366samefile_head()
367{
368	atf_set "descr" "Copy a file to itself"
369}
370samefile_body()
371{
372	echo "foo" >foo
373	ln foo bar
374	ln -s bar baz
375	atf_check -e match:"baz and baz are identical" \
376	    -s exit:1 cp baz baz
377	atf_check -e match:"bar and baz are identical" \
378	    -s exit:1 cp baz bar
379	atf_check -e match:"foo and baz are identical" \
380	    -s exit:1 cp baz foo
381	atf_check -e match:"bar and foo are identical" \
382	    -s exit:1 cp foo bar
383}
384
385file_is_sparse()
386{
387	atf_check ${0%/*}/sparse "$1"
388}
389
390files_are_equal()
391{
392	atf_check_not_equal "$(stat -f%d,%i "$1")" "$(stat -f%d,%i "$2")"
393	atf_check cmp "$1" "$2"
394}
395
396atf_test_case sparse_leading_hole
397sparse_leading_hole_head()
398{
399	atf_set "descr" "Copy a sparse file stat starts with a hole"
400}
401sparse_leading_hole_body()
402{
403	# A 16-megabyte hole followed by one megabyte of data
404	truncate -s 16M foo
405	seq -f%015g 65536 >>foo
406	file_is_sparse foo
407
408	atf_check cp foo bar
409	files_are_equal foo bar
410	file_is_sparse bar
411}
412
413atf_test_case sparse_multiple_holes
414sparse_multiple_hole_head()
415{
416	atf_set "descr" "Copy a sparse file with multiple holes"
417}
418sparse_multiple_holes_body()
419{
420	# Three one-megabyte blocks of data preceded, separated, and
421	# followed by 16-megabyte holes
422	truncate -s 16M foo
423	seq -f%015g 65536 >>foo
424	truncate -s 33M foo
425	seq -f%015g 65536 >>foo
426	truncate -s 50M foo
427	seq -f%015g 65536 >>foo
428	truncate -s 67M foo
429	file_is_sparse foo
430
431	atf_check cp foo bar
432	files_are_equal foo bar
433	file_is_sparse bar
434}
435
436atf_test_case sparse_only_hole
437sparse_only_hole_head()
438{
439	atf_set "descr" "Copy a sparse file consisting entirely of a hole"
440}
441sparse_only_hole_body()
442{
443	# A 16-megabyte hole
444	truncate -s 16M foo
445	file_is_sparse foo
446
447	atf_check cp foo bar
448	files_are_equal foo bar
449	file_is_sparse bar
450}
451
452atf_test_case sparse_to_dev
453sparse_to_dev_head()
454{
455	atf_set "descr" "Copy a sparse file to a device"
456}
457sparse_to_dev_body()
458{
459	# Three one-megabyte blocks of data preceded, separated, and
460	# followed by 16-megabyte holes
461	truncate -s 16M foo
462	seq -f%015g 65536 >>foo
463	truncate -s 33M foo
464	seq -f%015g 65536 >>foo
465	truncate -s 50M foo
466	seq -f%015g 65536 >>foo
467	truncate -s 67M foo
468	file_is_sparse foo
469
470	atf_check -o file:foo cp foo /dev/stdout
471}
472
473atf_test_case sparse_trailing_hole
474sparse_trailing_hole_head()
475{
476	atf_set "descr" "Copy a sparse file that ends with a hole"
477}
478sparse_trailing_hole_body()
479{
480	# One megabyte of data followed by a 16-megabyte hole
481	seq -f%015g 65536 >foo
482	truncate -s 17M foo
483	file_is_sparse foo
484
485	atf_check cp foo bar
486	files_are_equal foo bar
487	file_is_sparse bar
488}
489
490atf_test_case standalone_Pflag
491standalone_Pflag_head()
492{
493	atf_set "descr" "Test -P without -R"
494}
495standalone_Pflag_body()
496{
497	echo "foo" > bar
498	ln -s bar foo
499
500	atf_check cp -P foo baz
501	atf_check test -L baz
502}
503
504atf_test_case symlink
505symlink_head()
506{
507	atf_set "descr" "Create a symbolic link to a file"
508}
509symlink_body()
510{
511	echo "foo" >foo
512	atf_check cp -s foo bar
513	atf_check -o inline:"foo\n" cat bar
514	atf_check -o inline:"foo\n" readlink bar
515}
516
517atf_test_case symlink_exists
518symlink_exists_head()
519{
520	atf_set "descr" "Attempt to create a symbolic link to a file, " \
521	    "but the destination already exists"
522}
523symlink_exists_body()
524{
525	echo "foo" >foo
526	echo "bar" >bar
527	atf_check -s not-exit:0 -e match:exists cp -s foo bar
528	atf_check -o inline:"bar\n" cat bar
529}
530
531atf_test_case symlink_exists_force
532symlink_exists_force_head()
533{
534	atf_set "descr" "Force creation of a symbolic link to a file " \
535	    "when the destination already exists"
536}
537symlink_exists_force_body()
538{
539	echo "foo" >foo
540	echo "bar" >bar
541	atf_check cp -fs foo bar
542	atf_check -o inline:"foo\n" cat bar
543	atf_check -o inline:"foo\n" readlink bar
544}
545
546atf_test_case directory_to_symlink
547directory_to_symlink_head()
548{
549	atf_set "descr" "Attempt to copy a directory to a symlink"
550}
551directory_to_symlink_body()
552{
553	mkdir -p foo
554	ln -s .. foo/bar
555	mkdir bar
556	touch bar/baz
557	atf_check -s not-exit:0 -e match:"Not a directory" \
558	    cp -R bar foo
559	atf_check -s not-exit:0 -e match:"Not a directory" \
560	    cp -r bar foo
561}
562
563atf_test_case overwrite_directory
564overwrite_directory_head()
565{
566	atf_set "descr" "Attempt to overwrite a directory with a file"
567}
568overwrite_directory_body()
569{
570	mkdir -p foo/bar/baz
571	touch bar
572	atf_check -s not-exit:0 -e match:"Is a directory" \
573	    cp bar foo
574	rm bar
575	mkdir bar
576	touch bar/baz
577	atf_check -s not-exit:0 -e match:"Is a directory" \
578	    cp -R bar foo
579	atf_check -s not-exit:0 -e match:"Is a directory" \
580	    cp -r bar foo
581}
582
583atf_test_case to_dir_dne
584to_dir_dne_head()
585{
586	atf_set "descr" "Copy a directory to a nonexistent directory"
587}
588to_dir_dne_body()
589{
590	mkdir dir
591	echo "foo" >dir/foo
592	atf_check cp -r dir dne
593	atf_check test -d dne
594	atf_check test -f dne/foo
595	atf_check cmp dir/foo dne/foo
596}
597
598atf_test_case to_nondir
599to_dir_dne_head()
600{
601	atf_set "descr" "Copy one or more files to a non-directory"
602}
603to_nondir_body()
604{
605	echo "foo" >foo
606	echo "bar" >bar
607	echo "baz" >baz
608	# This is described as “case 1” in source code comments
609	atf_check cp foo bar
610	atf_check cmp -s foo bar
611	# This is “case 2”, the target must be a directory
612	atf_check -s not-exit:0 -e match:"Not a directory" \
613	    cp foo bar baz
614}
615
616atf_test_case to_deadlink
617to_deadlink_head()
618{
619	atf_set "descr" "Copy a file to a dead symbolic link"
620}
621to_deadlink_body()
622{
623	echo "foo" >foo
624	ln -s bar baz
625	atf_check cp foo baz
626	atf_check cmp -s foo bar
627}
628
629atf_test_case to_deadlink_append
630to_deadlink_append_head()
631{
632	atf_set "descr" "Copy multiple files to a dead symbolic link"
633}
634to_deadlink_append_body()
635{
636	echo "foo" >foo
637	mkdir bar
638	ln -s baz bar/foo
639	atf_check cp foo bar
640	atf_check cmp -s foo bar/baz
641	rm -f bar/foo bar/baz
642	ln -s baz bar/foo
643	atf_check cp foo bar/
644	atf_check cmp -s foo bar/baz
645	rm -f bar/foo bar/baz
646	ln -s $PWD/baz bar/foo
647	atf_check cp foo bar/
648	atf_check cmp -s foo baz
649}
650
651atf_test_case to_dirlink
652to_dirlink_head()
653{
654	atf_set "descr" "Copy things to a symbolic link to a directory"
655}
656to_dirlink_body()
657{
658	mkdir src dir
659	echo "foo" >src/file
660	ln -s dir dst
661	atf_check cp -r src dst
662	atf_check cmp -s src/file dir/src/file
663	rm -r dir/*
664	atf_check cp -r src dst/
665	atf_check cmp -s src/file dir/src/file
666	rm -r dir/*
667	# If the source is a directory and ends in a slash, our cp has
668	# traditionally copied the contents of the source rather than
669	# the source itself.  It is unclear whether this is intended
670	# or simply a consequence of how FTS handles the situation.
671	# Notably, GNU cp does not behave in this manner.
672	atf_check cp -r src/ dst
673	atf_check cmp -s src/file dir/file
674	rm -r dir/*
675	atf_check cp -r src/ dst/
676	atf_check cmp -s src/file dir/file
677	rm -r dir/*
678}
679
680atf_test_case to_deaddirlink
681to_deaddirlink_head()
682{
683	atf_set "descr" "Copy things to a symbolic link to a nonexistent " \
684	    "directory"
685}
686to_deaddirlink_body()
687{
688	mkdir src
689	echo "foo" >src/file
690	ln -s dir dst
691	# It is unclear which error we should expect in these cases.
692	# Our current implementation always reports ENOTDIR, but one
693	# might be equally justified in expecting EEXIST or ENOENT.
694	# GNU cp reports EEXIST when the destination is given with a
695	# trailing slash and “cannot overwrite non-directory with
696	# directory” otherwise.
697	atf_check -s not-exit:0 -e ignore \
698	    cp -r src dst
699	atf_check -s not-exit:0 -e ignore \
700	    cp -r src dst/
701	atf_check -s not-exit:0 -e ignore \
702	    cp -r src/ dst
703	atf_check -s not-exit:0 -e ignore \
704	    cp -r src/ dst/
705	atf_check -s not-exit:0 -e ignore \
706	    cp -R src dst
707	atf_check -s not-exit:0 -e ignore \
708	    cp -R src dst/
709	atf_check -s not-exit:0 -e ignore \
710	    cp -R src/ dst
711	atf_check -s not-exit:0 -e ignore \
712	    cp -R src/ dst/
713}
714
715atf_test_case to_link_outside
716to_link_outside_head()
717{
718	atf_set "descr" "Recursively copy a directory containing a symbolic " \
719	    "link that points to somewhere outside the source directory"
720}
721to_link_outside_body()
722{
723	mkdir dir dst dst/dir
724	echo "foo" >dir/file
725	ln -s ../../file dst/dir/file
726	atf_check \
727	    -s exit:1 \
728	    -e match:"dst/dir/file: Permission denied" \
729	    cp -r dir dst
730}
731
732atf_test_case dstmode
733dstmode_head()
734{
735	atf_set "descr" "Verify that directories are created with the " \
736	    "correct permissions"
737}
738dstmode_body()
739{
740	mkdir -m 0755 dir
741	echo "foo" >dir/file
742	umask 0177
743	atf_check cp -R dir dst
744	umask 022
745	atf_check -o inline:"40600\n" stat -f%p dst
746	atf_check chmod 0750 dst
747	atf_check cmp dir/file dst/file
748}
749
750atf_test_case to_root cleanup
751to_root_head()
752{
753	atf_set "require.user" "unprivileged"
754}
755to_root_body()
756{
757	dst="test.$(atf_get ident).$$"
758	echo "$dst" >dst
759	echo "foo" >"$dst"
760	atf_check -s not-exit:0 \
761	    -e match:"^cp: /$dst: (Permission|Read-only)" \
762	    cp "$dst" /
763	atf_check -s not-exit:0 \
764	    -e match:"^cp: /$dst: (Permission|Read-only)" \
765	    cp "$dst" //
766}
767to_root_cleanup()
768{
769	(dst=$(cat dst) && rm "/$dst") 2>/dev/null || true
770}
771
772atf_test_case dirloop
773dirloop_head()
774{
775	atf_set "descr" "Test cycle detection when recursing"
776}
777dirloop_body()
778{
779	mkdir -p src/a src/b
780	ln -s ../b src/a
781	ln -s ../a src/b
782	atf_check \
783	    -s exit:1 \
784	    -e match:"src/a/b/a: directory causes a cycle" \
785	    -e match:"src/b/a/b: directory causes a cycle" \
786	    cp -r src dst
787	atf_check test -d dst
788	atf_check test -d dst/a
789	atf_check test -d dst/b
790	atf_check test -d dst/a/b
791	atf_check test ! -e dst/a/b/a
792	atf_check test -d dst/b/a
793	atf_check test ! -e dst/b/a/b
794}
795
796atf_test_case unrdir
797unrdir_head()
798{
799	atf_set "descr" "Test handling of unreadable directories"
800	atf_set "require.user" "unprivileged"
801}
802unrdir_body()
803{
804	for d in a b c ; do
805		mkdir -p src/$d
806		echo "$d" >src/$d/f
807	done
808	chmod 0 src/b
809	atf_check \
810	    -s exit:1 \
811	    -e match:"^cp: src/b: Permission denied" \
812	    cp -R --sort src dst
813	atf_check test -d dst/a
814	atf_check cmp src/a/f dst/a/f
815	atf_check test -d dst/b
816	atf_check test ! -e dst/b/f
817	atf_check test -d dst/c
818	atf_check cmp src/c/f dst/c/f
819}
820
821atf_test_case unrfile
822unrfile_head()
823{
824	atf_set "descr" "Test handling of unreadable files"
825	atf_set "require.user" "unprivileged"
826}
827unrfile_body()
828{
829	mkdir src
830	for d in a b c ; do
831		echo "$d" >src/$d
832	done
833	chmod 0 src/b
834	atf_check \
835	    -s exit:1 \
836	    -e match:"^cp: src/b: Permission denied" \
837	    cp -R --sort src dst
838	atf_check test -d dst
839	atf_check cmp src/a dst/a
840	atf_check test ! -e dst/b
841	atf_check cmp src/c dst/c
842}
843
844atf_test_case nopermute
845nopermute_head()
846{
847	atf_set descr "Check that getopt_long does not permute options"
848}
849nopermute_body()
850{
851	mkdir src dst
852	atf_check \
853	    -s exit:1 \
854	    -e match:'cp: -p: No such file' \
855	    cp -R src -p dst
856	atf_check test -d dst/src
857}
858
859atf_init_test_cases()
860{
861	atf_add_test_case basic
862	atf_add_test_case basic_symlink
863	atf_add_test_case chrdev
864	atf_add_test_case hardlink
865	atf_add_test_case hardlink_exists
866	atf_add_test_case hardlink_exists_force
867	atf_add_test_case matching_srctgt
868	atf_add_test_case matching_srctgt_contained
869	atf_add_test_case matching_srctgt_link
870	atf_add_test_case matching_srctgt_nonexistent
871	atf_add_test_case pflag_acls
872	atf_add_test_case pflag_flags
873	atf_add_test_case recursive_link_dflt
874	atf_add_test_case recursive_link_Hflag
875	atf_add_test_case recursive_link_Lflag
876	atf_add_test_case samefile
877	atf_add_test_case sparse_leading_hole
878	atf_add_test_case sparse_multiple_holes
879	atf_add_test_case sparse_only_hole
880	atf_add_test_case sparse_to_dev
881	atf_add_test_case sparse_trailing_hole
882	atf_add_test_case standalone_Pflag
883	atf_add_test_case symlink
884	atf_add_test_case symlink_exists
885	atf_add_test_case symlink_exists_force
886	atf_add_test_case directory_to_symlink
887	atf_add_test_case overwrite_directory
888	atf_add_test_case to_dir_dne
889	atf_add_test_case to_nondir
890	atf_add_test_case to_deadlink
891	atf_add_test_case to_deadlink_append
892	atf_add_test_case to_dirlink
893	atf_add_test_case to_deaddirlink
894	atf_add_test_case to_link_outside
895	atf_add_test_case dstmode
896	atf_add_test_case to_root
897	atf_add_test_case dirloop
898	atf_add_test_case unrdir
899	atf_add_test_case unrfile
900	atf_add_test_case nopermute
901}
902