2 # SPDX-License-Identifier: LGPL-2.1-or-later
3 # vi: ts=4 sw=4 tw=0 et:
7 # * MD (mdadm) -> dm-crypt -> LVM
8 # * iSCSI -> dm-crypt -> LVM
11 TEST_DESCRIPTION
="systemd-udev storage tests"
13 # Save only journals of failing test cases by default (to conserve space)
14 TEST_SAVE_JOURNAL
="${TEST_SAVE_JOURNAL:-fail}"
16 # shellcheck source=test/test-functions
17 .
"${TEST_BASE_DIR:?}/test-functions"
19 USER_QEMU_OPTIONS
="${QEMU_OPTIONS:-}"
20 USER_KERNEL_APPEND
="${KERNEL_APPEND:-}"
22 _host_has_feature
() {(
30 # Client/initiator (Open-iSCSI)
31 command -v iscsiadm
&& command -v iscsid ||
return $?
33 command -v tgtadm
&& command -v tgtd ||
return $?
36 command -v lvm ||
return $?
42 command -v multipath
&& command -v multipathd ||
return $?
45 echo >&2 "ERROR: Unknown feature '$1'"
46 # Make this a hard error to distinguish an invalid feature from
52 test_append_files
() {(
54 # An associative array of requested (but optional) features and their
55 # respective "handlers" from test/test-functions
57 # Note: we install cryptsetup unconditionally, hence it's not explicitly
64 [multipath
]=install_multipath
67 instmods
"=block" "=md" "=nvme" "=scsi"
69 image_install lsblk swapoff swapon
wc wipefs
71 # Install the optional features if the host has the respective tooling
72 for feature
in "${!features[@]}"; do
73 if _host_has_feature
"$feature"; then
74 "${features[$feature]}"
78 generate_module_dependencies
81 dd if=/dev
/zero of
="${TESTDIR:?}/disk$i.img" bs
=1M count
=1
82 echo "device$i" >"${TESTDIR:?}/disk$i.img"
88 # Clean up certain "problematic" files which may be left over by failing tests
89 : >"${initdir:?}/etc/fstab"
90 : >"${initdir:?}/etc/crypttab"
91 # Clear previous assignment
96 local test_id
="${1:?}"
98 if run_qemu
"$test_id"; then
99 check_result_qemu ||
{ echo "qemu test failed"; return 1; }
106 local test_id
="${1:?}"
114 if get_bool
"${TEST_NO_QEMU:=}" ||
! find_qemu_bin
; then
115 dwarn
"can't run qemu, skipping"
119 # Execute each currently defined function starting with "testcase_"
120 for testcase
in "${TESTCASES[@]}"; do
122 echo "------ $testcase: BEGIN ------"
123 # Note for my future frustrated self: `fun && xxx` (as well as ||, if, while,
124 # until, etc.) _DISABLES_ the `set -e` behavior in _ALL_ nested function
125 # calls made from `fun()`, i.e. the function _CONTINUES_ even when a called
126 # command returned non-zero EC. That may unexpectedly hide failing commands
127 # if not handled properly. See: bash(1) man page, `set -e` section.
129 # So, be careful when adding clean up snippets in the testcase_*() functions -
130 # if the `test_run_one()` function isn't the last command, you have propagate
131 # the exit code correctly (e.g. `test_run_one() || return $?`, see below).
133 "$testcase" "$test_id" || ec
=$?
136 passed
+=("$testcase")
140 skipped
+=("$testcase")
144 failed
+=("$testcase")
147 echo "------ $testcase: END ($state) ------"
150 echo "Passed tests: ${#passed[@]}"
151 printf " * %s\n" "${passed[@]}"
152 echo "Skipped tests: ${#skipped[@]}"
153 printf " * %s\n" "${skipped[@]}"
154 echo "Failed tests: ${#failed[@]}"
155 printf " * %s\n" "${failed[@]}"
157 [[ ${#failed[@]} -eq 0 ]] ||
return 1
162 testcase_megasas2_basic
() {
163 if ! "${QEMU_BIN:?}" -device help |
grep 'name "megasas-gen2"'; then
164 echo "megasas-gen2 device driver is not available, skipping test..."
170 "-device megasas-gen2,id=scsi0"
171 "-device megasas-gen2,id=scsi1"
172 "-device megasas-gen2,id=scsi2"
173 "-device megasas-gen2,id=scsi3"
176 for i
in {0.
.127}; do
177 # Add 128 drives, 32 per bus
179 "-device scsi-hd,drive=drive$i,bus=scsi$((i / 32)).0,channel=0,scsi-id=$((i % 32)),lun=0"
180 "-drive format=raw,cache=unsafe,file=${TESTDIR:?}/disk$i.img,if=none,id=drive$i"
184 KERNEL_APPEND
="systemd.setenv=TEST_FUNCTION_NAME=${FUNCNAME[0]} ${USER_KERNEL_APPEND:-}"
185 QEMU_OPTIONS
="${qemu_opts[*]} ${USER_QEMU_OPTIONS:-}"
186 test_run_one
"${1:?}"
189 testcase_nvme_basic
() {
190 if ! "${QEMU_BIN:?}" -device help |
grep 'name "nvme"'; then
191 echo "nvme device driver is not available, skipping test..."
198 for (( i
= 0; i
< 5; i
++ )); do
200 "-device" "nvme,drive=nvme$i,serial=deadbeef$i,num_queues=8"
201 "-drive" "format=raw,cache=unsafe,file=${TESTDIR:?}/disk$i.img,if=none,id=nvme$i"
204 for (( i
= 5; i
< 10; i
++ )); do
206 "-device" "nvme,drive=nvme$i,serial= deadbeef $i ,num_queues=8"
207 "-drive" "format=raw,cache=unsafe,file=${TESTDIR:?}/disk$i.img,if=none,id=nvme$i"
210 for (( i
= 10; i
< 15; i
++ )); do
212 "-device" "nvme,drive=nvme$i,serial= dead/beef/$i ,num_queues=8"
213 "-drive" "format=raw,cache=unsafe,file=${TESTDIR:?}/disk$i.img,if=none,id=nvme$i"
216 for (( i
= 15; i
< 20; i
++ )); do
218 "-device" "nvme,drive=nvme$i,serial=dead/../../beef/$i,num_queues=8"
219 "-drive" "format=raw,cache=unsafe,file=${TESTDIR:?}/disk$i.img,if=none,id=nvme$i"
223 KERNEL_APPEND
="systemd.setenv=TEST_FUNCTION_NAME=${FUNCNAME[0]} ${USER_KERNEL_APPEND:-}"
224 QEMU_OPTIONS
="${USER_QEMU_OPTIONS}"
225 QEMU_OPTIONS_ARRAY
=("${qemu_opts[@]}")
226 test_run_one
"${1:?}"
230 # * https://github.com/systemd/systemd/pull/24748
231 # * https://github.com/systemd/systemd/pull/24766
232 # * https://github.com/systemd/systemd/pull/24946
233 # Docs: https://qemu.readthedocs.io/en/latest/system/devices/nvme.html#nvm-subsystems
234 testcase_nvme_subsystem
() {
235 if ! "${QEMU_BIN:?}" -device help |
grep 'name "nvme-subsys"'; then
236 echo "nvme-subsystem device driver is not available, skipping test..."
242 # Create an NVM Subsystem Device
243 "-device nvme-subsys,id=nvme-subsys-64,nqn=subsys64"
244 # Attach two NVM controllers to it
245 "-device nvme,subsys=nvme-subsys-64,serial=deadbeef"
246 "-device nvme,subsys=nvme-subsys-64,serial=deadbeef"
247 # And create two shared namespaces attached to both controllers
248 "-device nvme-ns,drive=nvme0,nsid=16,shared=on"
249 "-drive format=raw,cache=unsafe,file=${TESTDIR:?}/disk0.img,if=none,id=nvme0"
250 "-device nvme-ns,drive=nvme1,nsid=17,shared=on"
251 "-drive format=raw,cache=unsafe,file=${TESTDIR:?}/disk1.img,if=none,id=nvme1"
254 KERNEL_APPEND
="systemd.setenv=TEST_FUNCTION_NAME=${FUNCNAME[0]} ${USER_KERNEL_APPEND:-}"
255 QEMU_OPTIONS
="${qemu_opts[*]} ${USER_QEMU_OPTIONS:-}"
256 test_run_one
"${1:?}"
259 # Test for issue https://github.com/systemd/systemd/issues/20212
260 testcase_virtio_scsi_identically_named_partitions
() {
262 if ! "${QEMU_BIN:?}" -device help |
grep 'name "virtio-scsi-pci"'; then
263 echo "virtio-scsi-pci device driver is not available, skipping test..."
267 # Create 16 disks, with 8 partitions per disk (all identically named)
268 # and attach them to a virtio-scsi controller
269 local qemu_opts
=("-device virtio-scsi-pci,id=scsi0,num_queues=4")
270 local diskpath
="${TESTDIR:?}/namedpart0.img"
271 local i num_disk qemu_timeout
273 if get_bool
"${IS_BUILT_WITH_ASAN:=}" ||
! get_bool
"$QEMU_KVM"; then
279 dd if=/dev
/zero of
="$diskpath" bs
=1M count
=18
281 for ((i
= 0; i
< num_disk
; i
++)); do
282 diskpath
="${TESTDIR:?}/namedpart$i.img"
283 if [[ $i -gt 0 ]]; then
284 cp -uv "${TESTDIR:?}/namedpart0.img" "$diskpath"
288 "-device scsi-hd,drive=drive$i,bus=scsi0.0,channel=0,scsi-id=0,lun=$i"
289 "-drive format=raw,cache=unsafe,file=$diskpath,if=none,id=drive$i"
293 # Bump the timeout when collecting test coverage, since the test is a bit
294 # slower in that case
295 if get_bool
"${IS_BUILT_WITH_ASAN:=}" ||
! get_bool
"$QEMU_KVM"; then
297 elif is_built_with_coverage
; then
303 KERNEL_APPEND
="systemd.setenv=TEST_FUNCTION_NAME=${FUNCNAME[0]} ${USER_KERNEL_APPEND:-}"
304 # Limit the number of VCPUs and set a timeout to make sure we trigger the issue
305 QEMU_OPTIONS
="${qemu_opts[*]} ${USER_QEMU_OPTIONS:-}"
306 QEMU_SMP
=1 QEMU_TIMEOUT
=$qemu_timeout test_run_one
"${1:?}" ||
return $?
308 rm -f "${TESTDIR:?}"/namedpart
*.img
311 testcase_multipath_basic_failover
() {
312 if ! _host_has_feature
"multipath"; then
313 echo "Missing multipath tools, skipping the test..."
317 local qemu_opts
=("-device virtio-scsi-pci,id=scsi")
318 local partdisk
="${TESTDIR:?}/multipathpartitioned.img"
319 local image nback ndisk wwn
321 dd if=/dev
/zero of
="$partdisk" bs
=1M count
=16
323 # Add 16 multipath devices, each backed by 4 paths
324 for ndisk
in {0.
.15}; do
325 wwn
="0xDEADDEADBEEF$(printf "%.4d
" "$ndisk")"
326 # Use a partitioned disk for the first device to test failover
327 [[ $ndisk -eq 0 ]] && image
="$partdisk" || image
="${TESTDIR:?}/disk$ndisk.img"
329 for nback
in {0.
.3}; do
331 "-device scsi-hd,drive=drive${ndisk}x${nback},serial=MPIO$ndisk,wwn=$wwn"
332 "-drive format=raw,cache=unsafe,file=$image,file.locking=off,if=none,id=drive${ndisk}x${nback}"
337 KERNEL_APPEND
="systemd.setenv=TEST_FUNCTION_NAME=${FUNCNAME[0]} ${USER_KERNEL_APPEND:-}"
338 QEMU_OPTIONS
="${qemu_opts[*]} ${USER_QEMU_OPTIONS:-}"
339 test_run_one
"${1:?}" ||
return $?
344 # Test case for issue https://github.com/systemd/systemd/issues/19946
345 testcase_simultaneous_events
() {
346 local qemu_opts
=("-device virtio-scsi-pci,id=scsi")
350 diskpath
="${TESTDIR:?}/simultaneousevents${i}.img"
352 dd if=/dev
/zero of
="$diskpath" bs
=1M count
=128
354 "-device scsi-hd,drive=drive$i,serial=deadbeeftest$i"
355 "-drive format=raw,cache=unsafe,file=$diskpath,if=none,id=drive$i"
359 KERNEL_APPEND
="systemd.setenv=TEST_FUNCTION_NAME=${FUNCNAME[0]} ${USER_KERNEL_APPEND:-}"
360 QEMU_OPTIONS
="${qemu_opts[*]} ${USER_QEMU_OPTIONS:-}"
361 test_run_one
"${1:?}" ||
return $?
366 testcase_lvm_basic
() {
367 if ! _host_has_feature
"lvm"; then
368 echo "Missing lvm tools, skipping the test..."
372 local qemu_opts
=("-device ahci,id=ahci0")
375 # Attach 4 SATA disks to the VM (and set their model and serial fields
376 # to something predictable, so we can refer to them later)
378 diskpath
="${TESTDIR:?}/lvmbasic${i}.img"
379 dd if=/dev
/zero of
="$diskpath" bs
=1M count
=32
381 "-device ide-hd,bus=ahci0.$i,drive=drive$i,model=foobar,serial=deadbeeflvm$i"
382 "-drive format=raw,cache=unsafe,file=$diskpath,if=none,id=drive$i"
386 KERNEL_APPEND
="systemd.setenv=TEST_FUNCTION_NAME=${FUNCNAME[0]} ${USER_KERNEL_APPEND:-}"
387 QEMU_OPTIONS
="${qemu_opts[*]} ${USER_QEMU_OPTIONS:-}"
388 test_run_one
"${1:?}" ||
return $?
390 rm -f "${TESTDIR:?}"/lvmbasic
*.img
393 testcase_btrfs_basic
() {
394 if ! _host_has_feature
"btrfs"; then
395 echo "Missing btrfs tools/modules, skipping the test..."
399 local qemu_opts
=("-device ahci,id=ahci0")
400 local diskpath i size
403 diskpath
="${TESTDIR:?}/btrfsbasic${i}.img"
404 # Make the first disk larger for multi-partition tests
405 [[ $i -eq 0 ]] && size
=350 || size
=128
407 dd if=/dev
/zero of
="$diskpath" bs
=1M count
="$size"
409 "-device ide-hd,bus=ahci0.$i,drive=drive$i,model=foobar,serial=deadbeefbtrfs$i"
410 "-drive format=raw,cache=unsafe,file=$diskpath,if=none,id=drive$i"
414 KERNEL_APPEND
="systemd.setenv=TEST_FUNCTION_NAME=${FUNCNAME[0]} ${USER_KERNEL_APPEND:-}"
415 QEMU_OPTIONS
="${qemu_opts[*]} ${USER_QEMU_OPTIONS:-}"
416 test_run_one
"${1:?}" ||
return $?
418 rm -f "${TESTDIR:?}"/btrfsbasic
*.img
421 testcase_iscsi_lvm
() {
422 if ! _host_has_feature
"iscsi" ||
! _host_has_feature
"lvm"; then
423 echo "Missing iSCSI client/server tools (Open-iSCSI/TGT) or LVM utilities, skipping the test..."
427 local qemu_opts
=("-device ahci,id=ahci0")
428 local diskpath i size
431 diskpath
="${TESTDIR:?}/iscsibasic${i}.img"
432 # Make the first disk larger for multi-partition tests
433 [[ $i -eq 0 ]] && size
=150 || size
=64
434 # Make the first disk larger for multi-partition tests
436 dd if=/dev
/zero of
="$diskpath" bs
=1M count
="$size"
438 "-device ide-hd,bus=ahci0.$i,drive=drive$i,model=foobar,serial=deadbeefiscsi$i"
439 "-drive format=raw,cache=unsafe,file=$diskpath,if=none,id=drive$i"
443 KERNEL_APPEND
="systemd.setenv=TEST_FUNCTION_NAME=${FUNCNAME[0]} ${USER_KERNEL_APPEND:-}"
444 QEMU_OPTIONS
="${qemu_opts[*]} ${USER_QEMU_OPTIONS:-}"
445 test_run_one
"${1:?}" ||
return $?
447 rm -f "${TESTDIR:?}"/iscsibasic
*.img
450 testcase_long_sysfs_path
() {
452 local testdisk
="${TESTDIR:?}/longsysfspath.img"
454 "-drive if=none,id=drive0,format=raw,cache=unsafe,file=$testdisk"
455 "-device pci-bridge,id=pci_bridge0,chassis_nr=64"
458 dd if=/dev
/zero of
="$testdisk" bs
=1M count
=64
459 # Create 25 additional PCI bridges, each one connected to the previous one
460 # (basically a really long extension cable), and attach a virtio drive to
461 # the last one. This should force udev into attempting to create a device
462 # unit with a _really_ long name.
463 for brid
in {1.
.25}; do
464 qemu_opts
+=("-device pci-bridge,id=pci_bridge$brid,bus=pci_bridge$((brid-1)),chassis_nr=$((64+brid))")
467 qemu_opts
+=("-device virtio-blk-pci,drive=drive0,scsi=off,bus=pci_bridge$brid")
469 KERNEL_APPEND
="systemd.setenv=TEST_FUNCTION_NAME=${FUNCNAME[0]} ${USER_KERNEL_APPEND:-}"
470 QEMU_OPTIONS
="${qemu_opts[*]} ${USER_QEMU_OPTIONS:-}"
471 test_run_one
"${1:?}" ||
return $?
473 rm -f "${testdisk:?}"
476 testcase_mdadm_basic
() {
477 if ! _host_has_feature
"mdadm"; then
478 echo "Missing mdadm tools/modules, skipping the test..."
482 local qemu_opts
=("-device ahci,id=ahci0")
483 local diskpath i size
486 diskpath
="${TESTDIR:?}/mdadmbasic${i}.img"
488 dd if=/dev
/zero of
="$diskpath" bs
=1M count
=64
490 "-device ide-hd,bus=ahci0.$i,drive=drive$i,model=foobar,serial=deadbeefmdadm$i"
491 "-drive format=raw,cache=unsafe,file=$diskpath,if=none,id=drive$i"
495 KERNEL_APPEND
="systemd.setenv=TEST_FUNCTION_NAME=${FUNCNAME[0]} ${USER_KERNEL_APPEND:-}"
496 QEMU_OPTIONS
="${qemu_opts[*]} ${USER_QEMU_OPTIONS:-}"
497 test_run_one
"${1:?}" ||
return $?
499 rm -f "${TESTDIR:?}"/mdadmbasic
*.img
502 testcase_mdadm_lvm
() {
503 if ! _host_has_feature
"mdadm" ||
! _host_has_feature
"lvm"; then
504 echo "Missing mdadm tools/modules or LVM tools, skipping the test..."
508 local qemu_opts
=("-device ahci,id=ahci0")
509 local diskpath i size
512 diskpath
="${TESTDIR:?}/mdadmlvm${i}.img"
514 dd if=/dev
/zero of
="$diskpath" bs
=1M count
=64
516 "-device ide-hd,bus=ahci0.$i,drive=drive$i,model=foobar,serial=deadbeefmdadmlvm$i"
517 "-drive format=raw,cache=unsafe,file=$diskpath,if=none,id=drive$i"
521 KERNEL_APPEND
="systemd.setenv=TEST_FUNCTION_NAME=${FUNCNAME[0]} ${USER_KERNEL_APPEND:-}"
522 QEMU_OPTIONS
="${qemu_opts[*]} ${USER_QEMU_OPTIONS:-}"
523 test_run_one
"${1:?}" ||
return $?
525 rm -f "${TESTDIR:?}"/mdadmlvm
*.img
527 # Allow overriding which tests should be run from the "outside", useful for manual
528 # testing (make -C test/... TESTCASES="testcase1 testcase2")
529 if [[ -v "TESTCASES" && -n "$TESTCASES" ]]; then
530 read -ra TESTCASES
<<< "$TESTCASES"
532 # This must run after all functions were defined, otherwise `declare -F` won't
534 mapfile
-t TESTCASES
< <(declare -F |
awk '$3 ~ /^testcase_/ {print $3;}')