xref: /freebsd/usr.bin/stat/tests/mountpoint_test.sh (revision c4fde87a3236b500246d4fce76dbca2a3d64f73a)
1#
2# SPDX-License-Identifier: BSD-2-Clause
3#
4# Copyright (c) 2026 Maksym Sobolev <sobomax@FreeBSD.org>
5#
6
7# Mount a file system and record its mount point for mp_cleanup.
8mp_mount()
9{
10	local mntpt
11
12	for mntpt; do :; done
13	[ -e "$mntpt" ] || atf_check mkdir -p "$mntpt"
14	echo "$PWD/$mntpt" >> mounts
15	atf_check mount "$@"
16}
17
18mp_cleanup()
19{
20	[ -f mounts ] || return 0
21	tail -r mounts | while read mntpt; do
22		umount -f "$mntpt" 2>/dev/null
23	done
24	return 0
25}
26
27# Populate a minimal root with sh, mountpoint and the given programs,
28# together with the shared libraries they need.
29mp_mkroot()
30{
31	local root lib prog
32
33	root=$1
34	shift
35	atf_check mkdir -p "$root/bin" "$root/libexec" "$root/mnt"
36	atf_check cp /libexec/ld-elf.so.1 "$root/libexec/"
37	for prog in sh mountpoint "$@"; do
38		prog=$(command -v "$prog") || atf_fail "$prog not found"
39		atf_check cp "$prog" "$root/bin/"
40		for lib in $(ldd -f '%p\n' "$prog" | grep '^/'); do
41			atf_check mkdir -p "$root${lib%/*}"
42			atf_check cp "$lib" "$root$lib"
43		done
44	done
45}
46
47mp_require_jail()
48{
49	if [ "$(sysctl -n security.jail.jailed)" != 0 ]; then
50		atf_skip "Cannot create jails from within a jail"
51	fi
52}
53
54# Run "mountpoint path" inside root, entered via chroot(8) or jail(8)
55# as selected by mode, and check that it reports rc.  jail(8) does not
56# pass the exit status of the command through, so it is echoed back.
57mp_check()
58{
59	local mode root path rc not
60
61	mode=$1
62	root=$2
63	path=$3
64	rc=$4
65	not=
66	[ "$rc" -eq 0 ] || not="not "
67	case "$mode" in
68	chroot)
69		set -- chroot "$root" /bin/sh
70		;;
71	jail)
72		set -- jail -c path="$PWD/$root" command=/bin/sh
73		;;
74	esac
75	atf_check -o inline:"$path is ${not}a mount point\nrc=$rc\n" \
76	    "$@" -c "/bin/mountpoint $path; echo rc=\$?"
77}
78
79atf_test_case not_mountpoint
80not_mountpoint_head()
81{
82	atf_set	"descr" "Verify that a plain directory or file is not " \
83			"reported as a mount point"
84}
85
86not_mountpoint_body()
87{
88	atf_check mkdir dir
89	atf_check -s exit:1 -o inline:"dir is not a mount point\n" \
90	    mountpoint dir
91	atf_check -s exit:1 mountpoint -q dir
92	atf_check touch file
93	atf_check -s exit:1 -o inline:"file is not a mount point\n" \
94	    mountpoint file
95}
96
97atf_test_case errors
98errors_head()
99{
100	atf_set	"descr" "Verify that errors are reported with exit status 2"
101}
102
103errors_body()
104{
105	atf_check -s exit:2 -e match:"No such file" mountpoint nonexistent
106	atf_check -s exit:2 -e match:"usage" mountpoint
107	atf_check -s exit:2 -e match:"usage" mountpoint a b
108	atf_check -s exit:2 -e match:"usage" mountpoint -x .
109}
110
111atf_test_case mountpoint cleanup
112mountpoint_head()
113{
114	atf_set	"descr" "Verify that a mounted file system root is " \
115			"reported as a mount point, also through a symlink"
116	atf_set	"require.user" "root"
117}
118
119mountpoint_body()
120{
121	mp_mount -t tmpfs tmpfs mnt
122	atf_check -o inline:"mnt is a mount point\n" mountpoint mnt
123	atf_check -o empty mountpoint -q mnt
124	atf_check ln -s mnt link
125	atf_check -o inline:"link is a mount point\n" mountpoint link
126	atf_check mkdir mnt/sub
127	atf_check -s exit:1 -o inline:"mnt/sub is not a mount point\n" \
128	    mountpoint mnt/sub
129}
130
131mountpoint_cleanup()
132{
133	mp_cleanup
134}
135
136atf_test_case file_mountpoint cleanup
137file_mountpoint_head()
138{
139	atf_set	"descr" "Verify that a regular file with a file system " \
140			"mounted over it is reported as a mount point"
141	atf_set	"require.user" "root"
142	atf_set	"require.kmods" "nullfs"
143}
144
145file_mountpoint_body()
146{
147	atf_check touch src dst
148	mp_mount -t nullfs "$PWD/src" dst
149	atf_check -o inline:"dst is a mount point\n" mountpoint dst
150	atf_check ln -s dst link
151	atf_check -o inline:"link is a mount point\n" mountpoint link
152	atf_check -s exit:1 -o inline:"src is not a mount point\n" \
153	    mountpoint src
154}
155
156file_mountpoint_cleanup()
157{
158	mp_cleanup
159}
160
161# The root is a plain directory on top of a mount point.  Neither the
162# root nor its ".." may be reported as a mount point, so the mount point
163# above the root does not leak in.
164root_not_mountpoint_body()
165{
166	mp_mount -t tmpfs tmpfs outer
167	mp_mkroot outer/root
168	mp_check $1 outer/root / 1
169	mp_check $1 outer/root /.. 1
170	mp_check $1 outer/root /bin 1
171}
172
173# The root is itself a mount point.
174root_mountpoint_body()
175{
176	mp_mount -t tmpfs tmpfs root
177	mp_mkroot root
178	mp_check $1 root / 0
179	mp_check $1 root /.. 0
180	mp_check $1 root /bin 1
181}
182
183# The root is a nullfs mount, as is common for jails.
184root_nullfs_body()
185{
186	mp_mkroot src
187	mp_mount -t nullfs "$PWD/src" root
188	mp_check $1 root / 0
189	mp_check $1 root /bin 1
190}
191
192# The root is a plain directory with a file system mounted inside it.
193submount_body()
194{
195	mp_mkroot root
196	mp_mount -t tmpfs tmpfs root/mnt
197	mp_check $1 root / 1
198	mp_check $1 root /mnt 0
199	mp_check $1 root /mnt/.. 1
200}
201
202atf_test_case chroot_root_not_mountpoint cleanup
203chroot_root_not_mountpoint_head()
204{
205	atf_set	"descr" "Verify that a chroot root which is not a mount " \
206			"point is not reported as one"
207	atf_set	"require.user" "root"
208}
209
210chroot_root_not_mountpoint_body()
211{
212	root_not_mountpoint_body chroot
213}
214
215chroot_root_not_mountpoint_cleanup()
216{
217	mp_cleanup
218}
219
220atf_test_case chroot_root_mountpoint cleanup
221chroot_root_mountpoint_head()
222{
223	atf_set	"descr" "Verify that a chroot root which is a mount point " \
224			"is reported as one"
225	atf_set	"require.user" "root"
226}
227
228chroot_root_mountpoint_body()
229{
230	root_mountpoint_body chroot
231}
232
233chroot_root_mountpoint_cleanup()
234{
235	mp_cleanup
236}
237
238atf_test_case chroot_root_nullfs cleanup
239chroot_root_nullfs_head()
240{
241	atf_set	"descr" "Verify that a chroot root which is a nullfs " \
242			"mount is reported as a mount point"
243	atf_set	"require.user" "root"
244	atf_set	"require.kmods" "nullfs"
245}
246
247chroot_root_nullfs_body()
248{
249	root_nullfs_body chroot
250}
251
252chroot_root_nullfs_cleanup()
253{
254	mp_cleanup
255}
256
257atf_test_case chroot_submount cleanup
258chroot_submount_head()
259{
260	atf_set	"descr" "Verify that a file system mounted inside a chroot " \
261			"is reported as a mount point"
262	atf_set	"require.user" "root"
263}
264
265chroot_submount_body()
266{
267	submount_body chroot
268}
269
270chroot_submount_cleanup()
271{
272	mp_cleanup
273}
274
275atf_test_case jail_root_not_mountpoint cleanup
276jail_root_not_mountpoint_head()
277{
278	atf_set	"descr" "Verify that a jail root which is not a mount " \
279			"point is not reported as one"
280	atf_set	"require.user" "root"
281	atf_set	"require.progs" "jail"
282}
283
284jail_root_not_mountpoint_body()
285{
286	mp_require_jail
287	root_not_mountpoint_body jail
288}
289
290jail_root_not_mountpoint_cleanup()
291{
292	mp_cleanup
293}
294
295atf_test_case jail_root_mountpoint cleanup
296jail_root_mountpoint_head()
297{
298	atf_set	"descr" "Verify that a jail root which is a mount point " \
299			"is reported as one"
300	atf_set	"require.user" "root"
301	atf_set	"require.progs" "jail"
302}
303
304jail_root_mountpoint_body()
305{
306	mp_require_jail
307	root_mountpoint_body jail
308}
309
310jail_root_mountpoint_cleanup()
311{
312	mp_cleanup
313}
314
315atf_test_case jail_root_nullfs cleanup
316jail_root_nullfs_head()
317{
318	atf_set	"descr" "Verify that a jail root which is a nullfs mount " \
319			"is reported as a mount point"
320	atf_set	"require.user" "root"
321	atf_set	"require.progs" "jail"
322	atf_set	"require.kmods" "nullfs"
323}
324
325jail_root_nullfs_body()
326{
327	mp_require_jail
328	root_nullfs_body jail
329}
330
331jail_root_nullfs_cleanup()
332{
333	mp_cleanup
334}
335
336atf_test_case jail_submount cleanup
337jail_submount_head()
338{
339	atf_set	"descr" "Verify that a file system mounted inside a jail " \
340			"from the host is reported as a mount point"
341	atf_set	"require.user" "root"
342	atf_set	"require.progs" "jail"
343}
344
345jail_submount_body()
346{
347	mp_require_jail
348	submount_body jail
349}
350
351jail_submount_cleanup()
352{
353	mp_cleanup
354}
355
356atf_test_case jail_mount_inside cleanup
357jail_mount_inside_head()
358{
359	atf_set	"descr" "Verify that a file system mounted from within " \
360			"a jail is reported as a mount point there"
361	atf_set	"require.user" "root"
362	atf_set	"require.progs" "jail"
363}
364
365jail_mount_inside_body()
366{
367	local out
368
369	mp_require_jail
370	mp_mkroot root mount umount
371	echo "$PWD/root/mnt" >> mounts
372	out="/mnt is a mount point\nrc=0\n/ is not a mount point\nrc=1\n"
373	atf_check -o inline:"$out" jail -c path="$PWD/root" \
374	    allow.mount allow.mount.tmpfs enforce_statfs=1 \
375	    command=/bin/sh -c "/bin/mount -t tmpfs tmpfs /mnt &&
376		/bin/mountpoint /mnt; echo rc=\$?;
377		/bin/mountpoint /; echo rc=\$?;
378		/bin/umount /mnt"
379}
380
381jail_mount_inside_cleanup()
382{
383	mp_cleanup
384}
385
386atf_init_test_cases()
387{
388	atf_add_test_case not_mountpoint
389	atf_add_test_case errors
390	atf_add_test_case mountpoint
391	atf_add_test_case file_mountpoint
392	atf_add_test_case chroot_root_not_mountpoint
393	atf_add_test_case chroot_root_mountpoint
394	atf_add_test_case chroot_root_nullfs
395	atf_add_test_case chroot_submount
396	atf_add_test_case jail_root_not_mountpoint
397	atf_add_test_case jail_root_mountpoint
398	atf_add_test_case jail_root_nullfs
399	atf_add_test_case jail_submount
400	atf_add_test_case jail_mount_inside
401}
402