xref: /freebsd/bin/cp/tests/cp_test.sh (revision 0729d1e8fd90afc2f19e9b9834533181caf6ddee)
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
182recursive_link_setup()
183{
184	extra_cpflag=$1
185
186	mkdir -p foo/bar
187	ln -s bar foo/baz
188
189	mkdir foo-mirror
190	eval "cp -R $extra_cpflag foo foo-mirror"
191}
192
193atf_test_case recursive_link_dflt
194recursive_link_dflt_body()
195{
196	recursive_link_setup
197
198	# -P is the default, so this should work and preserve the link.
199	atf_check cp -R foo foo-mirror
200	atf_check test -L foo-mirror/foo/baz
201}
202
203atf_test_case recursive_link_Hflag
204recursive_link_Hflag_body()
205{
206	recursive_link_setup
207
208	# -H will not follow either, so this should also work and preserve the
209	# link.
210	atf_check cp -RH foo foo-mirror
211	atf_check test -L foo-mirror/foo/baz
212}
213
214atf_test_case recursive_link_Lflag
215recursive_link_Lflag_body()
216{
217	recursive_link_setup -L
218
219	# -L will work, but foo/baz ends up expanded to a directory.
220	atf_check test -d foo-mirror/foo/baz -a \
221	    '(' ! -L foo-mirror/foo/baz ')'
222	atf_check cp -RL foo foo-mirror
223	atf_check test -d foo-mirror/foo/baz -a \
224	    '(' ! -L foo-mirror/foo/baz ')'
225}
226
227atf_test_case samefile
228samefile_body()
229{
230	echo "foo" >foo
231	ln foo bar
232	ln -s bar baz
233	atf_check -e match:"baz and baz are identical" \
234	    -s exit:1 cp baz baz
235	atf_check -e match:"bar and baz are identical" \
236	    -s exit:1 cp baz bar
237	atf_check -e match:"foo and baz are identical" \
238	    -s exit:1 cp baz foo
239	atf_check -e match:"bar and foo are identical" \
240	    -s exit:1 cp foo bar
241}
242
243file_is_sparse()
244{
245	atf_check ${0%/*}/sparse "$1"
246}
247
248files_are_equal()
249{
250	atf_check_not_equal "$(stat -f%d,%i "$1")" "$(stat -f%d,%i "$2")"
251	atf_check cmp "$1" "$2"
252}
253
254atf_test_case sparse_leading_hole
255sparse_leading_hole_body()
256{
257	# A 16-megabyte hole followed by one megabyte of data
258	truncate -s 16M foo
259	seq -f%015g 65536 >>foo
260	file_is_sparse foo
261
262	atf_check cp foo bar
263	files_are_equal foo bar
264	file_is_sparse bar
265}
266
267atf_test_case sparse_multiple_holes
268sparse_multiple_holes_body()
269{
270	# Three one-megabyte blocks of data preceded, separated, and
271	# followed by 16-megabyte holes
272	truncate -s 16M foo
273	seq -f%015g 65536 >>foo
274	truncate -s 33M foo
275	seq -f%015g 65536 >>foo
276	truncate -s 50M foo
277	seq -f%015g 65536 >>foo
278	truncate -s 67M foo
279	file_is_sparse foo
280
281	atf_check cp foo bar
282	files_are_equal foo bar
283	file_is_sparse bar
284}
285
286atf_test_case sparse_only_hole
287sparse_only_hole_body()
288{
289	# A 16-megabyte hole
290	truncate -s 16M foo
291	file_is_sparse foo
292
293	atf_check cp foo bar
294	files_are_equal foo bar
295	file_is_sparse bar
296}
297
298atf_test_case sparse_to_dev
299sparse_to_dev_body()
300{
301	# Three one-megabyte blocks of data preceded, separated, and
302	# followed by 16-megabyte holes
303	truncate -s 16M foo
304	seq -f%015g 65536 >>foo
305	truncate -s 33M foo
306	seq -f%015g 65536 >>foo
307	truncate -s 50M foo
308	seq -f%015g 65536 >>foo
309	truncate -s 67M foo
310	file_is_sparse foo
311
312	atf_check -o file:foo cp foo /dev/stdout
313}
314
315atf_test_case sparse_trailing_hole
316sparse_trailing_hole_body()
317{
318	# One megabyte of data followed by a 16-megabyte hole
319	seq -f%015g 65536 >foo
320	truncate -s 17M foo
321	file_is_sparse foo
322
323	atf_check cp foo bar
324	files_are_equal foo bar
325	file_is_sparse bar
326}
327
328atf_test_case standalone_Pflag
329standalone_Pflag_body()
330{
331	echo "foo" > bar
332	ln -s bar foo
333
334	atf_check cp -P foo baz
335	atf_check -o inline:'Symbolic Link\n' stat -f %SHT baz
336}
337
338atf_test_case symlink
339symlink_body()
340{
341	echo "foo" >foo
342	atf_check cp -s foo bar
343	atf_check -o inline:"foo\n" cat bar
344	atf_check -o inline:"foo\n" readlink bar
345}
346
347atf_test_case symlink_exists
348symlink_exists_body()
349{
350	echo "foo" >foo
351	echo "bar" >bar
352	atf_check -s not-exit:0 -e match:exists cp -s foo bar
353	atf_check -o inline:"bar\n" cat bar
354}
355
356atf_test_case symlink_exists_force
357symlink_exists_force_body()
358{
359	echo "foo" >foo
360	echo "bar" >bar
361	atf_check cp -fs foo bar
362	atf_check -o inline:"foo\n" cat bar
363	atf_check -o inline:"foo\n" readlink bar
364}
365
366atf_test_case directory_to_symlink
367directory_to_symlink_body()
368{
369	mkdir -p foo
370	ln -s .. foo/bar
371	mkdir bar
372	touch bar/baz
373	atf_check -s not-exit:0 -e match:"Not a directory" \
374	    cp -R bar foo
375	atf_check -s not-exit:0 -e match:"Not a directory" \
376	    cp -r bar foo
377}
378
379atf_test_case overwrite_directory
380overwrite_directory_body()
381{
382	mkdir -p foo/bar/baz
383	touch bar
384	atf_check -s not-exit:0 -e match:"Is a directory" \
385	    cp bar foo
386	rm bar
387	mkdir bar
388	touch bar/baz
389	atf_check -s not-exit:0 -e match:"Is a directory" \
390	    cp -R bar foo
391	atf_check -s not-exit:0 -e match:"Is a directory" \
392	    cp -r bar foo
393}
394
395atf_init_test_cases()
396{
397	atf_add_test_case basic
398	atf_add_test_case basic_symlink
399	atf_add_test_case chrdev
400	atf_add_test_case hardlink
401	atf_add_test_case hardlink_exists
402	atf_add_test_case hardlink_exists_force
403	atf_add_test_case matching_srctgt
404	atf_add_test_case matching_srctgt_contained
405	atf_add_test_case matching_srctgt_link
406	atf_add_test_case matching_srctgt_nonexistent
407	atf_add_test_case recursive_link_dflt
408	atf_add_test_case recursive_link_Hflag
409	atf_add_test_case recursive_link_Lflag
410	atf_add_test_case samefile
411	atf_add_test_case sparse_leading_hole
412	atf_add_test_case sparse_multiple_holes
413	atf_add_test_case sparse_only_hole
414	atf_add_test_case sparse_to_dev
415	atf_add_test_case sparse_trailing_hole
416	atf_add_test_case standalone_Pflag
417	atf_add_test_case symlink
418	atf_add_test_case symlink_exists
419	atf_add_test_case symlink_exists_force
420	atf_add_test_case directory_to_symlink
421	atf_add_test_case overwrite_directory
422}
423