xref: /freebsd/tests/sys/fs/tarfs/tarfs_test.sh (revision 83a1ee578c9d1ab7013e997289c7cd470c0e6902)
1#!/bin/sh
2#-
3# SPDX-License-Identifier: BSD-2-Clause
4#
5# Copyright (c) 2023-2024 Klara, Inc.
6#
7# Redistribution and use in source and binary forms, with or without
8# modification, are permitted provided that the following conditions
9# are met:
10# 1. Redistributions of source code must retain the above copyright
11#    notice, this list of conditions and the following disclaimer.
12# 2. Redistributions in binary form must reproduce the above copyright
13#    notice, this list of conditions and the following disclaimer in the
14#    documentation and/or other materials provided with the distribution.
15#
16# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26# SUCH DAMAGE.
27#
28
29mnt="$(realpath ${TMPDIR:-/tmp})/mnt"
30
31# expected SHA256 checksum of file contained in test tarball
32sum=4da2143234486307bb44eaa610375301781a577d1172f362b88bb4b1643dee62
33
34tar() {
35	if [ -n "${TARFS_USE_GNU_TAR}" ] ; then
36		gtar --posix --absolute-names "$@"
37	else
38		bsdtar "$@"
39	fi
40}
41
42mktar() {
43	"$(atf_get_srcdir)"/mktar ${TARFS_USE_GNU_TAR+-g} "$@"
44}
45
46tarsum() {
47	"$(atf_get_srcdir)"/tarsum
48}
49
50tarfs_setup() {
51	mkdir "${mnt}"
52}
53
54tarfs_cleanup() {
55	umount -f "${mnt}" 2>/dev/null || true
56}
57
58atf_test_case tarfs_basic cleanup
59tarfs_basic_head() {
60	atf_set "descr" "Basic function test"
61	atf_set "require.user" "root"
62	atf_set "require.kmods" "tarfs"
63}
64tarfs_basic_body() {
65	tarfs_setup
66	local tarball="${PWD}/tarfs_test.tar.zst"
67	mktar "${tarball}"
68	atf_check mount -rt tarfs "${tarball}" "${mnt}"
69	atf_check -o match:"^${tarball} on ${mnt} \(tarfs," mount
70	atf_check_equal "$(stat -f%d,%i "${mnt}"/sparse_file)" "$(stat -f%d,%i "${mnt}"/hard_link)"
71	atf_check_equal "$(stat -f%d,%i "${mnt}"/sparse_file)" "$(stat -L -f%d,%i "${mnt}"/short_link)"
72	atf_check_equal "$(stat -f%d,%i "${mnt}"/sparse_file)" "$(stat -L -f%d,%i "${mnt}"/long_link)"
73	atf_check -o inline:"${sum}\n" sha256 -q "${mnt}"/sparse_file
74	atf_check -o inline:"2,40755\n" stat -f%l,%p "${mnt}"/directory
75	atf_check -o inline:"1,100644\n" stat -f%l,%p "${mnt}"/file
76	atf_check -o inline:"2,100644\n" stat -f%l,%p "${mnt}"/hard_link
77	atf_check -o inline:"1,120755\n" stat -f%l,%p "${mnt}"/long_link
78	atf_check -o inline:"1,120755\n" stat -f%l,%p "${mnt}"/short_link
79	atf_check -o inline:"2,100644\n" stat -f%l,%p "${mnt}"/sparse_file
80	atf_check -o inline:"3,40755\n" stat -f%l,%p "${mnt}"
81}
82tarfs_basic_cleanup() {
83	tarfs_cleanup
84}
85
86atf_test_case tarfs_basic_gnu cleanup
87tarfs_basic_gnu_head() {
88	atf_set "descr" "Basic function test using GNU tar"
89	atf_set "require.user" "root"
90	atf_set "require.kmods" "tarfs"
91	atf_set "require.progs" "gtar"
92}
93tarfs_basic_gnu_body() {
94	TARFS_USE_GNU_TAR=true
95	tarfs_basic_body
96}
97tarfs_basic_gnu_cleanup() {
98	tarfs_basic_cleanup
99}
100
101atf_test_case tarfs_notdir_device cleanup
102tarfs_notdir_device_head() {
103	atf_set "descr" "Regression test for PR 269519 and 269561"
104	atf_set "require.user" "root"
105	atf_set "require.kmods" "tarfs"
106}
107tarfs_notdir_device_body() {
108	tarfs_setup
109	atf_check mknod d b 0xdead 0xbeef
110	tar -cf tarfs_notdir.tar d
111	rm d
112	mkdir d
113	echo "boom" >d/f
114	tar -rf tarfs_notdir.tar d/f
115	atf_check -s not-exit:0 -e match:"Invalid" \
116	    mount -rt tarfs tarfs_notdir.tar "${mnt}"
117}
118tarfs_notdir_device_cleanup() {
119	tarfs_cleanup
120}
121
122atf_test_case tarfs_notdir_device_gnu cleanup
123tarfs_notdir_device_gnu_head() {
124	atf_set "descr" "Regression test for PR 269519 and 269561 using GNU tar"
125	atf_set "require.user" "root"
126	atf_set "require.kmods" "tarfs"
127	atf_set "require.progs" "gtar"
128}
129tarfs_notdir_device_gnu_body() {
130	TARFS_USE_GNU_TAR=true
131	tarfs_notdir_device_body
132}
133tarfs_notdir_device_gnu_cleanup() {
134	tarfs_notdir_device_cleanup
135}
136
137atf_test_case tarfs_notdir_dot cleanup
138tarfs_notdir_dot_head() {
139	atf_set "descr" "Regression test for PR 269519 and 269561"
140	atf_set "require.user" "root"
141	atf_set "require.kmods" "tarfs"
142}
143tarfs_notdir_dot_body() {
144	tarfs_setup
145	echo "hello" >d
146	tar -cf tarfs_notdir.tar d
147	rm d
148	mkdir d
149	echo "world" >d/f
150	tar -rf tarfs_notdir.tar d/./f
151	atf_check -s not-exit:0 -e match:"Invalid" \
152	    mount -rt tarfs tarfs_notdir.tar "${mnt}"
153}
154tarfs_notdir_dot_cleanup() {
155	tarfs_cleanup
156}
157
158atf_test_case tarfs_notdir_dot_gnu cleanup
159tarfs_notdir_dot_gnu_head() {
160	atf_set "descr" "Regression test for PR 269519 and 269561 using GNU tar"
161	atf_set "require.user" "root"
162	atf_set "require.kmods" "tarfs"
163	atf_set "require.progs" "gtar"
164}
165tarfs_notdir_dot_gnu_body() {
166	TARFS_USE_GNU_TAR=true
167	tarfs_notdir_dot_body
168}
169tarfs_notdir_dot_gnu_cleanup() {
170	tarfs_notdir_dot_cleanup
171}
172
173atf_test_case tarfs_notdir_dotdot cleanup
174tarfs_notdir_dotdot_head() {
175	atf_set "descr" "Regression test for PR 269519 and 269561"
176	atf_set "require.user" "root"
177	atf_set "require.kmods" "tarfs"
178}
179tarfs_notdir_dotdot_body() {
180	tarfs_setup
181	echo "hello" >d
182	tar -cf tarfs_notdir.tar d
183	rm d
184	mkdir d
185	echo "world" >f
186	tar -rf tarfs_notdir.tar d/../f
187	atf_check -s not-exit:0 -e match:"Invalid" \
188	    mount -rt tarfs tarfs_notdir.tar "${mnt}"
189}
190tarfs_notdir_dotdot_cleanup() {
191	tarfs_cleanup
192}
193
194atf_test_case tarfs_notdir_dotdot_gnu cleanup
195tarfs_notdir_dotdot_gnu_head() {
196	atf_set "descr" "Regression test for PR 269519 and 269561 using GNU tar"
197	atf_set "require.user" "root"
198	atf_set "require.kmods" "tarfs"
199	atf_set "require.progs" "gtar"
200}
201tarfs_notdir_dotdot_gnu_body() {
202	TARFS_USE_GNU_TAR=true
203	tarfs_notdir_dotdot_body
204}
205tarfs_notdir_dotdot_gnu_cleanup() {
206	tarfs_notdir_dotdot_cleanup
207}
208
209atf_test_case tarfs_notdir_file cleanup
210tarfs_notdir_file_head() {
211	atf_set "descr" "Regression test for PR 269519 and 269561"
212	atf_set "require.user" "root"
213	atf_set "require.kmods" "tarfs"
214}
215tarfs_notdir_file_body() {
216	tarfs_setup
217	echo "hello" >d
218	tar -cf tarfs_notdir.tar d
219	rm d
220	mkdir d
221	echo "world" >d/f
222	tar -rf tarfs_notdir.tar d/f
223	atf_check -s not-exit:0 -e match:"Invalid" \
224	    mount -rt tarfs tarfs_notdir.tar "${mnt}"
225}
226tarfs_notdir_file_cleanup() {
227	tarfs_cleanup
228}
229
230atf_test_case tarfs_notdir_file_gnu cleanup
231tarfs_notdir_file_gnu_head() {
232	atf_set "descr" "Regression test for PR 269519 and 269561 using GNU tar"
233	atf_set "require.user" "root"
234	atf_set "require.kmods" "tarfs"
235	atf_set "require.progs" "gtar"
236}
237tarfs_notdir_file_gnu_body() {
238	TARFS_USE_GNU_TAR=true
239	tarfs_notdir_file_body
240}
241tarfs_notdir_file_gnu_cleanup() {
242	tarfs_notdir_file_cleanup
243}
244
245atf_test_case tarfs_emptylink cleanup
246tarfs_emptylink_head() {
247	atf_set "descr" "Regression test for PR 277360: empty link target"
248	atf_set "require.user" "root"
249	atf_set "require.kmods" "tarfs"
250}
251tarfs_emptylink_body() {
252	tarfs_setup
253	touch z
254	ln -f z hard
255	ln -fs z soft
256	tar -cf - z hard soft | dd bs=512 skip=1 | tr z '\0' | \
257		tarsum >> tarfs_emptylink.tar
258	atf_check -s not-exit:0 -e match:"Invalid" \
259		  mount -rt tarfs tarfs_emptylink.tar "${mnt}"
260}
261tarfs_emptylink_cleanup() {
262	tarfs_cleanup
263}
264
265atf_test_case tarfs_linktodir cleanup
266tarfs_linktodir_head() {
267	atf_set "descr" "Regression test for PR 277360: link to directory"
268	atf_set "require.user" "root"
269	atf_set "require.kmods" "tarfs"
270}
271tarfs_linktodir_body() {
272	tarfs_setup
273	mkdir d
274	tar -cf - d | dd bs=512 count=1 > tarfs_linktodir.tar
275	rmdir d
276	touch d
277	ln -f d link
278	tar -cf - d link | dd bs=512 skip=1 >> tarfs_linktodir.tar
279	atf_check -s not-exit:0 -e match:"Invalid" \
280		  mount -rt tarfs tarfs_linktodir.tar "${mnt}"
281}
282tarfs_linktodir_cleanup() {
283	tarfs_cleanup
284}
285
286atf_test_case tarfs_linktononexistent cleanup
287tarfs_linktononexistent_head() {
288	atf_set "descr" "Regression test for PR 277360: link to nonexistent target"
289	atf_set "require.user" "root"
290	atf_set "require.kmods" "tarfs"
291}
292tarfs_linktononexistent_body() {
293	tarfs_setup
294	touch f
295	ln -f f link
296	tar -cf - f link | dd bs=512 skip=1 >> tarfs_linktononexistent.tar
297	atf_check -s not-exit:0 -e match:"Invalid" \
298		  mount -rt tarfs tarfs_linktononexistent.tar "${mnt}"
299}
300tarfs_linktononexistent_cleanup() {
301	tarfs_cleanup
302}
303
304atf_test_case tarfs_checksum cleanup
305tarfs_checksum_head() {
306	atf_set "descr" "Verify that the checksum covers header padding"
307	atf_set "require.user" "root"
308	atf_set "require.kmods" "tarfs"
309}
310tarfs_checksum_body() {
311	tarfs_setup
312	touch f
313	tar -cf tarfs_checksum.tar f
314	truncate -s 500 tarfs_checksum.tar
315	printf "\1\1\1\1\1\1\1\1\1\1\1\1" >> tarfs_checksum.tar
316	dd if=/dev/zero bs=512 count=2 >> tarfs_checksum.tar
317	hexdump -C tarfs_checksum.tar
318	atf_check -s not-exit:0 -e match:"Invalid" \
319		  mount -rt tarfs tarfs_checksum.tar "${mnt}"
320}
321tarfs_checksum_cleanup() {
322	tarfs_cleanup
323}
324
325atf_test_case tarfs_long_names cleanup
326tarfs_long_names_head() {
327	atf_set "descr" "Verify that tarfs supports long file names"
328	atf_set "require.user" "root"
329	atf_set "require.kmods" "tarfs"
330}
331tarfs_long_names_body() {
332	tarfs_setup
333	local a b c d e
334	a="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
335	b="bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
336	c="cccccccccccccccccccccccccccccccccccccccc"
337	d="dddddddddddddddddddddddddddddddddddddddd"
338	e="eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"
339	mkdir -p "${a}"
340	touch "${a}/${b}_${c}_${d}_${e}_foo"
341	ln "${a}/${b}_${c}_${d}_${e}_foo" "${a}/${b}_${c}_${d}_${e}_bar"
342	ln -s "${b}_${c}_${d}_${e}_bar" "${a}/${b}_${c}_${d}_${e}_baz"
343	tar -cf tarfs_long_names.tar "${a}"
344	atf_check mount -rt tarfs tarfs_long_names.tar "${mnt}"
345}
346tarfs_long_names_cleanup() {
347	tarfs_cleanup
348}
349
350atf_test_case tarfs_long_paths cleanup
351tarfs_long_paths_head() {
352	atf_set "descr" "Verify that tarfs supports long paths"
353	atf_set "require.user" "root"
354	atf_set "require.kmods" "tarfs"
355}
356tarfs_long_paths_body() {
357	tarfs_setup
358	local a b c d e
359	a="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
360	b="bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
361	c="cccccccccccccccccccccccccccccccccccccccc"
362	d="dddddddddddddddddddddddddddddddddddddddd"
363	e="eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"
364	mkdir -p "${a}/${b}/${c}/${d}/${e}"
365	touch "${a}/${b}/${c}/${d}/${e}/foo"
366	ln "${a}/${b}/${c}/${d}/${e}/foo" "${a}/${b}/${c}/${d}/${e}/bar"
367	ln -s "${b}/${c}/${d}/${e}/bar" "${a}/baz"
368	tar -cf tarfs_long_paths.tar "${a}"
369	atf_check mount -rt tarfs tarfs_long_paths.tar "${mnt}"
370}
371tarfs_long_paths_cleanup() {
372	tarfs_cleanup
373}
374
375atf_test_case tarfs_git_archive cleanup
376tarfs_git_archive_head() {
377	atf_set "descr" "Verify that tarfs supports archives created by git"
378	atf_set "require.user" "root"
379	atf_set "require.kmods" "tarfs"
380	atf_set "require.progs" "git"
381}
382tarfs_git_archive_body() {
383	tarfs_setup
384	mkdir foo
385	echo "Hello, world!" >foo/bar
386	git -C foo init --initial-branch=tarfs
387	git -C foo config user.name "File System"
388	git -C foo config user.email fs@freebsd.org
389	git -C foo add bar
390	git -C foo commit -m bar
391	git -C foo archive --output=../tarfs_git_archive.tar HEAD
392	atf_check mount -rt tarfs tarfs_git_archive.tar "${mnt}"
393	atf_check -o file:foo/bar cat "${mnt}"/bar
394}
395tarfs_git_archive_cleanup() {
396	tarfs_cleanup
397}
398
399atf_init_test_cases() {
400	atf_add_test_case tarfs_basic
401	atf_add_test_case tarfs_basic_gnu
402	atf_add_test_case tarfs_notdir_device
403	atf_add_test_case tarfs_notdir_device_gnu
404	atf_add_test_case tarfs_notdir_dot
405	atf_add_test_case tarfs_notdir_dot_gnu
406	atf_add_test_case tarfs_notdir_dotdot
407	atf_add_test_case tarfs_notdir_dotdot_gnu
408	atf_add_test_case tarfs_notdir_file
409	atf_add_test_case tarfs_notdir_file_gnu
410	atf_add_test_case tarfs_emptylink
411	atf_add_test_case tarfs_linktodir
412	atf_add_test_case tarfs_linktononexistent
413	atf_add_test_case tarfs_checksum
414	atf_add_test_case tarfs_long_names
415	atf_add_test_case tarfs_long_paths
416	atf_add_test_case tarfs_git_archive
417}
418