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