From 86ff48bcaa2824e887d010934634d1a423de4508 Mon Sep 17 00:00:00 2001 From: Benjamin Marzinski Date: Wed, 28 Jan 2026 16:46:51 -0500 Subject: [PATCH 01/23] multipathd: fix possible null deference in purge_disconnected code purge_disconnected only works on paths that are part of a multipath device. However, get_new_state() is also run on uninitialized paths that are not part of any multipath device, and the purge_disconnected code in it was dereferencing pp->mpp, without checking if it was NULL. Fix that. Fixes: 690550a1 ("multipathd: implement purge functionality for disconnected paths") Signed-off-by: Benjamin Marzinski Reviewed-by: Martin Wilck --- multipathd/main.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/multipathd/main.c b/multipathd/main.c index 05dd65e6a..d87c7b127 100644 --- a/multipathd/main.c +++ b/multipathd/main.c @@ -2518,7 +2518,8 @@ get_new_state(struct path *pp) * checker to trigger special handling before becoming PATH_DOWN. */ if (newstate == PATH_DISCONNECTED) { - if (pp->mpp->purge_disconnected == PURGE_DISCONNECTED_ON && + if (pp->mpp && + pp->mpp->purge_disconnected == PURGE_DISCONNECTED_ON && pp->disconnected == NOT_DISCONNECTED) { condlog(2, "%s: mark (%s) path for purge", pp->dev, checker_state_name(newstate)); From 865fef73e7b7c62d9032a3fffde9df70b203e2da Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Wed, 28 Jan 2026 15:44:47 +0100 Subject: [PATCH 02/23] test_kpartx: add cleanup action before running kpartx If kpartx crashes and the cleanup action isn't yet pushed, the cleanup will fail. Signed-off-by: Martin Wilck --- kpartx/test-kpartx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kpartx/test-kpartx b/kpartx/test-kpartx index d3c9aef51..065d60a52 100755 --- a/kpartx/test-kpartx +++ b/kpartx/test-kpartx @@ -181,9 +181,9 @@ mk_partitions $LO4 # Test invocation of kpartx with regular file here LO2P1=/dev/mapper/$(basename $LO2)-foo1 +push_cleanup 'dmsetup remove -f $(basename $LO2P1)' $KPARTX $KPARTX_OPTS -a -p -foo $FILE2 [[ -b $LO2P1 ]] -push_cleanup 'dmsetup remove -f $(basename $LO2P1)' step "remove partitions with deleted ptable" wipe_ptable $LO2 @@ -195,8 +195,8 @@ $KPARTX $KPARTX_OPTS -a -p -foo $FILE2 [[ -b $LO2P1 ]] LO1P1=/dev/mapper/$(basename $LO1)-eggs1 -$KPARTX $KPARTX_OPTS -a -p -eggs $LO1 push_cleanup 'dmsetup remove -f $(basename $LO1P1)' +$KPARTX $KPARTX_OPTS -a -p -eggs $LO1 usleep $WAIT_US [[ -b $LO1P1 ]] From 74bb951d8a691ef86bc8d4d7feda9d3acbfa5984 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Wed, 28 Jan 2026 15:46:27 +0100 Subject: [PATCH 03/23] test_kpartx: Use system kpartx for cleanup If our kpartx binary is broken, it's better to use the system kpartx for cleanup, if possible. Signed-off-by: Martin Wilck --- kpartx/test-kpartx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/kpartx/test-kpartx b/kpartx/test-kpartx index 065d60a52..9dbc1ab83 100755 --- a/kpartx/test-kpartx +++ b/kpartx/test-kpartx @@ -83,6 +83,12 @@ step preparation fi } [[ $KPARTX ]] +echo "== Using kpartx = $KPARTX ==" >&2 + +# Try to use system kpartx for cleanup +# Fall back to ours if not found +CLEANUP_KPARTX=$(which kpartx) +[[ "$CLEANUP_KPARTX" ]] || CLEANUP_KPARTX=$KPARTX FILE1=kpartx1 FILE2=kpartx2 @@ -221,13 +227,13 @@ SPAN2P1=/dev/mapper/${SPAN2}-bar1 # udev rules may have created partition mappings without UUIDs # which aren't removed by default (if system standard kpartx doesn't # set the UUID). Remove them using -f -push_cleanup '$KPARTX $KPARTX_OPTS -f -d /dev/mapper/$SPAN2' +push_cleanup '$CLEANUP_KPARTX $KPARTX_OPTS -f -d /dev/mapper/$SPAN2' push_cleanup 'dmsetup remove -f $(basename $SPAN2P1)' $KPARTX $KPARTX_OPTS -a -p -spam /dev/mapper/$SPAN1 SPAN1P1=/dev/mapper/${SPAN1}-spam1 # see above -push_cleanup '$KPARTX $KPARTX_OPTS -f -d /dev/mapper/$SPAN1' +push_cleanup '$CLEANUP_KPARTX $KPARTX_OPTS -f -d /dev/mapper/$SPAN1' push_cleanup 'dmsetup remove -f $(basename $SPAN1P1)' usleep $WAIT_US From 99066e02396c0d7f7dbd1b732297d2b9f8f9b91a Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Wed, 28 Jan 2026 15:50:29 +0100 Subject: [PATCH 04/23] test_kpartx: hide dd output Signed-off-by: Martin Wilck --- kpartx/test-kpartx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kpartx/test-kpartx b/kpartx/test-kpartx index 9dbc1ab83..836b06bea 100755 --- a/kpartx/test-kpartx +++ b/kpartx/test-kpartx @@ -69,7 +69,7 @@ mk_partitions() { } wipe_ptable() { - dd if=/dev/zero of=$1 bs=1b count=1 + dd if=/dev/zero of=$1 bs=1b count=1 &>/dev/null } step preparation From 28b0515a0c04e07306213d8a8d3a6e833998eb1a Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Wed, 28 Jan 2026 15:53:01 +0100 Subject: [PATCH 05/23] test_kpartx: add function to debug current system state If something goes wrong, it's useful to see the current loop device and dm device setup on the system. Signed-off-by: Martin Wilck --- kpartx/test-kpartx | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/kpartx/test-kpartx b/kpartx/test-kpartx index 836b06bea..6441001e4 100755 --- a/kpartx/test-kpartx +++ b/kpartx/test-kpartx @@ -29,6 +29,8 @@ : ${KPARTX:=} # Options to pass to kpartx always : ${KPARTX_OPTS:=-s} +# Set non-empty to enable some debug messages +: ${KPARTX_DEBUG:=} # Time to wait for device nodes to appear (microseconds) # Waiting is only needed if "s" is not in $KPARTX_OPTS : ${WAIT_US:=0} @@ -72,6 +74,12 @@ wipe_ptable() { dd if=/dev/zero of=$1 bs=1b count=1 &>/dev/null } +current_state() { + [[ "$KPARTX_DEBUG" ]] || return 0 + echo "--------------------" + dmsetup ls +} + step preparation [[ $UID -eq 0 ]] @@ -174,6 +182,7 @@ lvcreate --config "$LVMCONF" -L $((SIZE/2))B -n $LV $VG push_cleanup 'lvremove --config "$LVMCONF" -f $VG/$LV' usleep $WAIT_US +current_state [[ -b /dev/mapper/$VG-$LV ]] # dmsetup table /dev/mapper/$VG-$LV @@ -185,37 +194,49 @@ mk_partitions $LO1 mk_partitions $LO2 mk_partitions $LO4 +step "run kpartx on regular file" + # Test invocation of kpartx with regular file here LO2P1=/dev/mapper/$(basename $LO2)-foo1 push_cleanup 'dmsetup remove -f $(basename $LO2P1)' $KPARTX $KPARTX_OPTS -a -p -foo $FILE2 +current_state [[ -b $LO2P1 ]] step "remove partitions with deleted ptable" wipe_ptable $LO2 $KPARTX $KPARTX_OPTS -d $LO2 +current_state [[ ! -b $LO2P1 ]] +step "re-add just removed partions" mk_partitions $LO2 $KPARTX $KPARTX_OPTS -a -p -foo $FILE2 +current_state [[ -b $LO2P1 ]] +step "run kpartx on loop device" LO1P1=/dev/mapper/$(basename $LO1)-eggs1 push_cleanup 'dmsetup remove -f $(basename $LO1P1)' $KPARTX $KPARTX_OPTS -a -p -eggs $LO1 usleep $WAIT_US +current_state [[ -b $LO1P1 ]] [[ -b $LO2P1 ]] # dmsetup info $LO2P1 +step "rename $(basename $LO1P1) to $(basename $LO1)" # Set pathological name for partition on $LO1 (same as loop device itself) dmsetup rename $(basename $LO1P1) $(basename $LO1) LO1P1=/dev/mapper/$(basename $LO1) pop_cleanup push_cleanup 'dmsetup remove -f $(basename $LO1P1)' +current_state +[[ -b $LO1P1 ]] + # dmsetup info $LO1P1 step "create partitions on DM devices" @@ -237,17 +258,20 @@ push_cleanup '$CLEANUP_KPARTX $KPARTX_OPTS -f -d /dev/mapper/$SPAN1' push_cleanup 'dmsetup remove -f $(basename $SPAN1P1)' usleep $WAIT_US +current_state [[ -b $SPAN2P1 ]] [[ -b $SPAN1P1 ]] step "rename partitions on DM device to default" $KPARTX $KPARTX_OPTS -u /dev/mapper/$SPAN1 +current_state [[ ! -b ${SPAN1P1} ]] # This assumes that $SPAN1 ends in a non-digit [[ -b ${SPAN1P1//-spam/} ]] step "rename partitions on DM device back from default" $KPARTX $KPARTX_OPTS -u -p -spam /dev/mapper/$SPAN1 +current_state [[ -b ${SPAN1P1} ]] [[ ! -b ${SPANP1//-foo/} ]] @@ -255,6 +279,7 @@ step "delete partitions on DM devices" $KPARTX $KPARTX_OPTS -d /dev/mapper/$SPAN1 >&2 usleep $WAIT_US +current_state [[ -b $SPAN2P1 ]] [[ -b $LO1P1 ]] [[ -b $LO2P1 ]] @@ -263,29 +288,34 @@ usleep $WAIT_US $KPARTX $KPARTX_OPTS -d /dev/mapper/$SPAN2 usleep $WAIT_US +current_state [[ -b $LO1P1 ]] [[ -b $LO2P1 ]] [[ ! -b $SPAN2P1 ]] step "rename partitions on loop device" $KPARTX $KPARTX_OPTS -u -p -spam $LO2 +current_state [[ ! -b ${LO2P1} ]] [[ -b ${LO2P1//-foo/-spam} ]] step "rename partitions on loop device back" $KPARTX $KPARTX_OPTS -u -p -foo $LO2 +current_state [[ -b ${LO2P1} ]] [[ ! -b ${LO2P1//-foo/-spam} ]] step "rename partitions on loop device to default" $KPARTX $KPARTX_OPTS -u $LO2 #read a +current_state [[ ! -b ${LO2P1} ]] # $LO1 ends in a digit [[ -b ${LO2P1//-foo/p} ]] step "rename partitions on loop device back from default" $KPARTX $KPARTX_OPTS -u -p -foo $LO2 +current_state [[ -b ${LO2P1} ]] [[ ! -b ${LO2P1//-foo/p} ]] @@ -302,11 +332,15 @@ $KPARTX $KPARTX_OPTS -d $LO1 usleep $WAIT_US # ls -l /dev/mapper +current_state [[ ! -b $LO1P1 ]] +# 'dmsetup remove -f $(basename $SPAN1P1)' pop_cleanup [[ ! -b $LO2P1 ]] +# '$CLEANUP_KPARTX $KPARTX_OPTS -f -d /dev/mapper/$SPAN1' pop_cleanup # spans should not have been removed +current_state [[ -b /dev/mapper/$SPAN1 ]] [[ -b /dev/mapper/$SPAN2 ]] [[ -b /dev/mapper/$USER1 ]] From 033ee8dfea6e6bb06a91304a678b591c5e15573d Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Wed, 28 Jan 2026 15:54:06 +0100 Subject: [PATCH 06/23] test_kpartx: run cleanly on systems using lvmdevices Signed-off-by: Martin Wilck --- kpartx/test-kpartx | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/kpartx/test-kpartx b/kpartx/test-kpartx index 6441001e4..159241e11 100755 --- a/kpartx/test-kpartx +++ b/kpartx/test-kpartx @@ -107,7 +107,6 @@ SECTSIZ=512 OFFS=32 # offset of linear mapping into dev, sectors VG=kpvg # volume group name LV=kplv # logical vol name -LVMCONF='devices { filter = [ "a|/dev/loop.*|", r".*" ] }' OK= @@ -116,6 +115,21 @@ OK= push_cleanup 'rm -rf $WORKDIR' } +USE_DEVICESFILE= +if [[ "$((lvmconfig --typeconfig full 2>/dev/null || true) | \ + sed -n 's/.*use_devicesfile=//p')" = 1 ]]; then + LVMCONF= + # This test may modify the devices file. Be sure to restore it + if [[ -f /etc/lvm/devices/system.devices ]]; then + cp -a /etc/lvm/devices/system.devices "$WORKDIR" + push_cleanup 'cp -a $WORKDIR/system.devices /etc/lvm/devices' + else + push_cleanup 'rm -f /etc/lvm/devices/system.devices' + fi +else + LVMCONF='devices { filter = [ "a|/dev/loop.*|", r".*" ] }' +fi + push_cleanup "cd $PWD" cd "$WORKDIR" @@ -175,11 +189,11 @@ usleep $WAIT_US step "create vg on $LO3" # On the 3rd loop device, we create a VG and an LV # The LV should not be removed by kpartx. -pvcreate --config "$LVMCONF" -f $LO3 -vgcreate --config "$LVMCONF" $VG $LO3 -push_cleanup 'vgremove --config "$LVMCONF" -f $VG' -lvcreate --config "$LVMCONF" -L $((SIZE/2))B -n $LV $VG -push_cleanup 'lvremove --config "$LVMCONF" -f $VG/$LV' +pvcreate ${LVMCONF:+--config "$LVMCONF"} -f $LO3 +vgcreate ${LVMCONF:+--config "$LVMCONF"} $VG $LO3 +push_cleanup 'vgremove ${LVMCONF:+--config "$LVMCONF"} -f $VG' +lvcreate ${LVMCONF:+--config "$LVMCONF"} -L $((SIZE/2))B -n $LV $VG +push_cleanup 'lvremove ${LVMCONF:+--config "$LVMCONF"} -f $VG/$LV' usleep $WAIT_US current_state From 8ecdf284e60f864350a3fd9671580673e2635cc9 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Wed, 28 Jan 2026 15:56:07 +0100 Subject: [PATCH 07/23] test-kpartx: use more unusual VG and LV names Signed-off-by: Martin Wilck --- kpartx/test-kpartx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/kpartx/test-kpartx b/kpartx/test-kpartx index 159241e11..902c764ef 100755 --- a/kpartx/test-kpartx +++ b/kpartx/test-kpartx @@ -105,8 +105,9 @@ FILE4=kpartx4 SIZE=$((1024*1024*1024)) # use bytes as units here SECTSIZ=512 OFFS=32 # offset of linear mapping into dev, sectors -VG=kpvg # volume group name -LV=kplv # logical vol name +VG=__kpvg__ # volume group name +LV=__kplv__ # logical vol name + OK= @@ -166,10 +167,11 @@ TABLE="\ 0 $((SIZE/SECTSIZ-OFFS)) linear $DEV1 $OFFS $((SIZE/SECTSIZ-OFFS)) $((SIZE/SECTSIZ-OFFS)) linear $DEV2 $OFFS" -SPAN1=kpt SPAN2=$(basename $LO2) dmsetup create $SPAN1 <<<"$TABLE" push_cleanup 'dmsetup remove -f $SPAN1' +# This must end in a normal letter +SPAN1=__kpt__ dmsetup create $SPAN2 <<<"$TABLE" push_cleanup 'dmsetup remove -f $SPAN2' From d6307835defa57421bde5945442e4f66f9d0705f Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Wed, 28 Jan 2026 16:12:28 +0100 Subject: [PATCH 08/23] test-kpartx: improve output for errors and cleanup Make this output better readable and understandable. Signed-off-by: Martin Wilck --- kpartx/test-kpartx | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/kpartx/test-kpartx b/kpartx/test-kpartx index 902c764ef..47cd15b8e 100755 --- a/kpartx/test-kpartx +++ b/kpartx/test-kpartx @@ -36,7 +36,10 @@ : ${WAIT_US:=0} # IMPORTANT: The ERR trap is essential for this program to work correctly! -trap 'LINE=$LINENO; trap - ERR; echo "== error in $BASH_COMMAND on line $LINE ==" >&2; exit 1' ERR +trap 'LINE=$LINENO +trap - ERR +eval echo "$0: == error in \\\"\$BASH_COMMAND\\\" \(\\\"$BASH_COMMAND\\\"\) on line $LINE ==" >&2 +exit 1' ERR trap 'cleanup' 0 CLEANUP=: @@ -48,11 +51,17 @@ cleanup() { else echo == step $STEP failed == >&2 fi + if [[ "$KPARTX_DEBUG" ]]; then + eval "echo \"== BEGIN CLEANUP == +$CLEANUP +== END CLEANUP ==\"" + fi eval "$CLEANUP" &>/dev/null } push_cleanup() { - CLEANUP="$@;$CLEANUP" + CLEANUP="$@ +$CLEANUP" } pop_cleanup() { From a47916e6f51362a1ee569656ca8d69fe2c0ed7d8 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Wed, 28 Jan 2026 16:18:26 +0100 Subject: [PATCH 09/23] test-kpartx: support running from kpartx subdirectory If run directly from the kpartx subdirectory, execute the local kpartx binary. Signed-off-by: Martin Wilck --- kpartx/test-kpartx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/kpartx/test-kpartx b/kpartx/test-kpartx index 47cd15b8e..29888c8bc 100755 --- a/kpartx/test-kpartx +++ b/kpartx/test-kpartx @@ -93,7 +93,9 @@ step preparation [[ $UID -eq 0 ]] [[ $KPARTX ]] || { - if [[ -x $PWD/kpartx/kpartx ]]; then + if [[ -f "$PWD/kpartx" && -x "$PWD/kpartx" ]]; then + KPARTX=$PWD/kpartx + elif [[ -f "$PWD/kpartx/kpartx" && -x "$PWD/kpartx/kpartx" ]]; then KPARTX=$PWD/kpartx/kpartx else KPARTX=$(which kpartx) From 2089ef970e5453542389ca3c0775f96c9e5b6d94 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Wed, 28 Jan 2026 18:40:56 +0100 Subject: [PATCH 10/23] test-kpartx: fix shellcheck-reported problems Mostly missing quotes. SC2016 must be generally disabled because it will complain about every push_cleanup invocation. SC2086 produces false positives for KPARTX_OPTS. Signed-off-by: Martin Wilck --- kpartx/test-kpartx | 334 ++++++++++++++++++++++++--------------------- 1 file changed, 181 insertions(+), 153 deletions(-) diff --git a/kpartx/test-kpartx b/kpartx/test-kpartx index 29888c8bc..5bce02b85 100755 --- a/kpartx/test-kpartx +++ b/kpartx/test-kpartx @@ -1,5 +1,8 @@ #! /bin/bash +# Run shellcheck with "-e SC2016", this is causing too many annotations +# (for each push_cleanup invocation). + # This is a unit test program for kpartx, in particular for deleting partitions. # # The rationale is the following: @@ -24,16 +27,16 @@ # Set WORKDIR in environment to existing dir to for persistence # WARNING: existing files will be truncated. # If empty, test will be done in temporary dir -: ${WORKDIR:=} +: "${WORKDIR:=}" # Set this environment variable to test an alternative kpartx executable -: ${KPARTX:=} +: "${KPARTX:=}" # Options to pass to kpartx always -: ${KPARTX_OPTS:=-s} +: "${KPARTX_OPTS:=-s}" # Set non-empty to enable some debug messages -: ${KPARTX_DEBUG:=} +: "${KPARTX_DEBUG:=}" # Time to wait for device nodes to appear (microseconds) # Waiting is only needed if "s" is not in $KPARTX_OPTS -: ${WAIT_US:=0} +: "${WAIT_US:=0}" # IMPORTANT: The ERR trap is essential for this program to work correctly! trap 'LINE=$LINENO @@ -46,10 +49,10 @@ CLEANUP=: cleanup() { trap - ERR trap - 0 - if [[ $OK ]]; then - echo == all tests completed successfully == >&2 + if [[ "$OK" ]]; then + echo "== all tests completed successfully ==" >&2 else - echo == step $STEP failed == >&2 + echo "== step $STEP failed ==" >&2 fi if [[ "$KPARTX_DEBUG" ]]; then eval "echo \"== BEGIN CLEANUP == @@ -60,7 +63,7 @@ $CLEANUP } push_cleanup() { - CLEANUP="$@ + CLEANUP="$* $CLEANUP" } @@ -70,17 +73,17 @@ pop_cleanup() { } step() { - STEP="$@" - echo == Test step: $STEP == >&2 + STEP=$* + echo "== Test step: $STEP ==" >&2 } mk_partitions() { - parted -s $1 mklabel msdos - parted -s -- $1 mkpart prim ext2 1MiB -1s + parted -s "$1" mklabel msdos + parted -s -- "$1" mkpart prim ext2 1MiB -1s } wipe_ptable() { - dd if=/dev/zero of=$1 bs=1b count=1 &>/dev/null + dd if=/dev/zero of="$1" bs=1b count=1 &>/dev/null } current_state() { @@ -91,8 +94,8 @@ current_state() { step preparation -[[ $UID -eq 0 ]] -[[ $KPARTX ]] || { +[[ "$UID" -eq 0 ]] +[[ "$KPARTX" ]] || { if [[ -f "$PWD/kpartx" && -x "$PWD/kpartx" ]]; then KPARTX=$PWD/kpartx elif [[ -f "$PWD/kpartx/kpartx" && -x "$PWD/kpartx/kpartx" ]]; then @@ -101,7 +104,7 @@ step preparation KPARTX=$(which kpartx) fi } -[[ $KPARTX ]] +[[ "$KPARTX" ]] echo "== Using kpartx = $KPARTX ==" >&2 # Try to use system kpartx for cleanup @@ -118,18 +121,16 @@ SECTSIZ=512 OFFS=32 # offset of linear mapping into dev, sectors VG=__kpvg__ # volume group name LV=__kplv__ # logical vol name - - OK= -[[ $WORKDIR ]] || { +[[ "$WORKDIR" ]] || { WORKDIR=$(mktemp -d /tmp/kpartx-XXXXXX) - push_cleanup 'rm -rf $WORKDIR' + # shellcheck disable=SC2016 + push_cleanup 'rm -rf "$WORKDIR"' } -USE_DEVICESFILE= -if [[ "$((lvmconfig --typeconfig full 2>/dev/null || true) | \ - sed -n 's/.*use_devicesfile=//p')" = 1 ]]; then +if [[ "$( (lvmconfig --typeconfig full 2>/dev/null || true) | \ + sed -n 's/.*use_devicesfile=//p' )" = 1 ]]; then LVMCONF= # This test may modify the devices file. Be sure to restore it if [[ -f /etc/lvm/devices/system.devices ]]; then @@ -139,33 +140,36 @@ if [[ "$((lvmconfig --typeconfig full 2>/dev/null || true) | \ push_cleanup 'rm -f /etc/lvm/devices/system.devices' fi else + # This isn't shell code, it's actually a single lvm command argument. + # shellcheck disable=SC2089 LVMCONF='devices { filter = [ "a|/dev/loop.*|", r".*" ] }' fi push_cleanup "cd $PWD" +# If cd fails, the script will terminate +# shellcheck disable=SC2164 cd "$WORKDIR" step "create loop devices" -truncate -s $SIZE $FILE1 -truncate -s $SIZE $FILE2 -truncate -s $SIZE $FILE3 -truncate -s $SIZE $FILE4 - -LO1=$(losetup -f $FILE1 --show) -push_cleanup 'losetup -d $LO1' -LO2=$(losetup -f $FILE2 --show) -push_cleanup 'losetup -d $LO2' -LO3=$(losetup -f $FILE3 --show) -push_cleanup 'losetup -d $LO3' -LO4=$(losetup -f $FILE4 --show) -push_cleanup 'losetup -d $LO4' - -[[ $LO1 && $LO2 && $LO3 && $LO4 && -b $LO1 && -b $LO2 && -b $LO3 && -b $LO4 ]] -DEV1=$(stat -c "%t:%T" $LO1) -DEV2=$(stat -c "%t:%T" $LO2) -DEV3=$(stat -c "%t:%T" $LO3) - -usleep $WAIT_US +truncate -s "$SIZE" "$FILE1" +truncate -s "$SIZE" "$FILE2" +truncate -s "$SIZE" "$FILE3" +truncate -s "$SIZE" "$FILE4" + +LO1=$(losetup -f "$FILE1" --show) +push_cleanup 'losetup -d "$LO1"' +LO2=$(losetup -f "$FILE2" --show) +push_cleanup 'losetup -d "$LO2"' +LO3=$(losetup -f "$FILE3" --show) +push_cleanup 'losetup -d "$LO3"' +LO4=$(losetup -f "$FILE4" --show) +push_cleanup 'losetup -d "$LO4"' + +[[ "$LO1" && "$LO2" && "$LO3" && "$LO4" && -b "$LO1" && -b "$LO2" && -b "$LO3" && -b "$LO4" ]] +DEV1=$(stat -c "%t:%T" "$LO1") +DEV2=$(stat -c "%t:%T" "$LO2") + +usleep "$WAIT_US" step "create DM devices (spans)" # Create two linear mappings spanning two loopdevs. @@ -178,225 +182,249 @@ TABLE="\ 0 $((SIZE/SECTSIZ-OFFS)) linear $DEV1 $OFFS $((SIZE/SECTSIZ-OFFS)) $((SIZE/SECTSIZ-OFFS)) linear $DEV2 $OFFS" -SPAN2=$(basename $LO2) -dmsetup create $SPAN1 <<<"$TABLE" -push_cleanup 'dmsetup remove -f $SPAN1' # This must end in a normal letter SPAN1=__kpt__ +SPAN2=$(basename "$LO2") +dmsetup create "$SPAN1" <<<"$TABLE" +push_cleanup 'dmsetup remove -f "$SPAN1"' -dmsetup create $SPAN2 <<<"$TABLE" -push_cleanup 'dmsetup remove -f $SPAN2' +dmsetup create "$SPAN2" <<<"$TABLE" +push_cleanup 'dmsetup remove -f "$SPAN2"' # This is a non-kpartx pseudo "partition" mapping USER1=user1 -push_cleanup 'dmsetup remove -f $USER1' -dmsetup create $USER1 <&2 -usleep $WAIT_US +# shellcheck disable=2086 +$KPARTX $KPARTX_OPTS -d "/dev/mapper/$SPAN1" >&2 +usleep "$WAIT_US" current_state -[[ -b $SPAN2P1 ]] -[[ -b $LO1P1 ]] -[[ -b $LO2P1 ]] -[[ ! -b $SPAN1P1 ]] +[[ -b "$SPAN2P1" ]] +[[ -b "$LO1P1" ]] +[[ -b "$LO2P1" ]] +[[ ! -b "$SPAN1P1" ]] -$KPARTX $KPARTX_OPTS -d /dev/mapper/$SPAN2 -usleep $WAIT_US +# shellcheck disable=2086 +$KPARTX $KPARTX_OPTS -d "/dev/mapper/$SPAN2" +usleep "$WAIT_US" current_state -[[ -b $LO1P1 ]] -[[ -b $LO2P1 ]] -[[ ! -b $SPAN2P1 ]] +[[ -b "$LO1P1" ]] +[[ -b "$LO2P1" ]] +[[ ! -b "$SPAN2P1" ]] step "rename partitions on loop device" -$KPARTX $KPARTX_OPTS -u -p -spam $LO2 +# shellcheck disable=2086 +$KPARTX $KPARTX_OPTS -u -p -spam "$LO2" current_state -[[ ! -b ${LO2P1} ]] -[[ -b ${LO2P1//-foo/-spam} ]] +[[ ! -b "$LO2P1" ]] +[[ -b "${LO2P1//-foo/-spam}" ]] step "rename partitions on loop device back" -$KPARTX $KPARTX_OPTS -u -p -foo $LO2 +# shellcheck disable=2086 +$KPARTX $KPARTX_OPTS -u -p -foo "$LO2" current_state -[[ -b ${LO2P1} ]] -[[ ! -b ${LO2P1//-foo/-spam} ]] +[[ -b "$LO2P1" ]] +[[ ! -b "${LO2P1//-foo/-spam}" ]] step "rename partitions on loop device to default" -$KPARTX $KPARTX_OPTS -u $LO2 +# shellcheck disable=2086 +$KPARTX $KPARTX_OPTS -u "$LO2" #read a current_state -[[ ! -b ${LO2P1} ]] +[[ ! -b "$LO2P1" ]] # $LO1 ends in a digit -[[ -b ${LO2P1//-foo/p} ]] +[[ -b "${LO2P1//-foo/p}" ]] step "rename partitions on loop device back from default" -$KPARTX $KPARTX_OPTS -u -p -foo $LO2 +# shellcheck disable=2086 +$KPARTX $KPARTX_OPTS -u -p -foo "$LO2" current_state -[[ -b ${LO2P1} ]] -[[ ! -b ${LO2P1//-foo/p} ]] +[[ -b "$LO2P1" ]] +[[ ! -b "${LO2P1//-foo/p}" ]] step "rename partitions on loop devices" -$KPARTX $KPARTX_OPTS -u -p spam $LO2 +# shellcheck disable=2086 +$KPARTX $KPARTX_OPTS -u -p spam "$LO2" step "delete partitions on loop devices" -$KPARTX $KPARTX_OPTS -d $LO3 +# shellcheck disable=2086 +$KPARTX $KPARTX_OPTS -d "$LO3" # This will also delete the loop device -$KPARTX $KPARTX_OPTS -d $FILE2 -$KPARTX $KPARTX_OPTS -d $LO1 -usleep $WAIT_US +# shellcheck disable=2086 +$KPARTX $KPARTX_OPTS -d "$FILE2" +# shellcheck disable=2086 +$KPARTX $KPARTX_OPTS -d "$LO1" +usleep "$WAIT_US" # ls -l /dev/mapper current_state -[[ ! -b $LO1P1 ]] -# 'dmsetup remove -f $(basename $SPAN1P1)' +[[ ! -b "$LO1P1" ]] +# 'dmsetup remove -f $(basename "$SPAN1P1")' pop_cleanup -[[ ! -b $LO2P1 ]] +[[ ! -b "$LO2P1" ]] # '$CLEANUP_KPARTX $KPARTX_OPTS -f -d /dev/mapper/$SPAN1' pop_cleanup # spans should not have been removed current_state -[[ -b /dev/mapper/$SPAN1 ]] -[[ -b /dev/mapper/$SPAN2 ]] -[[ -b /dev/mapper/$USER1 ]] +[[ -b "/dev/mapper/$SPAN1" ]] +[[ -b "/dev/mapper/$SPAN2" ]] +[[ -b "/dev/mapper/$USER1" ]] # LVs neither -[[ -b /dev/mapper/$VG-$LV ]] +[[ -b "/dev/mapper/$VG-$LV" ]] step "delete partitions on $LO3 with -f" -$KPARTX $KPARTX_OPTS -f -d $LO3 +# shellcheck disable=2086 +$KPARTX $KPARTX_OPTS -f -d "$LO3" # -d -f should delete the LV, too -[[ ! -b /dev/mapper/$VG-$LV ]] -[[ -b /dev/mapper/$SPAN1 ]] -[[ -b /dev/mapper/$SPAN2 ]] +[[ ! -b "/dev/mapper/$VG-$LV" ]] +[[ -b "/dev/mapper/$SPAN1" ]] +[[ -b "/dev/mapper/$SPAN2" ]] step "test kpartx creation/deletion on an image file with no existing loopdev" -losetup -d $LO4 +losetup -d "$LO4" -OUTPUT=$($KPARTX $KPARTX_OPTS -v -a $FILE4 2>&1) -read loop dm < \ +# shellcheck disable=2086 +OUTPUT=$($KPARTX $KPARTX_OPTS -v -a "$FILE4" 2>&1) +read -r loop dm < \ <(sed -n 's/^add map \(loop[0-9]*\)p1 ([0-9]*:\([0-9]*\)).*$/\1 dm-\2/p' \ - <<<$OUTPUT) -[[ $dm && $loop ]] -push_cleanup "dmsetup remove -f /dev/$dm" -push_cleanup "losetup -d /dev/$loop" - -[[ -b /dev/mapper/${loop}p1 ]] -$KPARTX -d $KPARTX_OPTS $FILE4 -[[ ! -b /dev/mapper/${loop}p1 ]] + <<<"$OUTPUT") +[[ "$dm" && "$loop" ]] +push_cleanup 'dmsetup remove -f "/dev/$dm"' +push_cleanup 'losetup -d "/dev/$loop"' + +[[ -b "/dev/mapper/${loop}p1" ]] +# shellcheck disable=2086 +$KPARTX -d $KPARTX_OPTS "$FILE4" +[[ ! -b "/dev/mapper/${loop}p1" ]] # /dev/$loop is _not_ automatically deleted -[[ -b /dev/${loop} ]] +[[ -b "/dev/${loop}" ]] OK=yes From 1a7ea48c8c810774709fca496c9e64fa7d97a7a4 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Wed, 28 Jan 2026 23:04:33 +0100 Subject: [PATCH 11/23] test-kpartx: make partition renaming test more robust Signed-off-by: Martin Wilck --- kpartx/test-kpartx | 35 ++++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/kpartx/test-kpartx b/kpartx/test-kpartx index 5bce02b85..46c37a407 100755 --- a/kpartx/test-kpartx +++ b/kpartx/test-kpartx @@ -92,6 +92,20 @@ current_state() { dmsetup ls } + +# Default name of partition device +partname() { + local base + base=${1##*/} + + # Is the last character a digit? + if [[ "${base:$((${#base}-1))}" =~ [0-9] ]]; then + printf "/dev/mapper/%sp%s" "$base" "$2" + else + printf "/dev/mapper/%s%s" "$base" "$2" + fi +} + step preparation [[ "$UID" -eq 0 ]] @@ -302,20 +316,20 @@ current_state [[ -b "$SPAN2P1" ]] [[ -b "$SPAN1P1" ]] -step "rename partitions on DM device to default" +step "rename partitions on DM device with default delimiter" # shellcheck disable=2086 $KPARTX $KPARTX_OPTS -u "/dev/mapper/$SPAN1" current_state [[ ! -b "$SPAN1P1" ]] # This assumes that $SPAN1 ends in a non-digit -[[ -b "${SPAN1P1//-spam/}" ]] +[[ -b "$(partname "$SPAN1" 1)" ]] -step "rename partitions on DM device back from default" +step "rename partitions on DM device with delimiter -spam" # shellcheck disable=2086 $KPARTX $KPARTX_OPTS -u -p -spam "/dev/mapper/$SPAN1" current_state [[ -b "$SPAN1P1" ]] -[[ ! -b "${SPAN1P1//-foo/}" ]] +[[ ! -b "$(partname "$SPAN1" 1)" ]] step "delete partitions on DM devices" # shellcheck disable=2086 @@ -337,35 +351,34 @@ current_state [[ -b "$LO2P1" ]] [[ ! -b "$SPAN2P1" ]] -step "rename partitions on loop device" +step "rename partitions on loop device with delimiter -spam" # shellcheck disable=2086 $KPARTX $KPARTX_OPTS -u -p -spam "$LO2" current_state [[ ! -b "$LO2P1" ]] [[ -b "${LO2P1//-foo/-spam}" ]] -step "rename partitions on loop device back" +step "rename partitions on loop device with delimiter -foo" # shellcheck disable=2086 $KPARTX $KPARTX_OPTS -u -p -foo "$LO2" current_state [[ -b "$LO2P1" ]] [[ ! -b "${LO2P1//-foo/-spam}" ]] -step "rename partitions on loop device to default" +step "rename partitions on loop device with default delimiter" # shellcheck disable=2086 $KPARTX $KPARTX_OPTS -u "$LO2" #read a current_state [[ ! -b "$LO2P1" ]] -# $LO1 ends in a digit -[[ -b "${LO2P1//-foo/p}" ]] +[[ -b "$(partname "$LO2" 1)" ]] -step "rename partitions on loop device back from default" +step "rename partitions on loop device with delimiter -foo" # shellcheck disable=2086 $KPARTX $KPARTX_OPTS -u -p -foo "$LO2" current_state [[ -b "$LO2P1" ]] -[[ ! -b "${LO2P1//-foo/p}" ]] +[[ ! -b "$(partname "$LO2" 1)" ]] step "rename partitions on loop devices" # shellcheck disable=2086 From 5813a60d6013b0c0965b9e8e5884185c4c72b0f5 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Wed, 28 Jan 2026 23:45:21 +0100 Subject: [PATCH 12/23] test-kpartx: use "command -v" instead of "which" "which" may not always be awailable. Signed-off-by: Martin Wilck --- kpartx/test-kpartx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/kpartx/test-kpartx b/kpartx/test-kpartx index 46c37a407..d3c61dd31 100755 --- a/kpartx/test-kpartx +++ b/kpartx/test-kpartx @@ -115,7 +115,7 @@ step preparation elif [[ -f "$PWD/kpartx/kpartx" && -x "$PWD/kpartx/kpartx" ]]; then KPARTX=$PWD/kpartx/kpartx else - KPARTX=$(which kpartx) + KPARTX=$(command -v kpartx) fi } [[ "$KPARTX" ]] @@ -123,8 +123,8 @@ echo "== Using kpartx = $KPARTX ==" >&2 # Try to use system kpartx for cleanup # Fall back to ours if not found -CLEANUP_KPARTX=$(which kpartx) -[[ "$CLEANUP_KPARTX" ]] || CLEANUP_KPARTX=$KPARTX +# shellcheck disable=SC2034 +CLEANUP_KPARTX=$(command -v kpartx) || CLEANUP_KPARTX=$KPARTX FILE1=kpartx1 FILE2=kpartx2 From 525d5632ae60abec5dbf7f7944c156eff577e5a4 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Wed, 28 Jan 2026 23:46:10 +0100 Subject: [PATCH 13/23] test-kpartx: use sleep for microsecond sleep The usleep command may not always be available. Signed-off-by: Martin Wilck --- kpartx/test-kpartx | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/kpartx/test-kpartx b/kpartx/test-kpartx index d3c61dd31..22bea726d 100755 --- a/kpartx/test-kpartx +++ b/kpartx/test-kpartx @@ -106,6 +106,10 @@ partname() { fi } +wait_a_moment() { + sleep "$(printf %d.%06d "$((WAIT_USEC / 1000000))" "$((WAIT_USEC % 1000000))")" +} + step preparation [[ "$UID" -eq 0 ]] @@ -183,7 +187,7 @@ push_cleanup 'losetup -d "$LO4"' DEV1=$(stat -c "%t:%T" "$LO1") DEV2=$(stat -c "%t:%T" "$LO2") -usleep "$WAIT_US" +wait_a_moment step "create DM devices (spans)" # Create two linear mappings spanning two loopdevs. @@ -212,7 +216,7 @@ dmsetup create "$USER1" <&2 -usleep "$WAIT_US" +wait_a_moment current_state [[ -b "$SPAN2P1" ]] @@ -344,7 +348,7 @@ current_state # shellcheck disable=2086 $KPARTX $KPARTX_OPTS -d "/dev/mapper/$SPAN2" -usleep "$WAIT_US" +wait_a_moment current_state [[ -b "$LO1P1" ]] @@ -394,7 +398,7 @@ $KPARTX $KPARTX_OPTS -d "$LO3" $KPARTX $KPARTX_OPTS -d "$FILE2" # shellcheck disable=2086 $KPARTX $KPARTX_OPTS -d "$LO1" -usleep "$WAIT_US" +wait_a_moment # ls -l /dev/mapper current_state From 37f0d634eec198b67854273d23dba0513058b1f6 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Thu, 29 Jan 2026 21:26:20 +0100 Subject: [PATCH 14/23] test-kpartx: also add trap for signals Signed-off-by: Martin Wilck --- kpartx/test-kpartx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kpartx/test-kpartx b/kpartx/test-kpartx index 22bea726d..a3e8a2c40 100755 --- a/kpartx/test-kpartx +++ b/kpartx/test-kpartx @@ -42,7 +42,7 @@ trap 'LINE=$LINENO trap - ERR eval echo "$0: == error in \\\"\$BASH_COMMAND\\\" \(\\\"$BASH_COMMAND\\\"\) on line $LINE ==" >&2 -exit 1' ERR +exit 1' ERR INT TERM trap 'cleanup' 0 CLEANUP=: From 57fcf59b772166f5169b914d40aed508863108ab Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Thu, 29 Jan 2026 21:26:20 +0100 Subject: [PATCH 15/23] test-kpartx: use sfdisk instead of parted parted has functionalilty to create partition mappings, which is not desired in our case. Signed-off-by: Martin Wilck --- kpartx/test-kpartx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/kpartx/test-kpartx b/kpartx/test-kpartx index a3e8a2c40..4dae39cbe 100755 --- a/kpartx/test-kpartx +++ b/kpartx/test-kpartx @@ -78,8 +78,14 @@ step() { } mk_partitions() { - parted -s "$1" mklabel msdos - parted -s -- "$1" mkpart prim ext2 1MiB -1s + # prefer sfdisk, as parted will try to create + # partition mappings by itself + if command -v sfdisk &>/dev/null; then + printf ",+,\n" | sfdisk -f "$1" &>/dev/null + else + parted -s "$1" mklabel msdos + parted -s -- "$1" mkpart prim ext2 1MiB -1s + fi } wipe_ptable() { From 785a75e4cef9850f89ddc1d8f2237379387efbea Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Thu, 29 Jan 2026 21:31:04 +0100 Subject: [PATCH 16/23] test-kpartx: improve step descriptions to ease debugging Signed-off-by: Martin Wilck --- kpartx/test-kpartx | 82 ++++++++++++++++++++++++++-------------------- 1 file changed, 46 insertions(+), 36 deletions(-) diff --git a/kpartx/test-kpartx b/kpartx/test-kpartx index 4dae39cbe..32a5aef01 100755 --- a/kpartx/test-kpartx +++ b/kpartx/test-kpartx @@ -195,7 +195,11 @@ DEV2=$(stat -c "%t:%T" "$LO2") wait_a_moment -step "create DM devices (spans)" +SPAN1=__kpt__ +SPAN2=$(basename "$LO2") +# This is a non-kpartx pseudo "partition" mapping +USER1=user1 +step "create DM devices => $SPAN1, $SPAN2, $USER1" # Create two linear mappings spanning two loopdevs. # One of them gets a pathological name colliding with # the loop device name. @@ -206,17 +210,12 @@ TABLE="\ 0 $((SIZE/SECTSIZ-OFFS)) linear $DEV1 $OFFS $((SIZE/SECTSIZ-OFFS)) $((SIZE/SECTSIZ-OFFS)) linear $DEV2 $OFFS" -# This must end in a normal letter -SPAN1=__kpt__ -SPAN2=$(basename "$LO2") dmsetup create "$SPAN1" <<<"$TABLE" push_cleanup 'dmsetup remove -f "$SPAN1"' dmsetup create "$SPAN2" <<<"$TABLE" push_cleanup 'dmsetup remove -f "$SPAN2"' -# This is a non-kpartx pseudo "partition" mapping -USER1=user1 push_cleanup 'dmsetup remove -f "$USER1"' dmsetup create "$USER1" < $VG-$LV, $USER1" # On the 3rd loop device, we create a VG and an LV # The LV should not be removed by kpartx. # shellcheck disable=SC2090 @@ -246,38 +245,38 @@ current_state # dmsetup table /dev/mapper/$VG-$LV # dmsetup info /dev/mapper/$VG-$LV -step "create partitions on loop devices" +step "create partitions on loop devices => $LO1 $LO2 $LO4" mk_partitions "$LO1" mk_partitions "$LO2" mk_partitions "$LO4" -step "run kpartx on regular file" # Test invocation of kpartx with regular file here LO2P1=/dev/mapper/$(basename "$LO2")-foo1 push_cleanup 'dmsetup remove -f "$(basename $LO2P1)"' +step "run kpartx on regular file $FILE2 => $LO2P1" # shellcheck disable=2086 $KPARTX $KPARTX_OPTS -a -p -foo "$FILE2" current_state [[ -b "$LO2P1" ]] -step "remove partitions with deleted ptable" +step "deleting partition table on $LO2 => -$LO2P1" wipe_ptable "$LO2" # shellcheck disable=2086 $KPARTX $KPARTX_OPTS -d "$LO2" current_state [[ ! -b "$LO2P1" ]] -step "re-add just removed partions" +step "re-add just removed partions on $LO2 => $LO2P1" mk_partitions "$LO2" # shellcheck disable=2086 $KPARTX $KPARTX_OPTS -a -p -foo "$FILE2" current_state [[ -b "$LO2P1" ]] -step "run kpartx on loop device" LO1P1=/dev/mapper/$(basename "$LO1")-eggs1 +step "run kpartx on loop device $LO1 => $LO1P1" push_cleanup 'dmsetup remove -f $(basename "$LO1P1")' # shellcheck disable=2086 $KPARTX $KPARTX_OPTS -a -p -eggs "$LO1" @@ -289,7 +288,7 @@ current_state # dmsetup info $LO2P1 -step "rename $(basename "$LO1P1") to $(basename "$LO1")" +step "rename $(basename "$LO1P1") -> $(basename "$LO1")" # Set pathological name for partition on $LO1 (same as loop device itself) dmsetup rename "$(basename "$LO1P1")" "$(basename "$LO1")" LO1P1=/dev/mapper/$(basename "$LO1") @@ -299,14 +298,14 @@ push_cleanup 'dmsetup remove -f "$(basename "$LO1P1")"' current_state [[ -b "$LO1P1" ]] -# dmsetup info $LO1P1 - -step "create partitions on DM devices" mk_partitions "/dev/mapper/$SPAN2" - +SPAN2P1=/dev/mapper/${SPAN2}-bar1 +step "create partitions on DM device $SPAN2 => $SPAN2P1" # shellcheck disable=2086 $KPARTX $KPARTX_OPTS -a -p -bar "/dev/mapper/$SPAN2" -SPAN2P1=/dev/mapper/${SPAN2}-bar1 +wait_a_moment +current_state +[[ -b "$SPAN2P1" ]] # udev rules may have created partition mappings without UUIDs # which aren't removed by default (if system standard kpartx doesn't @@ -314,6 +313,7 @@ SPAN2P1=/dev/mapper/${SPAN2}-bar1 push_cleanup '$CLEANUP_KPARTX $KPARTX_OPTS -f -d "/dev/mapper/$SPAN2"' push_cleanup 'dmsetup remove -f "$(basename "$SPAN2P1")"' +step "create partitions on DM device $SPAN1 => $SPAN1P1" # shellcheck disable=2086 $KPARTX $KPARTX_OPTS -a -p -spam "/dev/mapper/$SPAN1" SPAN1P1=/dev/mapper/${SPAN1}-spam1 @@ -326,7 +326,7 @@ current_state [[ -b "$SPAN2P1" ]] [[ -b "$SPAN1P1" ]] -step "rename partitions on DM device with default delimiter" +step "rename partitions on $SPAN1 with default delimiter => $SPAN1P1 -> $(partname "$SPAN1" 1)" # shellcheck disable=2086 $KPARTX $KPARTX_OPTS -u "/dev/mapper/$SPAN1" current_state @@ -334,14 +334,14 @@ current_state # This assumes that $SPAN1 ends in a non-digit [[ -b "$(partname "$SPAN1" 1)" ]] -step "rename partitions on DM device with delimiter -spam" +step "rename partitions on DM device with delimiter -spam => $(partname "$SPAN1" 1) -> $SPAN1P1" # shellcheck disable=2086 $KPARTX $KPARTX_OPTS -u -p -spam "/dev/mapper/$SPAN1" current_state [[ -b "$SPAN1P1" ]] [[ ! -b "$(partname "$SPAN1" 1)" ]] -step "delete partitions on DM devices" +step "delete partitions on DM device $SPAN1 => -$SPAN1P1" # shellcheck disable=2086 $KPARTX $KPARTX_OPTS -d "/dev/mapper/$SPAN1" >&2 wait_a_moment @@ -352,68 +352,78 @@ current_state [[ -b "$LO2P1" ]] [[ ! -b "$SPAN1P1" ]] +step "delete partitions on DM device $SPAN2 => -$SPAN2P1" # shellcheck disable=2086 $KPARTX $KPARTX_OPTS -d "/dev/mapper/$SPAN2" wait_a_moment - current_state [[ -b "$LO1P1" ]] [[ -b "$LO2P1" ]] [[ ! -b "$SPAN2P1" ]] -step "rename partitions on loop device with delimiter -spam" +step "rename on $LO2 with delimiter -spam => $LO2P1 -> ${LO2P1//-foo/-spam}" # shellcheck disable=2086 $KPARTX $KPARTX_OPTS -u -p -spam "$LO2" +wait_a_moment current_state [[ ! -b "$LO2P1" ]] [[ -b "${LO2P1//-foo/-spam}" ]] -step "rename partitions on loop device with delimiter -foo" +step "rename partitions on $LO2 with delimiter -foo => ${LO2P1//-foo/-spam} -> $LO2P1" # shellcheck disable=2086 $KPARTX $KPARTX_OPTS -u -p -foo "$LO2" +wait_a_moment current_state [[ -b "$LO2P1" ]] [[ ! -b "${LO2P1//-foo/-spam}" ]] -step "rename partitions on loop device with default delimiter" +step "rename partitions on $LO2 with default delimiter => $LO2P1 -> $(partname "$LO2" 1)" # shellcheck disable=2086 $KPARTX $KPARTX_OPTS -u "$LO2" -#read a +wait_a_moment current_state [[ ! -b "$LO2P1" ]] [[ -b "$(partname "$LO2" 1)" ]] -step "rename partitions on loop device with delimiter -foo" +step "rename partitions on $LO2 with delimiter -foo => $(partname "$LO2" 1) -> $LO2P1" # shellcheck disable=2086 $KPARTX $KPARTX_OPTS -u -p -foo "$LO2" current_state [[ -b "$LO2P1" ]] [[ ! -b "$(partname "$LO2" 1)" ]] -step "rename partitions on loop devices" +step "rename partitions on $LO2 with delimiter spam => $LO2P1 -> ${LO2P1//-foo/spam} " # shellcheck disable=2086 $KPARTX $KPARTX_OPTS -u -p spam "$LO2" +wait_a_moment +current_state +[[ ! -b "$LO2P1" ]] +[[ -b "${LO2P1//-foo/spam}" ]] -step "delete partitions on loop devices" - +step "delete partitions on loop device $LO3" # shellcheck disable=2086 $KPARTX $KPARTX_OPTS -d "$LO3" +wait_a_moment +current_state +step "delete partitions on file $FILE2 / $LO2" # This will also delete the loop device # shellcheck disable=2086 $KPARTX $KPARTX_OPTS -d "$FILE2" +wait_a_moment +current_state + +step "delete partitions on file $LO1" # shellcheck disable=2086 $KPARTX $KPARTX_OPTS -d "$LO1" wait_a_moment - -# ls -l /dev/mapper current_state [[ ! -b "$LO1P1" ]] -# 'dmsetup remove -f $(basename "$SPAN1P1")' -pop_cleanup + +pop_cleanup # 'dmsetup remove -f $(basename "$SPAN1P1")' [[ ! -b "$LO2P1" ]] -# '$CLEANUP_KPARTX $KPARTX_OPTS -f -d /dev/mapper/$SPAN1' -pop_cleanup +pop_cleanup # '$CLEANUP_KPARTX $KPARTX_OPTS -f -d /dev/mapper/$SPAN1' + # spans should not have been removed current_state [[ -b "/dev/mapper/$SPAN1" ]] From bfd063a32510b663c6032fa426188bf6927cae6d Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Wed, 28 Jan 2026 23:04:52 +0100 Subject: [PATCH 17/23] kpartx/Makefile: add test target to run test-kpartx This test must be run as root. Signed-off-by: Martin Wilck --- kpartx/Makefile | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/kpartx/Makefile b/kpartx/Makefile index 849fd36d0..fe83e2905 100644 --- a/kpartx/Makefile +++ b/kpartx/Makefile @@ -40,8 +40,13 @@ uninstall: $(Q)$(RM) $(DESTDIR)$(libudevdir)/rules.d/67-kpartx-compat.rules $(Q)$(RM) $(DESTDIR)$(libudevdir)/rules.d/68-del-part-nodes.rules +kpartx.out: $(EXEC) + ./test-kpartx >$@ 2>&1 || { cat $@; rm -f $@; exit 1; } + +test: kpartx.out + clean: dep_clean - $(Q)$(RM) core *.o $(EXEC) kpartx.rules + $(Q)$(RM) core *.o $(EXEC) kpartx.rules kpartx.out include $(wildcard $(OBJS:.o=.d)) From 124558cb4027457b9c407abd25ed2e41d4ad50eb Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Thu, 29 Jan 2026 18:03:00 +0100 Subject: [PATCH 18/23] kpartx/Makefile: enable debugging for test-kpartx Signed-off-by: Martin Wilck --- kpartx/Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/kpartx/Makefile b/kpartx/Makefile index fe83e2905..cdadaeca8 100644 --- a/kpartx/Makefile +++ b/kpartx/Makefile @@ -43,6 +43,7 @@ uninstall: kpartx.out: $(EXEC) ./test-kpartx >$@ 2>&1 || { cat $@; rm -f $@; exit 1; } +test: export KPARTX_DEBUG := 1 test: kpartx.out clean: dep_clean From f67c2ef2e6002b63e534d8515dfadd87e5ae0f0c Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Wed, 28 Jan 2026 23:47:04 +0100 Subject: [PATCH 19/23] GitHub workflows: add kpartx test to build-and-unittest.yaml Signed-off-by: Martin Wilck --- .github/workflows/build-and-unittest.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/build-and-unittest.yaml b/.github/workflows/build-and-unittest.yaml index 2fb5327d4..fdcc59b73 100644 --- a/.github/workflows/build-and-unittest.yaml +++ b/.github/workflows/build-and-unittest.yaml @@ -67,6 +67,8 @@ jobs: run: rm -f tests/dmevents.out tests/directio.out - name: root-test run: sudo make DIO_TEST_DEV=/dev/zram$ZRAM test + - name: kpartx-test + run: sudo make -C kpartx test noble: runs-on: ubuntu-24.04 strategy: @@ -108,3 +110,5 @@ jobs: run: rm -f tests/dmevents.out tests/directio.out - name: root-test run: sudo make DIO_TEST_DEV=/dev/ram0 test + - name: kpartx-test + run: sudo make -C kpartx test From 900f5359e01256712847f20987ef517808561a5a Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Thu, 29 Jan 2026 15:16:06 +0100 Subject: [PATCH 20/23] Update NEWS.md for 0.14.2 Signed-off-by: Martin Wilck --- NEWS.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/NEWS.md b/NEWS.md index 7aa94cd30..03f337739 100644 --- a/NEWS.md +++ b/NEWS.md @@ -9,6 +9,18 @@ release. These bug fixes will be tracked in stable branches. See [README.md](README.md) for additional information. +## multipath-tools 0.14.2, 2026/01 + +### Bug fixes + +* Fix a possible NULL pointer dereference in the path purging code. + Fixes 0.14.0. Commit 86ff48b. + +### CI + +* Updated the `test_kpartx` test script, and added it to the + `basic-build-and-ci` workflow. + ## multipath-tools 0.14.1, 2026/01 ### Bug fixes From b35369bbbab24c127ee1a006ad59994952684cf3 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Thu, 29 Jan 2026 15:18:06 +0100 Subject: [PATCH 21/23] libmultipath: bump version to 0.14.2 Signed-off-by: Martin Wilck --- libmultipath/version.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libmultipath/version.h b/libmultipath/version.h index ddc7591fb..fa2650b37 100644 --- a/libmultipath/version.h +++ b/libmultipath/version.h @@ -11,9 +11,9 @@ #ifndef VERSION_H_INCLUDED #define VERSION_H_INCLUDED -#define VERSION_CODE 0x000E01 +#define VERSION_CODE 0x000E02 /* MMDDYY, in hex */ -#define DATE_CODE 0x01171A +#define DATE_CODE 0x011D1A #define PROG "multipath-tools" From 957b5cb20abac9536c361940190afc572e423584 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Fri, 30 Jan 2026 11:08:16 +0100 Subject: [PATCH 22/23] GitHub workflows: run abi.yaml for pushes on "master", too In the opensvc/multipath-tools repository, we use "master" as the reference branch for the ABI check, but we didn't run it for pushes to "master" so far (a merge into "master" seems to count as "push"). Therefore, the reference ABI was missing unless it had been triggered manually for "master, and the workflow failed. Run "abi.yaml" for every merge to master. Signed-off-by: Martin Wilck --- .github/workflows/abi.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/abi.yaml b/.github/workflows/abi.yaml index adc2081f1..3b5f1c1e4 100644 --- a/.github/workflows/abi.yaml +++ b/.github/workflows/abi.yaml @@ -2,6 +2,7 @@ name: check-abi on: push: branches: + - master - queue - abi paths: From 04e3297368ca1e0b129fc02cbb02b366e38379b5 Mon Sep 17 00:00:00 2001 From: Martin Wilck Date: Fri, 30 Jan 2026 11:41:49 +0100 Subject: [PATCH 23/23] GitHub workflows: abi-stable: try to download the artifact ... and only rebuild it if necessary Signed-off-by: Martin Wilck --- .github/workflows/abi-stable.yaml | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/.github/workflows/abi-stable.yaml b/.github/workflows/abi-stable.yaml index 8f9a9dbf3..d0ee4543f 100644 --- a/.github/workflows/abi-stable.yaml +++ b/.github/workflows/abi-stable.yaml @@ -30,9 +30,22 @@ jobs: - name: assert parent tag run: /bin/false if: ${{ env.PARENT_TAG == '' }} + - name: try to download ABI for ${{ env.PARENT_TAG }} + id: download_abi + continue-on-error: true + uses: dawidd6/action-download-artifact@v6 + with: + workflow: abi-stable.yaml + workflow_conclusion: '' + branch: ${{ github.ref_name }} + name: multipath-abi-${{ env.PARENT_TAG }} + search_artifacts: true + path: __unused__ - name: update + if: steps.download_abi.outcome != 'success' run: sudo apt-get update - name: dependencies + if: steps.download_abi.outcome != 'success' run: > sudo apt-get install --yes gcc gcc make pkg-config abigail-tools @@ -40,12 +53,15 @@ jobs: libudev-dev libjson-c-dev liburcu-dev libcmocka-dev libedit-dev libmount-dev - name: checkout ${{ env.PARENT_TAG }} + if: steps.download_abi.outcome != 'success' uses: actions/checkout@v4 with: ref: ${{ env.PARENT_TAG }} - name: build ABI for ${{ env.PARENT_TAG }} + if: steps.download_abi.outcome != 'success' run: make -j$(nproc) -Orecurse abi - name: save ABI + if: steps.download_abi.outcome != 'success' uses: actions/upload-artifact@v4 with: name: multipath-abi-${{ env.PARENT_TAG }} @@ -74,13 +90,16 @@ jobs: ref: ${{ github.ref }} - name: download ABI for ${{ env.PARENT_TAG }} id: download_abi - uses: actions/download-artifact@v4 + uses: dawidd6/action-download-artifact@v6 with: + workflow: abi-stable.yaml + workflow_conclusion: '' + branch: ${{ github.ref_name }} name: multipath-abi-${{ env.PARENT_TAG }} + search_artifacts: true path: reference-abi - name: update run: sudo apt-get update - if: steps.download_abi.outcome != 'success' - name: dependencies run: > sudo apt-get install --yes gcc