Skip to content

Commit 23fbe5a

Browse files
daandemeyerkawasaki
andcommitted
loop: add regression test for partscan double-scan race
Add a stress test that detects spurious partition removal events when setting up a loop device with partscan enabled. The kernel bug was that disk_force_media_change() set GD_NEED_PART_SCAN, causing udev's device open to trigger a partition scan racing with the explicit scan from loop_reread_partitions(). The second scan would drop and re-add all partitions, making partition devices briefly disappear. The test monitors kernel uevents while repeatedly setting up and tearing down a loop device with partscan. Each cycle should produce exactly one add and one remove uevent for the partition device. Extra events indicate the double-scan race was triggered. Link: https://lore.kernel.org/linux-block/[email protected]/T/#u Signed-off-by: Daan De Meyer <[email protected]> Co-Authored-By: Shinichiro Kawasaki <[email protected]>
1 parent dd55eba commit 23fbe5a

3 files changed

Lines changed: 95 additions & 0 deletions

File tree

common/rc

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -603,6 +603,22 @@ _systemd_start_udevd() {
603603
return 0
604604
}
605605

606+
_have_systemd_ver() {
607+
local required_ver=${1}
608+
local ver
609+
610+
if ! command -v systemctl &>/dev/null; then
611+
SKIP_REASONS+=("systemd is not available")
612+
return 1
613+
fi
614+
615+
ver=$(systemctl --version | head -1 | sed 's/systemd //;s/[^0-9].*//')
616+
if ((ver < required_ver)); then
617+
SKIP_REASONS+=("systemd version is older than ${required_ver}")
618+
return 1
619+
fi
620+
}
621+
606622
# Run the given command as NORMAL_USER
607623
_run_user() {
608624
su "$NORMAL_USER" -c "$1"

tests/loop/012

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
#!/bin/bash
2+
# SPDX-License-Identifier: GPL-3.0+
3+
# Copyright (C) 2026 Daan De Meyer
4+
#
5+
# Regression test for a race between udev and loop_reread_partitions().
6+
#
7+
# When LOOP_CONFIGURE is called with LO_FLAGS_PARTSCAN,
8+
# disk_force_media_change() used to set GD_NEED_PART_SCAN before the
9+
# uevent was sent. When udev opened the device in response,
10+
# blkdev_get_whole() would trigger a partition scan, and then
11+
# loop_reread_partitions() would scan again. The second scan drops all
12+
# partitions from the first scan before re-adding them, causing
13+
# partition devices to briefly disappear.
14+
#
15+
# Verify that setting up a loop device with partscan does not produce
16+
# spurious partition add/remove events.
17+
18+
. tests/loop/rc
19+
20+
DESCRIPTION="check for spurious partition removal when partscan is enabled"
21+
TIMED=1
22+
23+
requires() {
24+
_have_program sfdisk
25+
_have_systemd_ver 259
26+
}
27+
28+
test() {
29+
echo "Running ${TEST_NAME}"
30+
31+
truncate -s 3MiB "$TMPDIR/img"
32+
sfdisk "$TMPDIR/img" >"$FULL" 2>&1 <<-EOF
33+
label: gpt
34+
size=1MiB
35+
EOF
36+
37+
local dev
38+
dev="$(losetup -f)"
39+
40+
# Monitor kernel uevents for partition block devices.
41+
udevadm monitor --kernel -s block/partition >"$TMPDIR/uevents" 2>&1 &
42+
local mon_pid=$!
43+
# Give the monitor time to set up its netlink socket.
44+
sleep 0.5
45+
46+
local iterations=0
47+
SECONDS=0
48+
while ((SECONDS < "${TIMEOUT:-5}")); do
49+
if ! losetup -P "$dev" "$TMPDIR/img" 2>>"$FULL"; then
50+
continue
51+
fi
52+
losetup -d "$dev" 2>>"$FULL"
53+
((iterations++))
54+
done
55+
56+
sleep 0.5
57+
kill "$mon_pid"
58+
wait "$mon_pid" 2>/dev/null
59+
60+
# Each setup+teardown cycle should produce exactly one add and one
61+
# remove kernel uevent for the partition device. If the race
62+
# triggers, a second partition scan produces an extra remove+add
63+
# pair, inflating the counts beyond the number of iterations.
64+
local name="${dev##*/}"
65+
local adds removes
66+
adds=$(grep -c "^KERNEL\[.*\] add.*${name}p" "$TMPDIR/uevents")
67+
removes=$(grep -c "^KERNEL\[.*\] remove.*${name}p" "$TMPDIR/uevents")
68+
69+
if ((adds > iterations)); then
70+
echo "Fail: $iterations iterations but $adds add events (expected $iterations)"
71+
fi
72+
if ((removes > iterations)); then
73+
echo "Fail: $iterations iterations but $removes remove events (expected $iterations)"
74+
fi
75+
76+
echo "Test complete"
77+
}

tests/loop/012.out

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Running loop/012
2+
Test complete

0 commit comments

Comments
 (0)