xref: /freebsd/bin/cp/tests/cp_test.sh (revision 069ac18495ad8fde2748bc94b0f80a50250bb01d)
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_init_test_cases()
367{
368	atf_add_test_case basic
369	atf_add_test_case basic_symlink
370	atf_add_test_case chrdev
371	atf_add_test_case hardlink
372	atf_add_test_case hardlink_exists
373	atf_add_test_case hardlink_exists_force
374	atf_add_test_case matching_srctgt
375	atf_add_test_case matching_srctgt_contained
376	atf_add_test_case matching_srctgt_link
377	atf_add_test_case matching_srctgt_nonexistent
378	atf_add_test_case recursive_link_dflt
379	atf_add_test_case recursive_link_Hflag
380	atf_add_test_case recursive_link_Lflag
381	atf_add_test_case samefile
382	atf_add_test_case sparse_leading_hole
383	atf_add_test_case sparse_multiple_holes
384	atf_add_test_case sparse_only_hole
385	atf_add_test_case sparse_to_dev
386	atf_add_test_case sparse_trailing_hole
387	atf_add_test_case standalone_Pflag
388	atf_add_test_case symlink
389	atf_add_test_case symlink_exists
390	atf_add_test_case symlink_exists_force
391}
392